Skip to content
Open
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
289 changes: 224 additions & 65 deletions ios/ReactNativeShareExtension.m
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
#import "ReactNativeShareExtension.h"
#import "React/RCTRootView.h"

#define ITEM_IDENTIFIER @"public.url"
#define IMAGE_IDENTIFIER @"public.image"

NSExtensionContext* extensionContext;

@implementation ReactNativeShareExtension {
Expand Down Expand Up @@ -34,74 +31,236 @@ - (void)viewDidLoad {
}


RCT_EXPORT_METHOD(close) {
[extensionContext completeRequestReturningItems:nil
completionHandler:nil];
RCT_EXPORT_METHOD(close:(NSString *)appGroupId) {
[self cleanUpTempFiles:appGroupId];
[extensionContext completeRequestReturningItems:nil
completionHandler:nil];
}



RCT_REMAP_METHOD(data,
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
appGroupId: (NSString *)appGroupId
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
[self extractDataFromContext: extensionContext withCallback:^(NSURL* url,NSString* contentType ,NSException* err) {
NSDictionary *inventory = @{
@"type": contentType,
@"value": [url absoluteString]
};

resolve(inventory);
}];
[self extractDataFromContext: extensionContext withAppGroup:appGroupId andCallback:^(NSArray* items ,NSError* err) {
if (items == nil) {
resolve(nil);
return;
}
resolve(items[0]);
}];
}

- (void)extractDataFromContext:(NSExtensionContext *)context withCallback:(void(^)(NSURL *url, NSString* contentType ,NSException *exception))callback {
@try {
NSExtensionItem *item = [context.inputItems firstObject];
NSArray *attachments = item.attachments;
__block NSItemProvider *urlProvider = nil;
__block NSItemProvider *imageProvider = nil;
[attachments enumerateObjectsUsingBlock:^(NSItemProvider *provider, NSUInteger idx, BOOL *stop) {
if([provider hasItemConformingToTypeIdentifier:ITEM_IDENTIFIER]) {
urlProvider = provider;
*stop = YES;
}else if ([provider hasItemConformingToTypeIdentifier:IMAGE_IDENTIFIER]){
imageProvider = provider;
*stop = YES;

}
}];

if(urlProvider) {
[urlProvider loadItemForTypeIdentifier:ITEM_IDENTIFIER options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
NSURL *url = (NSURL *)item;

if(callback) {
callback(url,@"text/plain" ,nil);
}
}];
}else if (imageProvider){
[imageProvider loadItemForTypeIdentifier:IMAGE_IDENTIFIER options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
NSURL *url = (NSURL *)item;

if(callback) {
callback(url,[[[url absoluteString] pathExtension] lowercaseString] ,nil);
}
}];


}
else {
if(callback) {
callback(nil, nil,[NSException exceptionWithName:@"Error" reason:@"couldn't find provider" userInfo:nil]);
}
}
}
@catch (NSException *exception) {
if(callback) {
callback(nil,nil ,exception);
}
}
RCT_REMAP_METHOD(dataMulti,
appGroupId: (NSString *)appGroupId
resolverMulti:(RCTPromiseResolveBlock)resolve
rejecterMulti:(RCTPromiseRejectBlock)reject)
{
[self extractDataFromContext: extensionContext withAppGroup: appGroupId andCallback:^(NSArray* items ,NSError* err) {
if (err) {
reject(@"dataMulti", @"Failed to extract attachment content", err);
return;
}
resolve(items);
}];
}

typedef void (^ProviderCallback)(NSString *content, NSString *contentType, BOOL owner, NSError *err);

- (void)extractDataFromContext:(NSExtensionContext *)context withAppGroup:(NSString *) appGroupId andCallback:(void(^)(NSArray *items ,NSError *err))callback {
@try {
NSExtensionItem *item = [context.inputItems firstObject];
NSArray *attachments = item.attachments;
NSMutableArray *items = [[NSMutableArray alloc] init];

__block int attachmentIdx = 0;
__block ProviderCallback providerCb = nil;
__block __weak ProviderCallback weakProviderCb = nil;
providerCb = ^ void (NSString *content, NSString *contentType, BOOL owner, NSError *err) {
if (err) {
callback(nil, err);
return;
}

if (content != nil) {
[items addObject:@{
@"type": contentType,
@"value": content,
@"owner": [NSNumber numberWithBool:owner],
}];
}

++attachmentIdx;
if (attachmentIdx == [attachments count]) {
callback(items, nil);
} else {
[self extractDataFromProvider:attachments[attachmentIdx] withAppGroup:appGroupId andCallback: weakProviderCb];
}
};
weakProviderCb = providerCb;
[self extractDataFromProvider:attachments[0] withAppGroup:appGroupId andCallback: providerCb];
}
@catch (NSException *exc) {
NSError *error = [NSError errorWithDomain:@"fiftythree.paste" code:1 userInfo:@{
@"reason": [exc description]
}];
callback(nil, error);
}
}

- (void)extractDataFromProvider:(NSItemProvider *)provider withAppGroup:(NSString *) appGroupId andCallback:(void(^)(NSString* content, NSString* contentType, BOOL owner, NSError *err))callback {

if([provider hasItemConformingToTypeIdentifier:@"public.image"]) {
[provider loadItemForTypeIdentifier:@"public.image" options:nil completionHandler:^(id<NSSecureCoding, NSObject> item, NSError *error) {
if (error) {
callback(nil, nil, NO, error);
return;
}

@try {
if ([item isKindOfClass: NSURL.class]) {
NSURL *url = (NSURL *)item;
return callback([url absoluteString], @"public.image", NO, nil);
} else if ([item isKindOfClass: UIImage.class]) {
UIImage *image = (UIImage *)item;
NSString *fileName = [NSString stringWithFormat:@"%@.jpg", [[NSUUID UUID] UUIDString]];
NSURL *tempContainerURL = [ReactNativeShareExtension tempContainerURL:appGroupId];
if (tempContainerURL == nil){
return callback(nil, nil, NO, nil);
}

NSURL *tempFileURL = [tempContainerURL URLByAppendingPathComponent: fileName];
BOOL created = [UIImageJPEGRepresentation(image, 0.95) writeToFile:[tempFileURL path] atomically:YES];
if (created) {
return callback([tempFileURL absoluteString], @"public.image", YES, nil);
} else {
return callback(nil, nil, NO, nil);
}
} else if ([item isKindOfClass: NSData.class]) {
NSString *fileName = [NSString stringWithFormat:@"%@.jpg", [[NSUUID UUID] UUIDString]];
NSData *data = (NSData *)item;
UIImage *image = [UIImage imageWithData:data];
NSURL *tempContainerURL = [ReactNativeShareExtension tempContainerURL:appGroupId];
if (tempContainerURL == nil){
return callback(nil, nil, NO, nil);
}
NSURL *tempFileURL = [tempContainerURL URLByAppendingPathComponent: fileName];
BOOL created = [UIImageJPEGRepresentation(image, 0.95) writeToFile:[tempFileURL path] atomically:YES];
if (created) {
return callback([tempFileURL absoluteString], @"public.image", YES, nil);
} else {
return callback(nil, nil, NO, nil);
}
} else {
// Do nothing, some type we don't support.
return callback(nil, nil, NO, nil);
}
}
@catch(NSException *exc) {
NSError *error = [NSError errorWithDomain:@"fiftythree.paste" code:2 userInfo:@{
@"reason": [exc description]
}];
callback(nil, nil, NO, error);
}
}];
return;
}

if([provider hasItemConformingToTypeIdentifier:@"public.file-url"]) {
[provider loadItemForTypeIdentifier:@"public.file-url" options:nil completionHandler:^(id<NSSecureCoding, NSObject> item, NSError *error) {
if (error) {
callback(nil, nil, NO, error);
return;
}

if ([item isKindOfClass:NSURL.class]) {
return callback([(NSURL *)item absoluteString], @"public.file-url", NO, nil);
} else if ([item isKindOfClass:NSString.class]) {
return callback((NSString *)item, @"public.file-url", NO, nil);
}
callback(nil, nil, NO, nil);
}];
return;
}

if([provider hasItemConformingToTypeIdentifier:@"public.url"]) {
[provider loadItemForTypeIdentifier:@"public.url" options:nil completionHandler:^(id<NSSecureCoding, NSObject> item, NSError *error) {
if (error) {
callback(nil, nil, NO, error);
return;
}

if ([item isKindOfClass:NSURL.class]) {
return callback([(NSURL *)item absoluteString], @"public.url", NO, nil);
} else if ([item isKindOfClass:NSString.class]) {
return callback((NSString *)item, @"public.url", NO, nil);
}
}];
return;
}

if([provider hasItemConformingToTypeIdentifier:@"public.plain-text"]) {
[provider loadItemForTypeIdentifier:@"public.plain-text" options:nil completionHandler:^(id<NSSecureCoding, NSObject> item, NSError *error) {
if (error) {
callback(nil, nil, NO, error);
return;
}

if ([item isKindOfClass:NSString.class]) {
return callback((NSString *)item, @"public.plain-text", NO, nil);
} else if ([item isKindOfClass:NSAttributedString.class]) {
NSAttributedString *str = (NSAttributedString *)item;
return callback([str string], @"public.plain-text", NO, nil);
} else if ([item isKindOfClass:NSData.class]) {
NSString *str = [[NSString alloc] initWithData:(NSData *)item encoding:NSUTF8StringEncoding];
if (str) {
return callback(str, @"public.plain-text", NO, nil);
} else {
return callback(nil, nil, NO, nil);
}
} else {
return callback(nil, nil, NO, nil);
}
}];
return;
}

callback(nil, nil, NO, nil);
}

+ (NSURL*) tempContainerURL: (NSString*)appGroupId {
NSFileManager *manager = [NSFileManager defaultManager];
NSURL *containerURL = [manager containerURLForSecurityApplicationGroupIdentifier: appGroupId];
NSURL *tempDirectoryURL = [containerURL URLByAppendingPathComponent:@"shareTempItems"];
if (![manager fileExistsAtPath:[tempDirectoryURL path]]) {
NSError *err;
[manager createDirectoryAtURL:tempDirectoryURL withIntermediateDirectories:YES attributes:nil error:&err];
if (err) {
return nil;
}
}

return tempDirectoryURL;
}

- (void) cleanUpTempFiles:(NSString *)appGroupId {
NSURL *tmpDirectoryURL = [ReactNativeShareExtension tempContainerURL:appGroupId];
if (tmpDirectoryURL == nil) {
return;
}

NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *tmpFiles = [fileManager contentsOfDirectoryAtPath:[tmpDirectoryURL path] error:&error];
if (error) {
return;
}

for (NSString *file in tmpFiles)
{
error = nil;
[fileManager removeItemAtPath:[[tmpDirectoryURL URLByAppendingPathComponent:file] path] error:&error];
}
}

@end
5 changes: 3 additions & 2 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { NativeModules } from 'react-native'
// const { type, value } = await NativeModules.ShareExtension.data()
// NativeModules.ShareExtension.close()
export default {
data: () => NativeModules.ReactNativeShareExtension.data(),
close: () => NativeModules.ReactNativeShareExtension.close()
data: (appGroupId) => NativeModules.ReactNativeShareExtension.data(appGroupId),
dataMulti: (appGroupId) => NativeModules.ReactNativeShareExtension.dataMulti(appGroupId),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a separate method or can it be supported in the existing data method.

close: (appGroupId) => NativeModules.ReactNativeShareExtension.close(appGroupId)
}
Loading