Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/friendly-masks-vanish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@react-native-documents/viewer": major
---

fix: viewer module errorCodes
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,18 @@ class RNDocumentViewerModule(reactContext: ReactApplicationContext) : NativeDocu
permissions: String,
mimeType: String?,
title: String?,
androidApplicationId: String?,
presentation: String?,
promise: Promise
) {
val currentActivity = reactApplicationContext.currentActivity
if (currentActivity == null) {
rejectWithNullActivity(promise)
return
}
val currentActivity = reactApplicationContext.currentActivity ?: return rejectWithNullActivity(promise)
if (BuildConfig.DEBUG && mimeType != null && !MimeTypeMap.getSingleton().hasMimeType(mimeType)) {
RNLog.w(
reactApplicationContext, "$mimeType appears to be an unusual mime type, are you sure it's correct?")
}

try {
val (uriToOpen, intentFlags) = constructUri(bookmarkOrUri, permissions)
val (uriToOpen, intentFlags) = constructUri(bookmarkOrUri, permissions, androidApplicationId)

// grantUriPermission is not needed (for file uris WE OWN), we're using the Flags
// on a Uri returned by FileProvider.getUriForFile()
Expand All @@ -53,17 +50,19 @@ class RNDocumentViewerModule(reactContext: ReactApplicationContext) : NativeDocu
}
}

private fun constructUri(bookmarkOrUri: String, permissions: String): UriWithFlags {
private fun constructUri(bookmarkOrUri: String, permissions: String, androidApplicationId: String?): UriWithFlags {
val flags = when (permissions) {
"write" -> Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
else -> Intent.FLAG_GRANT_READ_URI_PERMISSION
}
return if (bookmarkOrUri.startsWith("content://")) {
UriWithFlags(Uri.parse(bookmarkOrUri), flags)
} else if (bookmarkOrUri.startsWith("file://")) {
// Package name may not be the same as applicationId but usually is.
// Also see document-viewer/android/src/main/AndroidManifest.xml
val applicationId = androidApplicationId ?: reactApplicationContext.packageName
val authority = "$applicationId.reactnativedocumentviewer.fileprovider"
val uri = Uri.parse(bookmarkOrUri)
// TODO package name may not be the same as applicationId. Also see document-viewer/android/src/main/AndroidManifest.xml
val authority = reactApplicationContext.packageName + ".reactnativedocumentviewer.fileprovider"
val uriPath = uri.path ?: throw IllegalArgumentException("file:// uri must have a path")
val fileUri = FileProvider.getUriForFile(
reactApplicationContext,
Expand All @@ -81,10 +80,10 @@ class RNDocumentViewerModule(reactContext: ReactApplicationContext) : NativeDocu

companion object {
fun rejectWithNullActivity(promise: Promise) {
promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "activity is null")
promise.reject(NULL_PRESENTER, "current activity is null")
}

private const val E_ACTIVITY_DOES_NOT_EXIST = "ACTIVITY_DOES_NOT_EXIST"
private const val NULL_PRESENTER = "NULL_PRESENTER"
private const val UNABLE_TO_OPEN_FILE_TYPE = "UNABLE_TO_OPEN_FILE_TYPE"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,5 @@ public NativeDocumentViewerSpec(ReactApplicationContext reactContext) {

@ReactMethod
@DoNotStrip
public abstract void viewDocument(String bookmarkOrUri, String permissions, @Nullable String mimeType, @Nullable String title, @Nullable String presentationStyle, Promise promise);
public abstract void viewDocument(String bookmarkOrUri, String permissions, @Nullable String mimeType, @Nullable String title, @Nullable String androidApplicationId, @Nullable String presentationStyle, Promise promise);
}
25 changes: 15 additions & 10 deletions packages/document-viewer/ios/RNDocumentViewer.mm
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
#import "RNDocumentViewer.h"
#import <React/RCTUtils.h>
#import "RNDPreviewController.h"
// for UIModalPresentationStyle conversion
// remove after https://github.com/facebook/react-native/commit/2d547a3252b328251e49dabfeec85f8d46c85411 is released
#import <React/RCTModalHostViewManager.h>
#import <React/RCTConvert.h>

static NSString * const RNDocViewerErrorUnableToOpenFileType = @"UNABLE_TO_OPEN_FILE_TYPE";
static NSString * const RNDocViewerErrorNullPresenter = @"NULL_PRESENTER";

@interface RNDocumentViewer ()
@property(nonatomic, nullable) NSURL *presentedUrl;
Expand All @@ -24,6 +25,7 @@ + (BOOL)requiresMainQueueSetup {
permissions:(NSString *)permissions
mimeType:(NSString *)mimeType
title:(NSString *)title
androidApplicationId:(NSString *)androidApplicationId
presentationStyle:(NSString *)presentationStyle
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
Expand All @@ -34,10 +36,10 @@ + (BOOL)requiresMainQueueSetup {
[self presentPreview:title restoredURL:restoredURL presentationStyle:_presentationStyle resolve:resolve reject:reject];
} else {
NSData *bookmarkData = [[NSData alloc] initWithBase64EncodedString:bookmarkOrUri options:0];

NSError *error = nil;
BOOL isStale = NO;

NSURL *restoredURL = [NSURL URLByResolvingBookmarkData:bookmarkData
options:NSURLBookmarkResolutionWithoutUI
relativeToURL:nil
Expand All @@ -50,7 +52,6 @@ + (BOOL)requiresMainQueueSetup {
reject(@"RNDocViewer", @"Bookmark was resolved but it's stale", nil);
return;
}

if ([restoredURL startAccessingSecurityScopedResource]) {
[self presentPreview:title restoredURL:restoredURL presentationStyle:_presentationStyle resolve:resolve reject:reject];
} else {
Expand All @@ -62,7 +63,7 @@ + (BOOL)requiresMainQueueSetup {
}
}

- (void)presentPreview:(NSString *)title
- (void)presentPreview:(NSString *)title
restoredURL:(NSURL *)restoredURL
presentationStyle:(UIModalPresentationStyle) presentationStyle
resolve:(RCTPromiseResolveBlock)resolve
Expand All @@ -75,12 +76,16 @@ - (void)presentPreview:(NSString *)title
controller.delegate = self;

if ([QLPreviewController canPreviewItem:item]) {
[RCTPresentedViewController() presentViewController:controller animated:YES completion:^{
UIViewController* presenter = RCTPresentedViewController();
if (presenter) {
[presenter presentViewController:controller animated:YES completion:nil];
resolve([NSNull null]);
}];
} else {
reject(RNDocViewerErrorNullPresenter, @"RCTPresentedViewController was nil", nil);
}
} else {
[self.presentedUrl stopAccessingSecurityScopedResource];
reject(@"UNABLE_TO_OPEN_FILE_TYPE", @"unsupported file", nil);
reject(RNDocViewerErrorUnableToOpenFileType, @"unsupported file", nil);
}
});
}
Expand Down
22 changes: 10 additions & 12 deletions packages/document-viewer/src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
export interface NativeModuleError extends Error {
code: string
}

const OPERATION_CANCELED = 'OPERATION_CANCELED'
const IN_PROGRESS = 'ASYNC_OP_IN_PROGRESS'
const UNABLE_TO_OPEN_FILE_TYPE = 'UNABLE_TO_OPEN_FILE_TYPE'
const NULL_PRESENTER = 'NULL_PRESENTER'

export const errorCodes = Object.freeze({
OPERATION_CANCELED,
IN_PROGRESS,
UNABLE_TO_OPEN_FILE_TYPE,
NULL_PRESENTER,
})

type ErrorCodes = (typeof errorCodes)[keyof typeof errorCodes]

export interface NativeModuleError extends Error {
code: ErrorCodes | (string & {})
}

/**
* TypeScript helper to check if an object has the `code` property.
* This is used to avoid `as` casting when you access the `code` property on errors returned by the module.
*/
export const isErrorWithCode = (error: any): error is NativeModuleError => {
// to account for https://github.com/facebook/react-native/issues/41950
const isNewArchErrorIOS = typeof error === 'object' && error != null
return (error instanceof Error || isNewArchErrorIOS) && 'code' in error
export const isErrorWithCode = (error: unknown): error is NativeModuleError => {
return error instanceof Error && 'code' in error
}
16 changes: 11 additions & 5 deletions packages/document-viewer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,28 @@ export type PresentationStyle =
export type BaseOptions = {
/**
* Android only: The type of permission to grant to the receiving app that will open the document.
* This only has effect if you're viewing a file that lives in the app's sandboxed storage.
* This only has an effect if you're viewing a file that lives in the app's sandboxed storage.
*/
grantPermissions?: 'read' | 'write'
/**
* iOS only: The title to display in the header of the document viewer.
* If not provided, the filename will be used.
* @default the file name.
*/
headerTitle?: string
/**
* Optional, but recommended: the mimetype of the document. This will help the Android OS to find the right app(s) to open the document.
* Optional, but strongly recommended: the mimetype of the document. This helps the Android OS to find the right app(s) to open the document.
*/
mimeType?: string

/**
* iOS only - Controls how the picker is presented, e.g. on an iPad you may want to present it fullscreen. Defaults to `pageSheet`.
* iOS only - Controls how the picker is presented, e.g. on an iPad you may want to present it fullscreen.
* @default `pageSheet`.
* */
presentationStyle?: PresentationStyle
/**
* Android only - Optional, only provide a value if `viewDocument` rejects with `IllegalArgumentException`. Represents the unique identifier for an Android application.
* @default application package name, which usually is the same as the application id.
*/
androidApplicationId?: string
}

/**
Expand Down Expand Up @@ -74,6 +79,7 @@ export function viewDocument(data: ViewDocumentOptions): Promise<null> {
data?.grantPermissions ?? 'read',
data?.mimeType,
data?.headerTitle,
data?.androidApplicationId,
data?.presentationStyle,
)
}
Expand Down
1 change: 1 addition & 0 deletions packages/document-viewer/src/spec/NativeDocumentViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface Spec extends TurboModule {
permissions: string,
mimeType?: string,
title?: string,
androidApplicationId?: string,
presentationStyle?: string,
): Promise<null>
}
Expand Down
Loading