From a6bc4939c2176e227f656dde720d1fb79afdba14 Mon Sep 17 00:00:00 2001 From: Corentin <2089620+shajz@users.noreply.github.com> Date: Fri, 30 Apr 2021 17:55:22 +0200 Subject: [PATCH] ADD [Android/iOS] Allow multiple mime types & multiple files share (#2) Co-authored-by: lucca-dev <> --- hooks/androidIntentFilters.js | 76 ++++ hooks/iosAddTarget.js | 11 +- hooks/iosCopyShareExtension.js | 6 +- hooks/iosRemoveTarget.js | 6 +- hooks/npmInstall.js | 30 +- hooks/utils.js | 10 + package.json | 2 + plugin.xml | 32 +- src/ios/OpenWithPlugin.m | 380 ++++++++-------- .../ShareExtension/ShareExtension-Info.plist | 78 ++-- src/ios/ShareExtension/ShareViewController.h | 1 - src/ios/ShareExtension/ShareViewController.m | 406 +++++++++--------- www/openwith.js | 265 ++++++------ 13 files changed, 698 insertions(+), 605 deletions(-) create mode 100644 hooks/androidIntentFilters.js create mode 100644 hooks/utils.js diff --git a/hooks/androidIntentFilters.js b/hooks/androidIntentFilters.js new file mode 100644 index 0000000..bfae795 --- /dev/null +++ b/hooks/androidIntentFilters.js @@ -0,0 +1,76 @@ +const fs = require("fs"); +const path = require("path"); +const et = require("elementtree"); + +// Parses a given file into an elementtree object +function parseElementtreeSync(filename) { + var contents = fs.readFileSync(filename, "utf-8"); + if (contents) { + //Windows is the BOM. Skip the Byte Order Mark. + contents = contents.substring(contents.indexOf("<")); + } + return new et.ElementTree(et.XML(contents)); +} + +function updateMimeTypes(manifest, mimeTypes) { + const tempManifest = parseElementtreeSync(manifest); + const root = tempManifest.getroot(); + + const parent = "application/activity"; + + mimeTypes.forEach((mimeType) => { + const parentEl = root.find(parent); + + const intentFilter = new et.Element("intent-filter"); + intentFilter.append( + new et.Element("data", { "android:mimeType": mimeType }) + ); + intentFilter.append( + new et.Element("action", { + "android:name": "android.intent.action.SEND", + }) + ); + intentFilter.append( + new et.Element("action", { + "android:name": "android.intent.action.SEND_MULTIPLE", + }) + ); + intentFilter.append( + new et.Element("category", { + "android:name": "android.intent.category.DEFAULT", + }) + ); + parentEl.append(intentFilter); + }); + + fs.writeFileSync(manifest, tempManifest.write({ indent: 4 }), "utf-8"); +} + +module.exports = function (context) { + // Prevent double execution + if ( + context.hook == "after_prepare" && + !RegExp("\\s+prepare").test(context.cmdLine) + ) { + return ""; + } + + const packageJson = require(path.join( + context.opts.projectRoot, + "package.json" + )); + + const manifestPath = path.join( + context.opts.projectRoot, + "platforms", + "android", + "app", + "src", + "main", + "AndroidManifest.xml" + ); + const pluginProperties = packageJson.cordova.plugins["cc.fovea.cordova.openwith"]; + + const mimeTypes = pluginProperties["ANDROID_MIME_TYPES"].split(","); + updateMimeTypes(manifestPath, mimeTypes); +}; diff --git a/hooks/iosAddTarget.js b/hooks/iosAddTarget.js index 68cc801..12b81ce 100755 --- a/hooks/iosAddTarget.js +++ b/hooks/iosAddTarget.js @@ -29,7 +29,7 @@ // THE SOFTWARE. // -const PLUGIN_ID = 'cc.fovea.cordova.openwith'; +const { PLUGIN_ID, redError } = require("./utils"); const BUNDLE_SUFFIX = '.shareextension'; var fs = require('fs'); @@ -37,10 +37,6 @@ var path = require('path'); var packageJson; var bundleIdentifier; -function redError(message) { - return new Error('"' + PLUGIN_ID + '" \x1b[1m\x1b[31m' + message + '\x1b[0m'); -} - function replacePreferencesInFile(filePath, preferences) { var content = fs.readFileSync(filePath, 'utf8'); for (var i = 0; i < preferences.length; i++) { @@ -138,7 +134,7 @@ function getCordovaParameter(configXml, variableName) { function getBundleId(context, configXml) { var elementTree = require('elementtree'); var etree = elementTree.parse(configXml); - return etree.getroot().get('id'); + return etree.getroot().get('ios-CFBundleIdentifier'); } function parsePbxProject(context, pbxProjectPath) { @@ -206,9 +202,6 @@ function getPreferences(context, configXml, projectName) { }, { key: '__URL_SCHEME__', value: getCordovaParameter(configXml, 'IOS_URL_SCHEME') - }, { - key: '__UNIFORM_TYPE_IDENTIFIER__', - value: getCordovaParameter(configXml, 'IOS_UNIFORM_TYPE_IDENTIFIER') }]; } diff --git a/hooks/iosCopyShareExtension.js b/hooks/iosCopyShareExtension.js index 4fe160a..155c804 100644 --- a/hooks/iosCopyShareExtension.js +++ b/hooks/iosCopyShareExtension.js @@ -29,13 +29,9 @@ // THE SOFTWARE. // +const { PLUGIN_ID, redError } = require("./utils"); var fs = require('fs'); var path = require('path'); -const PLUGIN_ID = "cc.fovea.cordova.openwith"; - -function redError(message) { - return new Error('"' + PLUGIN_ID + '" \x1b[1m\x1b[31m' + message + '\x1b[0m'); -} function getPreferenceValue (config, name) { var value = config.match(new RegExp('name="' + name + '" value="(.*?)"', "i")); diff --git a/hooks/iosRemoveTarget.js b/hooks/iosRemoveTarget.js index 12d1cf3..2ddce4e 100644 --- a/hooks/iosRemoveTarget.js +++ b/hooks/iosRemoveTarget.js @@ -29,16 +29,12 @@ // THE SOFTWARE. // -const PLUGIN_ID = "cc.fovea.cordova.openwith"; +const { PLUGIN_ID, redError } = require("./utils"); const BUNDLE_SUFFIX = ".shareextension"; var fs = require('fs'); var path = require('path'); -function redError(message) { - return new Error('"' + PLUGIN_ID + '" \x1b[1m\x1b[31m' + message + '\x1b[0m'); -} - // Determine the full path to the app's xcode project file. function findXCodeproject(context, callback) { fs.readdir(iosFolder(context), function(err, data) { diff --git a/hooks/npmInstall.js b/hooks/npmInstall.js index f712ece..a319b85 100644 --- a/hooks/npmInstall.js +++ b/hooks/npmInstall.js @@ -18,22 +18,24 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -const PLUGIN_ID = "cc.fovea.cordova.openwith"; +const { PLUGIN_ID } = require("./utils"); module.exports = function (context) { - var child_process = require('child_process'); - var deferral = require('q').defer(); + var child_process = require("child_process"); + var deferral = require("q").defer(); - console.log('Installing "' + PLUGIN_ID + '" dependencies'); - child_process.exec('npm install --production', {cwd:__dirname}, function (error) { - if (error !== null) { - console.log('exec error: ' + error); - deferral.reject('npm installation failed'); - } - deferral.resolve(); - }); + console.log('Installing "' + PLUGIN_ID + '" dependencies'); + child_process.exec( + "npm install --production", + { cwd: __dirname }, + function (error) { + if (error !== null) { + console.log("exec error: " + error); + deferral.reject("npm installation failed"); + } + deferral.resolve(); + } + ); - return deferral.promise; + return deferral.promise; }; - -// vim: ts=4:sw=4:et diff --git a/hooks/utils.js b/hooks/utils.js new file mode 100644 index 0000000..4c92baa --- /dev/null +++ b/hooks/utils.js @@ -0,0 +1,10 @@ +const PLUGIN_ID = "cc.fovea.cordova.openwith"; + +function redError(message) { + return new Error(`"${PLUGIN_ID}" \x1b[1m\x1b[31m${message}\x1b[0m`); +} + +module.exports = { + PLUGIN_ID, + redError, +}; diff --git a/package.json b/package.json index 8d38604..104e9e2 100644 --- a/package.json +++ b/package.json @@ -72,10 +72,12 @@ "src/ios/ShareExtension/ShareViewController.m", "www/openwith.js", "www/test-openwith.js", + "hooks/androidIntentFilters.js", "hooks/iosAddTarget.js", "hooks/iosRemoveTarget.js", "hooks/iosCopyShareExtension.js", "hooks/npmInstall.js", + "hooks/utils.js", "install-pmd", "plugin.xml", "LICENSE", diff --git a/plugin.xml b/plugin.xml index b6f0a45..2a1d1bf 100644 --- a/plugin.xml +++ b/plugin.xml @@ -24,26 +24,22 @@ SOFTWARE. --> + xmlns:android="http://schemas.android.com/apk/res/android" id="cc.fovea.cordova.openwith" version="2.0.0"> OpenWith - Cordova "Open With" plugin for iOS and Android + Cordova "Open With" plugin for iOS and Android https://github.com/j3k0/cordova-plugin-openwith.git https://github.com/j3k0/cordova-plugin-openwith/issues - MIT + MIT cordova,phonegap,openwith,ios,android - - - + @@ -106,24 +102,11 @@ SOFTWARE. - - - - - - - $ANDROID_EXTRA_ACTIONS - - - - - - - - + + @@ -132,9 +115,10 @@ SOFTWARE. + - diff --git a/src/ios/OpenWithPlugin.m b/src/ios/OpenWithPlugin.m index 289e167..8e164e2 100644 --- a/src/ios/OpenWithPlugin.m +++ b/src/ios/OpenWithPlugin.m @@ -11,38 +11,38 @@ - (NSString*)convertToBase64; @implementation NSData (Base64) - (NSString*)convertToBase64 { - const uint8_t* input = (const uint8_t*)[self bytes]; - NSInteger length = [self length]; - - static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - - NSMutableData* data = [NSMutableData dataWithLength:((length + 2) / 3) * 4]; - uint8_t* output = (uint8_t*)data.mutableBytes; - - NSInteger i; - for (i=0; i < length; i += 3) { - NSInteger value = 0; - NSInteger j; - for (j = i; j < (i + 3); j++) { - value <<= 8; - - if (j < length) { - value |= (0xFF & input[j]); - } - } - - NSInteger theIndex = (i / 3) * 4; - output[theIndex + 0] = table[(value >> 18) & 0x3F]; - output[theIndex + 1] = table[(value >> 12) & 0x3F]; - output[theIndex + 2] = (i + 1) < length ? table[(value >> 6) & 0x3F] : '='; - output[theIndex + 3] = (i + 2) < length ? table[(value >> 0) & 0x3F] : '='; - } - - NSString *ret = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; + const uint8_t* input = (const uint8_t*)[self bytes]; + NSInteger length = [self length]; + + static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + + NSMutableData* data = [NSMutableData dataWithLength:((length + 2) / 3) * 4]; + uint8_t* output = (uint8_t*)data.mutableBytes; + + NSInteger i; + for (i=0; i < length; i += 3) { + NSInteger value = 0; + NSInteger j; + for (j = i; j < (i + 3); j++) { + value <<= 8; + + if (j < length) { + value |= (0xFF & input[j]); + } + } + + NSInteger theIndex = (i / 3) * 4; + output[theIndex + 0] = table[(value >> 18) & 0x3F]; + output[theIndex + 1] = table[(value >> 12) & 0x3F]; + output[theIndex + 2] = (i + 1) < length ? table[(value >> 6) & 0x3F] : '='; + output[theIndex + 3] = (i + 2) < length ? table[(value >> 0) & 0x3F] : '='; + } + + NSString *ret = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; #if ARC_DISABLED - [ret autorelease]; + [ret autorelease]; #endif - return ret; + return ret; } @end @@ -66,11 +66,11 @@ - (NSString*)convertToBase64 { */ @interface OpenWithPlugin : CDVPlugin { - NSString* _loggerCallback; - NSString* _handlerCallback; - NSUserDefaults *_userDefaults; - int _verbosityLevel; - NSString *_backURL; + NSString* _loggerCallback; + NSString* _handlerCallback; + NSUserDefaults *_userDefaults; + int _verbosityLevel; + NSString *_backURL; } @property (nonatomic,retain) NSString* loggerCallback; @@ -105,24 +105,24 @@ @implementation OpenWithPlugin // + (void) load { [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(didFinishLaunching:) - name:UIApplicationDidFinishLaunchingNotification - object:nil]; + selector:@selector(didFinishLaunching:) + name:UIApplicationDidFinishLaunchingNotification + object:nil]; } + (void) didFinishLaunching:(NSNotification*)notification { - launchOptions = notification.userInfo; + launchOptions = notification.userInfo; } - (void) log:(int)level message:(NSString*)message { - if (level >= self.verbosityLevel) { - NSLog(@"[OpenWithPlugin.m]%@", message); - if (self.loggerCallback != nil) { - CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:message]; - pluginResult.keepCallback = [NSNumber numberWithBool:YES]; - [self.commandDelegate sendPluginResult:pluginResult callbackId:self.loggerCallback]; - } - } + if (level >= self.verbosityLevel) { + NSLog(@"[OpenWithPlugin.m]%@", message); + if (self.loggerCallback != nil) { + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:message]; + pluginResult.keepCallback = [NSNumber numberWithBool:YES]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:self.loggerCallback]; + } + } } - (void) debug:(NSString*)message { [self log:VERBOSITY_DEBUG message:message]; } - (void) info:(NSString*)message { [self log:VERBOSITY_INFO message:message]; } @@ -130,176 +130,188 @@ - (void) warn:(NSString*)message { [self log:VERBOSITY_WARN message:message]; } - (void) error:(NSString*)message { [self log:VERBOSITY_ERROR message:message]; } - (void) pluginInitialize { - // You can listen to more app notifications, see: - // http://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UIApplication_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40006728-CH3-DontLinkElementID_4 - - // NOTE: if you want to use these, make sure you uncomment the corresponding notification handler - - // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onPause) name:UIApplicationDidEnterBackgroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onResume) name:UIApplicationWillEnterForegroundNotification object:nil]; - // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onOrientationWillChange) name:UIApplicationWillChangeStatusBarOrientationNotification object:nil]; - // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onOrientationDidChange) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; - - // Added in 2.5.0 - // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pageDidLoad:) name:CDVPageDidLoadNotification object:self.webView]; - //Added in 4.3.0 - // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewWillAppear:) name:CDVViewWillAppearNotification object:nil]; - // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewDidAppear:) name:CDVViewDidAppearNotification object:nil]; - // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewWillDisappear:) name:CDVViewWillDisappearNotification object:nil]; - // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewDidDisappear:) name:CDVViewDidDisappearNotification object:nil]; - // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewWillLayoutSubviews:) name:CDVViewWillLayoutSubviewsNotification object:nil]; - // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewDidLayoutSubviews:) name:CDVViewDidLayoutSubviewsNotification object:nil]; - // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewWillTransitionToSize:) name:CDVViewWillTransitionToSizeNotification object:nil]; - [self onReset]; - [self info:@"[pluginInitialize] OK"]; + // You can listen to more app notifications, see: + // http://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UIApplication_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40006728-CH3-DontLinkElementID_4 + + // NOTE: if you want to use these, make sure you uncomment the corresponding notification handler + + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onPause) name:UIApplicationDidEnterBackgroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onResume) name:UIApplicationWillEnterForegroundNotification object:nil]; + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onOrientationWillChange) name:UIApplicationWillChangeStatusBarOrientationNotification object:nil]; + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onOrientationDidChange) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; + + // Added in 2.5.0 + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pageDidLoad:) name:CDVPageDidLoadNotification object:self.webView]; + //Added in 4.3.0 + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewWillAppear:) name:CDVViewWillAppearNotification object:nil]; + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewDidAppear:) name:CDVViewDidAppearNotification object:nil]; + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewWillDisappear:) name:CDVViewWillDisappearNotification object:nil]; + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewDidDisappear:) name:CDVViewDidDisappearNotification object:nil]; + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewWillLayoutSubviews:) name:CDVViewWillLayoutSubviewsNotification object:nil]; + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewDidLayoutSubviews:) name:CDVViewDidLayoutSubviewsNotification object:nil]; + // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewWillTransitionToSize:) name:CDVViewWillTransitionToSizeNotification object:nil]; + [self onReset]; + [self info:@"[pluginInitialize] OK"]; } - (void) onReset { - [self info:@"[onReset]"]; - self.userDefaults = [[NSUserDefaults alloc] initWithSuiteName:SHAREEXT_GROUP_IDENTIFIER]; - self.verbosityLevel = VERBOSITY_INFO; - self.loggerCallback = nil; - self.handlerCallback = nil; + [self info:@"[onReset]"]; + self.userDefaults = [[NSUserDefaults alloc] initWithSuiteName:SHAREEXT_GROUP_IDENTIFIER]; + self.verbosityLevel = VERBOSITY_INFO; + self.loggerCallback = nil; + self.handlerCallback = nil; } - (void) onResume { - [self debug:@"[onResume]"]; - [self checkForFileToShare]; + [self debug:@"[onResume]"]; + [self checkForFileToShare]; } - (void) setVerbosity:(CDVInvokedUrlCommand*)command { - NSNumber *value = [command argumentAtIndex:0 - withDefault:[NSNumber numberWithInt: VERBOSITY_INFO] - andClass:[NSNumber class]]; - self.verbosityLevel = value.integerValue; - [self.userDefaults setInteger:self.verbosityLevel forKey:@"verbosityLevel"]; - [self.userDefaults synchronize]; - [self debug:[NSString stringWithFormat:@"[setVerbosity] %d", self.verbosityLevel]]; - CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; - [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + NSNumber *value = [command argumentAtIndex:0 + withDefault:[NSNumber numberWithInt: VERBOSITY_INFO] + andClass:[NSNumber class]]; + self.verbosityLevel = value.integerValue; + [self.userDefaults setInteger:self.verbosityLevel forKey:@"verbosityLevel"]; + [self.userDefaults synchronize]; + [self debug:[NSString stringWithFormat:@"[setVerbosity] %d", self.verbosityLevel]]; + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } - (void) setLogger:(CDVInvokedUrlCommand*)command { - self.loggerCallback = command.callbackId; - [self debug:[NSString stringWithFormat:@"[setLogger] %@", self.loggerCallback]]; - CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_NO_RESULT]; - pluginResult.keepCallback = [NSNumber numberWithBool:YES]; - [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + self.loggerCallback = command.callbackId; + [self debug:[NSString stringWithFormat:@"[setLogger] %@", self.loggerCallback]]; + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_NO_RESULT]; + pluginResult.keepCallback = [NSNumber numberWithBool:YES]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } - (void) setHandler:(CDVInvokedUrlCommand*)command { - self.handlerCallback = command.callbackId; - [self debug:[NSString stringWithFormat:@"[setHandler] %@", self.handlerCallback]]; - CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_NO_RESULT]; - pluginResult.keepCallback = [NSNumber numberWithBool:YES]; - [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + self.handlerCallback = command.callbackId; + [self debug:[NSString stringWithFormat:@"[setHandler] %@", self.handlerCallback]]; + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_NO_RESULT]; + pluginResult.keepCallback = [NSNumber numberWithBool:YES]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } - (NSString *)mimeTypeFromUti: (NSString*)uti { - if (uti == nil) { - return nil; - } - CFStringRef cret = UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)uti, kUTTagClassMIMEType); - NSString *ret = (__bridge_transfer NSString *)cret; - return ret == nil ? uti : ret; + if (uti == nil) { + return nil; + } + CFStringRef cret = UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)uti, kUTTagClassMIMEType); + NSString *ret = (__bridge_transfer NSString *)cret; + return ret == nil ? uti : ret; +} + +- (NSDictionary*) mapToCordova: (NSDictionary*)object { + // Extract sharing data, make sure that it is valid + if (![object isKindOfClass:[NSDictionary class]]) { + [self debug:@"[checkForFileToShare] Data object is invalid"]; + return nil; + } + NSDictionary *dict = (NSDictionary*)object; + NSData *data = dict[@"data"]; + NSString *text = dict[@"text"]; + NSString *name = dict[@"name"]; + NSString *url = dict[@"url"]; + NSString *path = dict[@"path"]; + self.backURL = dict[@"backURL"]; + NSString *type = [self mimeTypeFromUti:dict[@"uti"]]; + if (![data isKindOfClass:NSData.class] || ![text isKindOfClass:NSString.class]) { + [self debug:@"[checkForFileToShare] Data content is invalid"]; + return nil; + } + NSArray *utis = dict[@"utis"]; + if (utis == nil) { + utis = @[]; + } + + // TODO: add the backURL to the shared intent, put it aside in the plugin + // TODO: implement cordova.openwith.exit(intent), will check if backURL is set + + // Send to javascript + [self debug:[NSString stringWithFormat: + @"[checkForFileToShare] Sharing text \"%@\" and a %d bytes file", + text, data.length]]; + + NSString *uri = [NSString stringWithFormat: @"shareextension://index=0,name=%@,type=%@", + name, type]; + return @{ + @"text" : text, + @"base64": [data convertToBase64], + @"type": type, + @"utis": utis, + @"uri": uri, + @"name": name, + @"url": url, + @"path": path + }; } - (void) checkForFileToShare { - [self debug:@"[checkForFileToShare]"]; - if (self.handlerCallback == nil) { - [self debug:@"[checkForFileToShare] javascript not ready yet."]; - return; - } - - [self.userDefaults synchronize]; - NSObject *object = [self.userDefaults objectForKey:@"items"]; - if (object == nil) { - [self debug:@"[checkForFileToShare] Nothing to share"]; - return; - } - - // Clean-up the object, assume it's been handled from now, prevent double processing - [self.userDefaults removeObjectForKey:@"items"]; - - // Extract sharing data, make sure that it is valid - if (![object isKindOfClass:[NSDictionary class]]) { - [self debug:@"[checkForFileToShare] Data object is invalid"]; - return; - } - NSDictionary *dict = (NSDictionary*)object; - NSData *data = dict[@"data"]; - NSString *text = dict[@"text"]; - NSString *name = dict[@"name"]; - NSString *url = dict[@"url"]; - NSString *path = dict[@"path"]; - self.backURL = dict[@"backURL"]; - NSString *type = [self mimeTypeFromUti:dict[@"uti"]]; - if (![data isKindOfClass:NSData.class] || ![text isKindOfClass:NSString.class]) { - [self debug:@"[checkForFileToShare] Data content is invalid"]; - return; - } - NSArray *utis = dict[@"utis"]; - if (utis == nil) { - utis = @[]; - } - - // TODO: add the backURL to the shared intent, put it aside in the plugin - // TODO: implement cordova.openwith.exit(intent), will check if backURL is set - - // Send to javascript - [self debug:[NSString stringWithFormat: - @"[checkForFileToShare] Sharing text \"%@\" and a %d bytes file", - text, data.length]]; - - NSString *uri = [NSString stringWithFormat: @"shareextension://index=0,name=%@,type=%@", - name, type]; - CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:@{ - @"action": @"SEND", - @"exit": @YES, - @"items": @[@{ - @"text" : text, - @"base64": [data convertToBase64], - @"type": type, - @"utis": utis, - @"uri": uri, - @"name": name, - @"url": url, - @"path": path - }] - }]; - pluginResult.keepCallback = [NSNumber numberWithBool:YES]; - [self.commandDelegate sendPluginResult:pluginResult callbackId:self.handlerCallback]; + [self debug:@"[checkForFileToShare]"]; + if (self.handlerCallback == nil) { + [self debug:@"[checkForFileToShare] javascript not ready yet."]; + return; + } + + [self.userDefaults synchronize]; + NSArray *object = [self.userDefaults objectForKey:@"items"]; + if (object == nil) { + [self debug:@"[checkForFileToShare] Nothing to share"]; + return; + } + + NSMutableArray* items = [[NSMutableArray alloc] init]; + for (NSDictionary* item in object) { + NSDictionary* cordovaItem = [self mapToCordova:item]; + if (cordovaItem != nil) { + [items addObject:cordovaItem]; + } + } + + // Clean-up the object, assume it's been handled from now, prevent double processing + [self.userDefaults removeObjectForKey:@"items"]; + + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:@{ + @"action": @"SEND", + @"exit": @NO, + @"items": items + }]; + pluginResult.keepCallback = [NSNumber numberWithBool:YES]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:self.handlerCallback]; } // Initialize the plugin - (void) init:(CDVInvokedUrlCommand*)command { - [self debug:@"[init]"]; - CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; - [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - [self checkForFileToShare]; + [self debug:@"[init]"]; + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + [self checkForFileToShare]; } // Load data from URL - (void) load:(CDVInvokedUrlCommand*)command { - [self debug:@"[load]"]; - // Base64 data already loaded, so this shouldn't happen - // the function is defined just to prevent crashes from unexpected client behaviours. - CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Load, it shouldn't have been!"]; - [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + [self debug:@"[load]"]; + // Base64 data already loaded, so this shouldn't happen + // the function is defined just to prevent crashes from unexpected client behaviours. + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Load, it shouldn't have been!"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } // Exit after sharing - (void) exit:(CDVInvokedUrlCommand*)command { - [self debug:[NSString stringWithFormat:@"[exit] %@", self.backURL]]; - if (self.backURL != nil) { - UIApplication *app = [UIApplication sharedApplication]; - NSURL *url = [NSURL URLWithString:self.backURL]; - if ([app canOpenURL:url]) { - [app openURL:url]; - } - } - CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; - [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + [self debug:[NSString stringWithFormat:@"[exit] %@", self.backURL]]; + if (self.backURL != nil) { + UIApplication *app = [UIApplication sharedApplication]; + NSURL *url = [NSURL URLWithString:self.backURL]; + if ([app canOpenURL:url]) { + [app openURL:url]; + } + } + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } @end diff --git a/src/ios/ShareExtension/ShareExtension-Info.plist b/src/ios/ShareExtension/ShareExtension-Info.plist index 2a1e0ea..0b795e3 100644 --- a/src/ios/ShareExtension/ShareExtension-Info.plist +++ b/src/ios/ShareExtension/ShareExtension-Info.plist @@ -1,45 +1,49 @@ - - CFBundleDevelopmentRegion - en - CFBundleDisplayName - __DISPLAY_NAME__ - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - __BUNDLE_IDENTIFIER__ - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - XPC! - CFBundleShortVersionString - __BUNDLE_SHORT_VERSION_STRING__ - CFBundleVersion - __BUNDLE_VERSION__ - NSExtension - NSExtensionAttributes + CFBundleDevelopmentRegion + en + CFBundleDisplayName + __DISPLAY_NAME__ + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + __BUNDLE_IDENTIFIER__ + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + XPC! + CFBundleShortVersionString + __BUNDLE_SHORT_VERSION_STRING__ + CFBundleVersion + __BUNDLE_VERSION__ + NSExtension - NSExtensionActivationRule - SUBQUERY ( - extensionItems, - $extensionItem, - SUBQUERY ( - $extensionItem.attachments, - $attachment, - ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "__UNIFORM_TYPE_IDENTIFIER__" - ).@count == $extensionItem.attachments.@count - ).@count = 1 - + NSExtensionAttributes + + NSExtensionActivationDictionaryVersion + 2 + NSExtensionActivationRule + + NSExtensionActivationSupportsAttachmentsWithMaxCount + 10 + NSExtensionActivationSupportsAttachmentsWithMinCount + 1 + NSExtensionActivationSupportsFileWithMaxCount + 10 + NSExtensionActivationSupportsImageWithMaxCount + 10 + + NSExtensionActivationUsesStrictMatching + 1 + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.share-services - NSExtensionMainStoryboard - MainInterface - NSExtensionPointIdentifier - com.apple.share-services - diff --git a/src/ios/ShareExtension/ShareViewController.h b/src/ios/ShareExtension/ShareViewController.h index f57416e..2051dad 100644 --- a/src/ios/ShareExtension/ShareViewController.h +++ b/src/ios/ShareExtension/ShareViewController.h @@ -27,4 +27,3 @@ #define SHAREEXT_GROUP_IDENTIFIER @"__GROUP_IDENTIFIER__" #define SHAREEXT_URL_SCHEME @"__URL_SCHEME__" -#define SHAREEXT_UNIFORM_TYPE_IDENTIFIER @"__UNIFORM_TYPE_IDENTIFIER__" diff --git a/src/ios/ShareExtension/ShareViewController.m b/src/ios/ShareExtension/ShareViewController.m index ff21bf8..f6e059d 100644 --- a/src/ios/ShareExtension/ShareViewController.m +++ b/src/ios/ShareExtension/ShareViewController.m @@ -32,9 +32,9 @@ #import "ShareViewController.h" @interface ShareViewController : SLComposeServiceViewController { - int _verbosityLevel; - NSUserDefaults *_userDefaults; - NSString *_backURL; + int _verbosityLevel; + NSUserDefaults *_userDefaults; + NSString *_backURL; } @property (nonatomic) int verbosityLevel; @property (nonatomic,retain) NSUserDefaults *userDefaults; @@ -57,9 +57,9 @@ @implementation ShareViewController @synthesize backURL = _backURL; - (void) log:(int)level message:(NSString*)message { - if (level >= self.verbosityLevel) { - NSLog(@"[ShareViewController.m]%@", message); - } + if (level >= self.verbosityLevel) { + NSLog(@"[ShareViewController.m]%@", message); + } } - (void) debug:(NSString*)message { [self log:VERBOSITY_DEBUG message:message]; } - (void) info:(NSString*)message { [self log:VERBOSITY_INFO message:message]; } @@ -67,221 +67,233 @@ - (void) warn:(NSString*)message { [self log:VERBOSITY_WARN message:message]; } - (void) error:(NSString*)message { [self log:VERBOSITY_ERROR message:message]; } - (void) setup { - self.userDefaults = [[NSUserDefaults alloc] initWithSuiteName:SHAREEXT_GROUP_IDENTIFIER]; - self.verbosityLevel = [self.userDefaults integerForKey:@"verbosityLevel"]; - [self debug:@"[setup]"]; + self.userDefaults = [[NSUserDefaults alloc] initWithSuiteName:SHAREEXT_GROUP_IDENTIFIER]; + self.verbosityLevel = [self.userDefaults integerForKey:@"verbosityLevel"]; + [self debug:@"[setup]"]; } - (BOOL) isContentValid { - return YES; + return YES; } - (void) openURL:(nonnull NSURL *)url { - SEL selector = NSSelectorFromString(@"openURL:options:completionHandler:"); - - UIResponder* responder = self; - while ((responder = [responder nextResponder]) != nil) { - NSLog(@"responder = %@", responder); - if([responder respondsToSelector:selector] == true) { - NSMethodSignature *methodSignature = [responder methodSignatureForSelector:selector]; - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; - - // Arguments - void (^completion)(BOOL success) = ^void(BOOL success) { - NSLog(@"Completions block: %i", success); - }; - if (@available(iOS 13.0, *)) { - UISceneOpenExternalURLOptions * options = [[UISceneOpenExternalURLOptions alloc] init]; - options.universalLinksOnly = false; - - [invocation setTarget: responder]; - [invocation setSelector: selector]; - [invocation setArgument: &url atIndex: 2]; - [invocation setArgument: &options atIndex:3]; - [invocation setArgument: &completion atIndex: 4]; - [invocation invoke]; - break; - } else { - NSDictionary *options = [NSDictionary dictionary]; - - [invocation setTarget: responder]; - [invocation setSelector: selector]; - [invocation setArgument: &url atIndex: 2]; - [invocation setArgument: &options atIndex:3]; - [invocation setArgument: &completion atIndex: 4]; - [invocation invoke]; - break; - } - } - } + SEL selector = NSSelectorFromString(@"openURL:options:completionHandler:"); + + UIResponder* responder = self; + while ((responder = [responder nextResponder]) != nil) { + NSLog(@"responder = %@", responder); + if([responder respondsToSelector:selector] == true) { + NSMethodSignature *methodSignature = [responder methodSignatureForSelector:selector]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; + + // Arguments + void (^completion)(BOOL success) = ^void(BOOL success) { + NSLog(@"Completions block: %i", success); + }; + if (@available(iOS 13.0, *)) { + UISceneOpenExternalURLOptions * options = [[UISceneOpenExternalURLOptions alloc] init]; + options.universalLinksOnly = false; + + [invocation setTarget: responder]; + [invocation setSelector: selector]; + [invocation setArgument: &url atIndex: 2]; + [invocation setArgument: &options atIndex:3]; + [invocation setArgument: &completion atIndex: 4]; + [invocation invoke]; + break; + } else { + NSDictionary *options = [NSDictionary dictionary]; + + [invocation setTarget: responder]; + [invocation setSelector: selector]; + [invocation setArgument: &url atIndex: 2]; + [invocation setArgument: &options atIndex:3]; + [invocation setArgument: &completion atIndex: 4]; + [invocation invoke]; + break; + } + } + } } -- (void)viewWillAppear:(BOOL)animated { - if( [(NSString*)self.contentText length] == 0 ) { - [super viewWillAppear:animated]; - } - else { - [self.view endEditing:YES]; - [self didSelectPost]; - } +- (void) viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + self.view.hidden = YES; + [self didSelectPost]; +} + +- (void) sendItemForIdentifier:(NSString*) utiToLoad itemProvider:(NSItemProvider*) itemProvider items:(NSMutableArray*) items totalCount:(NSUInteger) totalCount { + [self debug:[NSString stringWithFormat:@"item provider = %@", itemProvider]]; + + if ([itemProvider.registeredTypeIdentifiers count] > 0) { + utiToLoad = itemProvider.registeredTypeIdentifiers[0]; + } + + [itemProvider loadItemForTypeIdentifier:utiToLoad options:nil completionHandler: ^(id item, NSError *error) { + + NSString *webURL = @""; + NSString *fileName = @""; + NSString *filePath = @""; + NSData *data = [[NSData alloc] init]; + if([(NSObject*)item isKindOfClass:[NSURL class]]) { + if ([[(NSURL*)item scheme] isEqualToString:@"http"] || [[(NSURL*)item scheme] isEqualToString:@"https"]) { + webURL = [(NSURL*)item absoluteString]; + } + else { + data = [NSData dataWithContentsOfURL:(NSURL*)item]; + fileName = [(NSURL*)item lastPathComponent]; + filePath = [(NSURL*)item absoluteString]; + } + } + else if([(NSObject*)item isKindOfClass:[NSData class]]) { + data = [NSData dataWithData:(NSData *)item]; + } + if([(NSObject*)item isKindOfClass:[UIImage class]]) { + data = UIImagePNGRepresentation((UIImage*)item); + } + + NSString *suggestedName = fileName; + if ([itemProvider respondsToSelector:NSSelectorFromString(@"getSuggestedName")]) { + suggestedName = [itemProvider valueForKey:@"suggestedName"]; + } + + NSString *uti = @""; + NSArray *utis = [NSArray new]; + if ([itemProvider.registeredTypeIdentifiers count] > 0) { + uti = itemProvider.registeredTypeIdentifiers[0]; + utis = itemProvider.registeredTypeIdentifiers; + } + else { + uti = utiToLoad; + } + NSDictionary *dict = @{ + @"text": @"", + @"backURL": self.backURL, + @"data" : data, + @"uti": uti, + @"utis": utis, + @"name": suggestedName, + @"url": webURL, + @"path": filePath + }; + [items addObject:dict]; + + if (totalCount == [items count]) { + [self sendItems:items]; + } + }]; } - (void) didSelectPost { - [self setup]; - [self debug:@"[didSelectPost]"]; - - // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments. - for (NSItemProvider* itemProvider in ((NSExtensionItem*)self.extensionContext.inputItems[0]).attachments) { - - if ([itemProvider hasItemConformingToTypeIdentifier:SHAREEXT_UNIFORM_TYPE_IDENTIFIER]) { - [self debug:[NSString stringWithFormat:@"item provider = %@", itemProvider]]; - - NSString *utiToLoad = SHAREEXT_UNIFORM_TYPE_IDENTIFIER; - if ([itemProvider.registeredTypeIdentifiers count] > 0) { - utiToLoad = itemProvider.registeredTypeIdentifiers[0]; - } - - [itemProvider loadItemForTypeIdentifier:utiToLoad options:nil completionHandler: ^(id item, NSError *error) { - - NSString *contentText = self.contentText; - NSString *webURL = @""; - NSString *fileName = @""; - NSString *filePath = @""; - NSData *data = [[NSData alloc] init]; - if([(NSObject*)item isKindOfClass:[NSURL class]]) { - if ([[(NSURL*)item scheme] isEqualToString:@"http"] || [[(NSURL*)item scheme] isEqualToString:@"https"]) { - webURL = [(NSURL*)item absoluteString]; - } - else { - data = [NSData dataWithContentsOfURL:(NSURL*)item]; - fileName = [(NSURL*)item lastPathComponent]; - filePath = [(NSURL*)item absoluteString]; - } - } - else if([(NSObject*)item isKindOfClass:[NSData class]]) { - data = [NSData dataWithData:(NSData *)item]; - } - else if([(NSObject*)item isKindOfClass:[NSString class]]) { - contentText = (NSString*)item; - } - if([(NSObject*)item isKindOfClass:[UIImage class]]) { - data = UIImagePNGRepresentation((UIImage*)item); - } - - NSString *suggestedName = fileName; - if ([itemProvider respondsToSelector:NSSelectorFromString(@"getSuggestedName")]) { - suggestedName = [itemProvider valueForKey:@"suggestedName"]; - } - - NSString *uti = @""; - NSArray *utis = [NSArray new]; - if ([itemProvider.registeredTypeIdentifiers count] > 0) { - uti = itemProvider.registeredTypeIdentifiers[0]; - utis = itemProvider.registeredTypeIdentifiers; - } - else { - uti = SHAREEXT_UNIFORM_TYPE_IDENTIFIER; - } - NSDictionary *dict = @{ - @"text": contentText, - @"backURL": self.backURL, - @"data" : data, - @"uti": uti, - @"utis": utis, - @"name": suggestedName, - @"url": webURL, - @"path": filePath - }; - [self.userDefaults setObject:dict forKey:@"items"]; - [self.userDefaults synchronize]; - - // Emit a URL that opens the cordova app - NSString *url = [NSString stringWithFormat:@"%@://items", SHAREEXT_URL_SCHEME]; - - // Not allowed: - // [[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]]; - - // Crashes: - // [self.extensionContext openURL:[NSURL URLWithString:url] completionHandler:nil]; - - // From https://stackoverflow.com/a/25750229/2343390 - // Reported not to work since iOS 8.3 - // NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:url]]; - // [self.webView loadRequest:request]; - - [self openURL:[NSURL URLWithString:url]]; - - // Inform the host that we're done, so it un-blocks its UI. - [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil]; - }]; - - return; - } - } - - // Inform the host that we're done, so it un-blocks its UI. - [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil]; + [self setup]; + [self debug:@"[didSelectPost]"]; + + // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments. + NSMutableArray* items = [[NSMutableArray alloc] init]; + NSArray* attachments = ((NSExtensionItem*)self.extensionContext.inputItems[0]).attachments; + NSMutableArray* itemProviders = [[NSMutableArray alloc] init]; + + // filter specific types + for (NSItemProvider* itemProvider in attachments) { + if ([itemProvider hasItemConformingToTypeIdentifier:@"public.image"] || [itemProvider hasItemConformingToTypeIdentifier:@"com.adobe.pdf"]) { + [itemProviders addObject:itemProvider]; + } + } + + for (NSItemProvider* itemProvider in itemProviders) { + if ([itemProvider hasItemConformingToTypeIdentifier:@"public.image"]) { + [self sendItemForIdentifier:@"public.image" itemProvider:itemProvider items:items totalCount:[itemProviders count]]; + } else if ([itemProvider hasItemConformingToTypeIdentifier:@"com.adobe.pdf"]) { + [self sendItemForIdentifier:@"com.adobe.pdf" itemProvider:itemProvider items:items totalCount:[itemProviders count]]; + } + } +} + +- (void) sendItems:(NSArray*) items { + [self.userDefaults setObject:items forKey:@"items"]; + [self.userDefaults synchronize]; + + // Emit a URL that opens the cordova app + NSString *url = [NSString stringWithFormat:@"%@://items", SHAREEXT_URL_SCHEME]; + + // Not allowed: + // [[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]]; + + // Crashes: + // [self.extensionContext openURL:[NSURL URLWithString:url] completionHandler:nil]; + + // From https://stackoverflow.com/a/25750229/2343390 + // Reported not to work since iOS 8.3 + // NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:url]]; + // [self.webView loadRequest:request]; + + [self openURL:[NSURL URLWithString:url]]; + + // Inform the host that we're done, so it un-blocks its UI. + [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil]; } - (NSArray*) configurationItems { - // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here. - return @[]; + // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here. + return @[]; } - (NSString*) backURLFromBundleID: (NSString*)bundleId { - if (bundleId == nil) return nil; - // App Store - com.apple.AppStore - if ([bundleId isEqualToString:@"com.apple.AppStore"]) return @"itms-apps://"; - // Calculator - com.apple.calculator - // Calendar - com.apple.mobilecal - // Camera - com.apple.camera - // Clock - com.apple.mobiletimer - // Compass - com.apple.compass - // Contacts - com.apple.MobileAddressBook - // FaceTime - com.apple.facetime - // Find Friends - com.apple.mobileme.fmf1 - // Find iPhone - com.apple.mobileme.fmip1 - // Game Center - com.apple.gamecenter - // Health - com.apple.Health - // iBooks - com.apple.iBooks - // iTunes Store - com.apple.MobileStore - // Mail - com.apple.mobilemail - message:// - if ([bundleId isEqualToString:@"com.apple.mobilemail"]) return @"message://"; - // Maps - com.apple.Maps - maps:// - if ([bundleId isEqualToString:@"com.apple.Maps"]) return @"maps://"; - // Messages - com.apple.MobileSMS - // Music - com.apple.Music - // News - com.apple.news - applenews:// - if ([bundleId isEqualToString:@"com.apple.news"]) return @"applenews://"; - // Notes - com.apple.mobilenotes - mobilenotes:// - if ([bundleId isEqualToString:@"com.apple.mobilenotes"]) return @"mobilenotes://"; - // Phone - com.apple.mobilephone - // Photos - com.apple.mobileslideshow - if ([bundleId isEqualToString:@"com.apple.mobileslideshow"]) return @"photos-redirect://"; - // Podcasts - com.apple.podcasts - // Reminders - com.apple.reminders - x-apple-reminder:// - if ([bundleId isEqualToString:@"com.apple.reminders"]) return @"x-apple-reminder://"; - // Safari - com.apple.mobilesafari - // Settings - com.apple.Preferences - // Stocks - com.apple.stocks - // Tips - com.apple.tips - // Videos - com.apple.videos - videos:// - if ([bundleId isEqualToString:@"com.apple.videos"]) return @"videos://"; - // Voice Memos - com.apple.VoiceMemos - voicememos:// - if ([bundleId isEqualToString:@"com.apple.VoiceMemos"]) return @"voicememos://"; - // Wallet - com.apple.Passbook - // Watch - com.apple.Bridge - // Weather - com.apple.weather - return @""; + if (bundleId == nil) return nil; + // App Store - com.apple.AppStore + if ([bundleId isEqualToString:@"com.apple.AppStore"]) return @"itms-apps://"; + // Calculator - com.apple.calculator + // Calendar - com.apple.mobilecal + // Camera - com.apple.camera + // Clock - com.apple.mobiletimer + // Compass - com.apple.compass + // Contacts - com.apple.MobileAddressBook + // FaceTime - com.apple.facetime + // Find Friends - com.apple.mobileme.fmf1 + // Find iPhone - com.apple.mobileme.fmip1 + // Game Center - com.apple.gamecenter + // Health - com.apple.Health + // iBooks - com.apple.iBooks + // iTunes Store - com.apple.MobileStore + // Mail - com.apple.mobilemail - message:// + if ([bundleId isEqualToString:@"com.apple.mobilemail"]) return @"message://"; + // Maps - com.apple.Maps - maps:// + if ([bundleId isEqualToString:@"com.apple.Maps"]) return @"maps://"; + // Messages - com.apple.MobileSMS + // Music - com.apple.Music + // News - com.apple.news - applenews:// + if ([bundleId isEqualToString:@"com.apple.news"]) return @"applenews://"; + // Notes - com.apple.mobilenotes - mobilenotes:// + if ([bundleId isEqualToString:@"com.apple.mobilenotes"]) return @"mobilenotes://"; + // Phone - com.apple.mobilephone + // Photos - com.apple.mobileslideshow + if ([bundleId isEqualToString:@"com.apple.mobileslideshow"]) return @"photos-redirect://"; + // Podcasts - com.apple.podcasts + // Reminders - com.apple.reminders - x-apple-reminder:// + if ([bundleId isEqualToString:@"com.apple.reminders"]) return @"x-apple-reminder://"; + // Safari - com.apple.mobilesafari + // Settings - com.apple.Preferences + // Stocks - com.apple.stocks + // Tips - com.apple.tips + // Videos - com.apple.videos - videos:// + if ([bundleId isEqualToString:@"com.apple.videos"]) return @"videos://"; + // Voice Memos - com.apple.VoiceMemos - voicememos:// + if ([bundleId isEqualToString:@"com.apple.VoiceMemos"]) return @"voicememos://"; + // Wallet - com.apple.Passbook + // Watch - com.apple.Bridge + // Weather - com.apple.weather + return @""; } // This is called at the point where the Post dialog is about to be shown. // We use it to store the _hostBundleID - (void) willMoveToParentViewController: (UIViewController*)parent { - NSString *hostBundleID = [parent valueForKey:(@"_hostBundleID")]; - self.backURL = [self backURLFromBundleID:hostBundleID]; + NSString *hostBundleID = [parent valueForKey:(@"_hostBundleID")]; + self.backURL = [self backURLFromBundleID:hostBundleID]; } @end + + diff --git a/www/openwith.js b/www/openwith.js index a1c7cd1..db26ea3 100644 --- a/www/openwith.js +++ b/www/openwith.js @@ -1,230 +1,237 @@ -function initOpenwithPlugin (root) { - 'use strict' +function initOpenwithPlugin(root) { + "use strict"; // imports // var cordova = require('cordova') - var PLUGIN_NAME = 'OpenWithPlugin' + let PLUGIN_NAME = "OpenWithPlugin"; // the returned object - var openwith = {} + let openwith = {}; // // exported constants // // logging levels - var DEBUG = openwith.DEBUG = 0 - var INFO = openwith.INFO = 10 - var WARN = openwith.WARN = 20 - var ERROR = openwith.ERROR = 30 + let DEBUG = (openwith.DEBUG = 0); + let INFO = (openwith.INFO = 10); + let WARN = (openwith.WARN = 20); + let ERROR = (openwith.ERROR = 30); // actions - openwith.SEND = 'SEND' - openwith.VIEW = 'VIEW' + openwith.SEND = "SEND"; + openwith.VIEW = "VIEW"; // // state variables // // default verbosity level is to show errors only - var verbosity + let verbosity; // list of registered handlers - var handlers + let handlers; // list of intents sent to this app // // it's never cleaned up, so that newly registered handlers (especially those registered a bit too late) // will still receive the list of intents. - var intents + let intents; // the logger function (defaults to console.log) - var logger + let logger; // the cordova object (defaults to global one) - var cordova + let cordova; // has init() been called or not already - var initCalled + let initCalled; // make sure a number is displayed with 2 digits - var twoDigits = function (n) { - return n < 10 - ? '0' + n - : '' + n - } + let twoDigits = function (n) { + return n < 10 ? `0${n}` : `${n}`; + }; // format a date for display - var formatDate = function (now) { - var date = now ? new Date(now) : new Date() - var d = [date.getMonth() + 1, date.getDate()].map(twoDigits) - var t = [date.getHours(), date.getMinutes(), date.getSeconds()].map(twoDigits) - return d.join('-') + ' ' + t.join(':') - } + let formatDate = function (now) { + let date = now ? new Date(now) : new Date(); + let d = [date.getMonth() + 1, date.getDate()].map(twoDigits); + let t = [date.getHours(), date.getMinutes(), date.getSeconds()].map( + twoDigits + ); + return `${d.join("-")} ${t.join(":")}`; + }; // format verbosity level for display - var formatVerbosity = function (level) { - if (level <= DEBUG) return 'D' - if (level <= INFO) return 'I' - if (level <= WARN) return 'W' - return 'E' - } + let formatVerbosity = function (level) { + if (level <= DEBUG) return "D"; + if (level <= INFO) return "I"; + if (level <= WARN) return "W"; + return "E"; + }; // display a log in the console only if the level is higher than current verbosity - var log = function (level, message) { + let log = function (level, message) { if (level >= verbosity) { - logger(formatDate() + ' ' + formatVerbosity(level) + ' openwith: ' + message) + logger(`${formatDate()} ${formatVerbosity(level)} openwith: ${message}`); } - } + }; // reset the state to default openwith.reset = function () { - log(DEBUG, 'reset') - verbosity = openwith.INFO - handlers = [] - intents = [] - logger = console.log - cordova = root.cordova - initCalled = false - } + log(DEBUG, "reset"); + verbosity = openwith.INFO; + handlers = []; + intents = []; + logger = console.log; + cordova = root.cordova; + initCalled = false; + }; // perform the initial reset - openwith.reset() + openwith.reset(); // change the logger function openwith.setLogger = function (value) { - logger = value - } + logger = value; + }; // change the cordova object (mostly for testing) openwith.setCordova = function (value) { - cordova = value - } + cordova = value; + }; // change the verbosity level openwith.setVerbosity = function (value) { - log(DEBUG, 'setVerbosity()') - if (value !== DEBUG && value !== INFO && value !== WARN && value !== ERROR) { - throw new Error('invalid verbosity level') + log(DEBUG, "setVerbosity()"); + if ( + value !== DEBUG && + value !== INFO && + value !== WARN && + value !== ERROR + ) { + throw new Error("invalid verbosity level"); } - verbosity = value - cordova.exec(null, null, PLUGIN_NAME, 'setVerbosity', [value]) - } + verbosity = value; + cordova.exec(null, null, PLUGIN_NAME, "setVerbosity", [value]); + }; // retrieve the verbosity level openwith.getVerbosity = function () { - log(DEBUG, 'getVerbosity()') - return verbosity - } + log(DEBUG, "getVerbosity()"); + return verbosity; + }; // a simple function to test that the plugin is correctly installed openwith.about = function () { - log(DEBUG, 'about()') - return 'cordova-plugin-openwith, (c) 2017 fovea.cc' - } + log(DEBUG, "about()"); + return "cordova-plugin-openwith, (c) 2017 fovea.cc"; + }; - var findHandler = function (callback) { - for (var i = 0; i < handlers.length; ++i) { + let findHandler = function (callback) { + for (let i = 0; i < handlers.length; ++i) { if (handlers[i] === callback) { - return i + return i; } } - return -1 - } + return -1; + }; // registers a intent handler openwith.addHandler = function (callback) { - log(DEBUG, 'addHandler()') - if (typeof callback !== 'function') { - throw new Error('invalid handler function') + log(DEBUG, "addHandler()"); + if (typeof callback !== "function") { + throw new Error("invalid handler function"); } if (findHandler(callback) >= 0) { - throw new Error('handler already defined') + throw new Error("handler already defined"); } - handlers.push(callback) - intents.forEach(function handleIntent (intent) { - callback(intent) - }) - } + handlers.push(callback); + intents.forEach((intent) => { + callback(intent); + }); + }; openwith.numHandlers = function () { - log(DEBUG, 'numHandler()') - return handlers.length - } + log(DEBUG, "numHandler()"); + return handlers.length; + }; openwith.load = function (dataDescriptor, successCallback, errorCallback) { - var loadSuccess = function (base64) { - dataDescriptor.base64 = base64 + let loadSuccess = function (base64) { + dataDescriptor.base64 = base64; if (successCallback) { - successCallback(base64, dataDescriptor) + successCallback(base64, dataDescriptor); } - } - var loadError = function (err) { + }; + let loadError = function (err) { if (errorCallback) { - errorCallback(err, dataDescriptor) + errorCallback(err, dataDescriptor); } - } + }; if (dataDescriptor.base64) { - loadSuccess(dataDescriptor.base64) + loadSuccess(dataDescriptor.base64); } else { - cordova.exec(loadSuccess, loadError, PLUGIN_NAME, 'load', [dataDescriptor]) + cordova.exec(loadSuccess, loadError, PLUGIN_NAME, "load", [ + dataDescriptor, + ]); } - } + }; - openwith.exit = function () { - log(DEBUG, 'exit()') - cordova.exec(null, null, PLUGIN_NAME, 'exit', []) - } + openwith.exit = () => { + log(DEBUG, "exit()"); + cordova.exec(null, null, PLUGIN_NAME, "exit", []); + }; - var onNewIntent = function (intent) { - log(DEBUG, 'onNewIntent(' + intent.action + ')') + let onNewIntent = function (intent) { + log(DEBUG, `onNewIntent(${intent.action})`); // process the new intent - handlers.forEach(function (handler) { - handler(intent) - }) - intents.push(intent) - } + handlers.forEach((handler) => { + handler(intent); + }); + intents.push(intent); + }; // Initialize the native side at startup openwith.init = function (successCallback, errorCallback) { - log(DEBUG, 'init()') + log(DEBUG, "init()"); if (initCalled) { - throw new Error('init should only be called once') + throw new Error("init should only be called once"); } - initCalled = true + initCalled = true; // callbacks have to be functions - if (successCallback && typeof successCallback !== 'function') { - throw new Error('invalid success callback') + if (successCallback && typeof successCallback !== "function") { + throw new Error("invalid success callback"); } - if (errorCallback && typeof errorCallback !== 'function') { - throw new Error('invalid error callback') - } - - var initSuccess = function () { - log(DEBUG, 'initSuccess()') - if (successCallback) successCallback() + if (errorCallback && typeof errorCallback !== "function") { + throw new Error("invalid error callback"); } - var initError = function () { - log(DEBUG, 'initError()') - if (errorCallback) errorCallback() - } - var nativeLogger = function (data) { - var split = data.split(':') - log(+split[0], '[native] ' + split.slice(1).join(':')) - } - - cordova.exec(nativeLogger, null, PLUGIN_NAME, 'setLogger', []) - cordova.exec(onNewIntent, null, PLUGIN_NAME, 'setHandler', []) - cordova.exec(initSuccess, initError, PLUGIN_NAME, 'init', []) - } - return openwith + let initSuccess = function () { + log(DEBUG, "initSuccess()"); + if (successCallback) successCallback(); + }; + let initError = function () { + log(DEBUG, "initError()"); + if (errorCallback) errorCallback(); + }; + let nativeLogger = function (data) { + let split = data.split(":"); + log(+split[0], `[native] ${split.slice(1).join(":")}`); + }; + + cordova.exec(nativeLogger, null, PLUGIN_NAME, "setLogger", []); + cordova.exec(onNewIntent, null, PLUGIN_NAME, "setHandler", []); + cordova.exec(initSuccess, initError, PLUGIN_NAME, "init", []); + }; + + return openwith; } // Export the plugin object -var openwith = initOpenwithPlugin(this) -module.exports = openwith -this.plugins = this.plugins || {} -this.plugins.openwith = openwith +let openwith = initOpenwithPlugin(this); +module.exports = openwith; +this.plugins = this.plugins || {}; +this.plugins.openwith = openwith;