diff --git a/README.md b/README.md index 0a6594b7..d8f4e252 100644 --- a/README.md +++ b/README.md @@ -226,6 +226,18 @@ iOS Quirks: ``` +##### Experimental feature: sharing directly to someone +Available in 5.0.8 and up - please let me know if this works for your device! Open an issue if not.. + +```html + +``` +For `receiver` on iOS pass in the Addressbook ID (or 'abid'). You can find those abid's by using the [Cordova Contacts Plugin](https://github.com/apache/cordova-plugin-contacts). +The result in the success callback of the `find` function is a JSON array of contact objects, use the 'id' you find in those objects. +Don't pass in an image on iOS because that can't be sent to someone directly unfortunately. Message and URL are fine though. + +On Android pass in the phone number of the person you want to send a message to (untested at the moment). + ####SMS Note that on Android, SMS via Hangouts may not behave correctly ```html @@ -381,7 +393,7 @@ Here's the list of available activities you can disable : ## 4b. Usage on Windows Phone -The available methods on WP8 are: `available`, `canShareViaEmail`, `share` and `shareViaEmail`. +The available methods on WP8 are: `available`, `canShareViaEmail`, `share`, `shareViaEmail` and `shareViaSMS`. Currently the first two always return true, but this may change in the future in case I can find a way to truly detect the availability. The `share` function on WP8 supports two flavours: message only, or a combination of message, title and link. diff --git a/package.json b/package.json index 5f21e1d9..b34769e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-x-socialsharing", - "version": "5.0.6", + "version": "5.0.10", "description": "Share text, images (and other files), or a link via the native sharing widget of your device. Android is fully supported, as well as iOS 6 and up. WP8 has somewhat limited support.", "cordova": { "id": "cordova-plugin-x-socialsharing", @@ -29,7 +29,7 @@ "ecosystem:cordova", "cordova-ios", "cordova-android", - "cordova-wp8" + "cordova-windows" ], "engines": [ { diff --git a/plugin.xml b/plugin.xml index 9a66d554..3b9c7289 100755 --- a/plugin.xml +++ b/plugin.xml @@ -1,8 +1,8 @@ + id="cordova-plugin-x-socialsharing" + version="5.0.10"> SocialSharing @@ -71,6 +71,7 @@ + diff --git a/src/android/nl/xservices/plugins/SocialSharing.java b/src/android/nl/xservices/plugins/SocialSharing.java index 08e9724b..521c546c 100644 --- a/src/android/nl/xservices/plugins/SocialSharing.java +++ b/src/android/nl/xservices/plugins/SocialSharing.java @@ -47,7 +47,9 @@ public class SocialSharing extends CordovaPlugin { private static final String ACTION_SHARE_VIA_SMS_EVENT = "shareViaSMS"; private static final String ACTION_SHARE_VIA_EMAIL_EVENT = "shareViaEmail"; + private static final int ACTIVITY_CODE_SEND = 1; private static final int ACTIVITY_CODE_SENDVIAEMAIL = 2; + private static final int ACTIVITY_CODE_SENDVIAWHATSAPP = 3; private CallbackContext _callbackContext; @@ -77,7 +79,11 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo this.pasteMessage = args.getString(4); return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "com.facebook.katana", false); } else if (ACTION_SHARE_VIA_WHATSAPP_EVENT.equals(action)) { - return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "whatsapp", false); + if (notEmpty(args.getString(4))) { + return shareViaWhatsAppDirectly(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), args.getString(4)); + } else { + return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "whatsapp", false); + } } else if (ACTION_SHARE_VIA_INSTAGRAM_EVENT.equals(action)) { if (notEmpty(args.getString(0))) { copyHintToClipboard(args.getString(0), "Instagram paste message"); @@ -222,6 +228,7 @@ public void run() { if (notEmpty(subject)) { sendIntent.putExtra(Intent.EXTRA_SUBJECT, subject); } + // add the URL to the message, as there seems to be no separate field if (notEmpty(url)) { if (notEmpty(message)) { @@ -275,7 +282,7 @@ public void run() { if (peek) { callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK)); } else { - mycordova.startActivityForResult(plugin, Intent.createChooser(sendIntent, null), 1); + mycordova.startActivityForResult(plugin, Intent.createChooser(sendIntent, null), ACTIVITY_CODE_SEND); } } } @@ -319,6 +326,10 @@ private Uri getFileUriAndSetType(Intent sendIntent, String dir, String image, St Matcher matcher = dispositionPattern.matcher(disposition); if (matcher.find()) { filename = matcher.group(1).replaceAll("[^a-zA-Z0-9._-]", ""); + if (filename.length() == 0) { + // in this case we can't determine a filetype so some targets (gmail) may not render it correctly + filename = "file"; + } localImage = "file://" + dir + "/" + filename; } } @@ -350,12 +361,80 @@ private Uri getFileUriAndSetType(Intent sendIntent, String dir, String image, St } saveFile(Base64.decode(encodedImg, Base64.DEFAULT), dir, fileName); localImage = "file://" + dir + "/" + fileName; + } else if (image.startsWith("df:")) { + // safeguard for https://code.google.com/p/android/issues/detail?id=7901#c43 + if (!image.contains(";base64,")) { + sendIntent.setType("text/plain"); + return null; + } + // format looks like this : df:filename.txt;data:image/png;base64,R0lGODlhDAA... + final String fileName = image.substring(image.indexOf("df:") + 3, image.indexOf(";data:")); + final String fileType = image.substring(image.indexOf(";data:") + 6, image.indexOf(";base64,")); + final String encodedImg = image.substring(image.indexOf(";base64,") + 8); + sendIntent.setType(fileType); + saveFile(Base64.decode(encodedImg, Base64.DEFAULT), dir, sanitizeFilename(fileName)); + localImage = "file://" + dir + "/" + fileName; } else if (!image.startsWith("file://")) { throw new IllegalArgumentException("URL_NOT_SUPPORTED"); } return Uri.parse(localImage); } + private boolean shareViaWhatsAppDirectly(final CallbackContext callbackContext, String message, final String subject, final JSONArray files, final String url, final String number) { + // add the URL to the message, as there seems to be no separate field + if (notEmpty(url)) { + if (notEmpty(message)) { + message += " " + url; + } else { + message = url; + } + } + final String shareMessage = message; + final SocialSharing plugin = this; + cordova.getThreadPool().execute(new SocialSharingRunnable(callbackContext) { + public void run() { + Intent intent = new Intent(Intent.ACTION_SENDTO); + intent.setData(Uri.parse("smsto:" + number)); + + intent.putExtra("sms_body", shareMessage); + intent.putExtra("sms_subject", subject); + intent.setPackage("com.whatsapp"); + + try { + if (files.length() > 0 && !"".equals(files.getString(0))) { + final boolean hasMultipleAttachments = files.length() > 1; + final String dir = getDownloadDir(); + if (dir != null) { + ArrayList fileUris = new ArrayList(); + Uri fileUri = null; + for (int i = 0; i < files.length(); i++) { + fileUri = getFileUriAndSetType(intent, dir, files.getString(i), subject, i); + if (fileUri != null) { + fileUris.add(fileUri); + } + } + if (!fileUris.isEmpty()) { + if (hasMultipleAttachments) { + intent.putExtra(Intent.EXTRA_STREAM, fileUris); + } else { + intent.putExtra(Intent.EXTRA_STREAM, fileUri); + } + } + } + } + } catch (Exception e) { + callbackContext.error(e.getMessage()); + } + try { + cordova.startActivityForResult(plugin, intent, ACTIVITY_CODE_SENDVIAWHATSAPP); + } catch (Exception e) { + callbackContext.error(e.getMessage()); + } + } + }); + return true; + } + private boolean invokeSMSIntent(final CallbackContext callbackContext, JSONObject options, String p_phonenumbers) { final String message = options.optString("message"); // TODO test this on a real SMS enabled device before releasing it @@ -458,13 +537,16 @@ private void createOrCleanDir(final String downloadDir) throws IOException { } private static String getFileName(String url) { + if (url.endsWith("/")) { + url = url.substring(0, url.length()-1); + } final String pattern = ".*/([^?#]+)?"; Pattern r = Pattern.compile(pattern); Matcher m = r.matcher(url); if (m.find()) { return m.group(1); } else { - return null; + return "file"; } } diff --git a/src/ios/SocialSharing.h b/src/ios/SocialSharing.h index b51474d3..399cd239 100755 --- a/src/ios/SocialSharing.h +++ b/src/ios/SocialSharing.h @@ -4,7 +4,7 @@ @interface SocialSharing : CDVPlugin @property (nonatomic, strong) MFMailComposeViewController *globalMailComposer; -@property (retain) UIDocumentInteractionController * documentInteractionController; +@property (nonatomic, strong) UIDocumentInteractionController * documentInteractionController; @property (retain) NSString * tempStoredFile; @property (retain) CDVInvokedUrlCommand * command; diff --git a/src/ios/SocialSharing.m b/src/ios/SocialSharing.m index cd0913a4..6e6fe49e 100755 --- a/src/ios/SocialSharing.m +++ b/src/ios/SocialSharing.m @@ -57,15 +57,15 @@ - (void)share:(CDVInvokedUrlCommand*)command { [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; return; } - + NSString *message = [command.arguments objectAtIndex:0]; NSString *subject = [command.arguments objectAtIndex:1]; NSArray *filenames = [command.arguments objectAtIndex:2]; NSString *urlString = [command.arguments objectAtIndex:3]; - + NSMutableArray *activityItems = [[NSMutableArray alloc] init]; [activityItems addObject:message]; - + NSMutableArray *files = [[NSMutableArray alloc] init]; if (filenames != (id)[NSNull null] && filenames.count > 0) { for (NSString* filename in filenames) { @@ -79,18 +79,18 @@ - (void)share:(CDVInvokedUrlCommand*)command { } [activityItems addObjectsFromArray:files]; } - + if (urlString != (id)[NSNull null]) { - [activityItems addObject:[NSURL URLWithString:urlString]]; + [activityItems addObject:[NSURL URLWithString:[urlString stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]]]; } - + UIActivity *activity = [[UIActivity alloc] init]; NSArray *applicationActivities = [[NSArray alloc] initWithObjects:activity, nil]; UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:applicationActivities]; if (subject != (id)[NSNull null]) { [activityVC setValue:subject forKey:@"subject"]; } - + // TODO deprecated in iOS 8.0, change this some day [activityVC setCompletionHandler:^(NSString *activityType, BOOL completed) { [self cleanupStoredFiles]; @@ -98,12 +98,12 @@ - (void)share:(CDVInvokedUrlCommand*)command { CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:completed]; [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; }]; - + NSArray * socialSharingExcludeActivities = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"SocialSharingExcludeActivities"]; if (socialSharingExcludeActivities!=nil && [socialSharingExcludeActivities count] > 0) { activityVC.excludedActivityTypes = socialSharingExcludeActivities; } - + dispatch_async(dispatch_get_main_queue(), ^(void){ // iPad on iOS >= 8 needs a different approach if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) { @@ -230,27 +230,27 @@ - (bool)isAvailableForSharing:(CDVInvokedUrlCommand*)command - (void)shareViaInternal:(CDVInvokedUrlCommand*)command type:(NSString *) type { - + NSString *message = [command.arguments objectAtIndex:0]; // subject is not supported by the SLComposeViewController NSArray *filenames = [command.arguments objectAtIndex:2]; NSString *urlString = [command.arguments objectAtIndex:3]; - + // boldly invoke the target app, because the phone will display a nice message asking to configure the app SLComposeViewController *composeViewController = [SLComposeViewController composeViewControllerForServiceType:type]; if (message != (id)[NSNull null]) { [composeViewController setInitialText:message]; } - + for (NSString* filename in filenames) { UIImage* image = [self getImage:filename]; if (image != nil) { [composeViewController addImage:image]; } } - + if (urlString != (id)[NSNull null]) { - [composeViewController addURL:[NSURL URLWithString:urlString]]; + [composeViewController addURL:[NSURL URLWithString:[urlString stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]]]; } [composeViewController setCompletionHandler:^(SLComposeViewControllerResult result) { @@ -272,7 +272,7 @@ - (void)shareViaInternal:(CDVInvokedUrlCommand*)command - (void)shareViaEmail:(CDVInvokedUrlCommand*)command { if ([self isEmailAvailable]) { - + if (TARGET_IPHONE_SIMULATOR && IsAtLeastiOSVersion(@"8.0")) { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"SocialSharing plugin" message:@"Sharing via email is not supported on the iOS 8 simulator." @@ -282,38 +282,38 @@ - (void)shareViaEmail:(CDVInvokedUrlCommand*)command { [alert show]; return; } - + self.globalMailComposer.mailComposeDelegate = self; - + if ([command.arguments objectAtIndex:0] != (id)[NSNull null]) { NSString *message = [command.arguments objectAtIndex:0]; BOOL isHTML = [message rangeOfString:@"<[^>]+>" options:NSRegularExpressionSearch].location != NSNotFound; [self.globalMailComposer setMessageBody:message isHTML:isHTML]; } - + if ([command.arguments objectAtIndex:1] != (id)[NSNull null]) { [self.globalMailComposer setSubject: [command.arguments objectAtIndex:1]]; } - + if ([command.arguments objectAtIndex:2] != (id)[NSNull null]) { [self.globalMailComposer setToRecipients:[command.arguments objectAtIndex:2]]; } - + if ([command.arguments objectAtIndex:3] != (id)[NSNull null]) { [self.globalMailComposer setCcRecipients:[command.arguments objectAtIndex:3]]; } - + if ([command.arguments objectAtIndex:4] != (id)[NSNull null]) { [self.globalMailComposer setBccRecipients:[command.arguments objectAtIndex:4]]; } - + if ([command.arguments objectAtIndex:5] != (id)[NSNull null]) { NSArray* attachments = [command.arguments objectAtIndex:5]; NSFileManager* fileManager = [NSFileManager defaultManager]; for (NSString* path in attachments) { NSURL *file = [self getFile:path]; NSData* data = [fileManager contentsAtPath:file.path]; - + NSString* fileName; NSString* mimeType; NSString* basename = [self getBasenameFromAttachmentPath:path]; @@ -331,14 +331,14 @@ - (void)shareViaEmail:(CDVInvokedUrlCommand*)command { [self.globalMailComposer addAttachmentData:data mimeType:mimeType fileName:fileName]; } } - + // remember the command, because we need it in the didFinishWithResult method _command = command; [self.commandDelegate runInBackground:^{ [[self getTopMostViewController] presentViewController:self.globalMailComposer animated:YES completion:nil]; }]; - + } else { CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; @@ -406,7 +406,7 @@ - (void)shareViaSMS:(CDVInvokedUrlCommand*)command { NSString *message = [options objectForKey:@"message"]; NSString *subject = [options objectForKey:@"subject"]; NSString *image = [options objectForKey:@"image"]; - + MFMessageComposeViewController *picker = [[MFMessageComposeViewController alloc] init]; picker.messageComposeDelegate = (id) self; if (message != (id)[NSNull null]) { @@ -424,7 +424,7 @@ - (void)shareViaSMS:(CDVInvokedUrlCommand*)command { } } } - + if (phonenumbers != (id)[NSNull null]) { [picker setRecipients:[phonenumbers componentsSeparatedByString:@","]]; } @@ -469,7 +469,7 @@ - (void)openImage:(NSString *)imageName { } - (void)shareViaInstagram:(CDVInvokedUrlCommand*)command { - + // on iOS9 canShareVia('instagram'..) will only work if instagram:// is whitelisted. // If it's not, this method will ask permission to the user on iOS9 for opening the app, // which is of course better than Instagram sharing not working at all because you forgot to whitelist it. @@ -492,7 +492,7 @@ - (void)shareViaInstagram:(CDVInvokedUrlCommand*)command { image = [self getImage:filename]; break; } - + // NSData *imageObj = [NSData dataFromBase64String:objectAtIndex0]; NSString *tmpDir = NSTemporaryDirectory(); NSString *path = [tmpDir stringByAppendingPathComponent:@"instagram.igo"]; @@ -517,7 +517,7 @@ - (void)shareViaInstagram:(CDVInvokedUrlCommand*)command { } - (void)shareViaWhatsApp:(CDVInvokedUrlCommand*)command { - + // on iOS9 canShareVia('whatsapp'..) will only work if whatsapp:// is whitelisted. // If it's not, this method will ask permission to the user on iOS9 for opening the app, // which is of course better than WhatsApp sharing not working at all because you forgot to whitelist it. @@ -534,6 +534,7 @@ - (void)shareViaWhatsApp:(CDVInvokedUrlCommand*)command { // subject is not supported by the SLComposeViewController NSArray *filenames = [command.arguments objectAtIndex:2]; NSString *urlString = [command.arguments objectAtIndex:3]; + NSString *abid = [command.arguments objectAtIndex:4]; // only use the first image (for now.. maybe we can share in a loop?) UIImage* image = nil; @@ -561,14 +562,18 @@ - (void)shareViaWhatsApp:(CDVInvokedUrlCommand*)command { if ([shareString isEqual: @""]) { shareString = urlString; } else { - shareString = [NSString stringWithFormat:@"%@ %@", shareString, urlString]; + shareString = [NSString stringWithFormat:@"%@ %@", shareString, [urlString stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]]; } } NSString * encodedShareString = [shareString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; // also encode the '=' character encodedShareString = [encodedShareString stringByReplacingOccurrencesOfString:@"=" withString:@"%3D"]; encodedShareString = [encodedShareString stringByReplacingOccurrencesOfString:@"&" withString:@"%26"]; - NSString * encodedShareStringForWhatsApp = [NSString stringWithFormat:@"whatsapp://send?text=%@", encodedShareString]; + NSString * abidString = @""; + if (abid != (id)[NSNull null]) { + abidString = [NSString stringWithFormat:@"abid=%@&", abid]; + } + NSString * encodedShareStringForWhatsApp = [NSString stringWithFormat:@"whatsapp://send?%@text=%@", abidString, encodedShareString]; NSURL *whatsappURL = [NSURL URLWithString:encodedShareStringForWhatsApp]; [[UIApplication sharedApplication] openURL: whatsappURL]; diff --git a/src/windows/SocialSharingProxy.js b/src/windows/SocialSharingProxy.js index 99e0af15..ff257d52 100644 --- a/src/windows/SocialSharingProxy.js +++ b/src/windows/SocialSharingProxy.js @@ -10,6 +10,36 @@ module.exports = { var fileOrFileArray = args[2]; //Web link var url = args[3]; + + var folder = Windows.Storage.ApplicationData.current.temporaryFolder; + + var getExtension = function (strBase64) { + return strBase64.substring(strBase64.indexOf("/") + 1, strBase64.indexOf(";base64")); + }; + + var replaceAll = function (str, find, replace) { + return str.replace(new RegExp(find, 'g'), replace); + }; + + var sanitizeFilename = function (name) { + return replaceAll(name, "[:\\\\/*?|<> ]", "_"); + }; + + var getFileName = function (position, fileExtension) { + var fileName = (subject ? sanitizeFilename(subject) : "file") + (position == 0 ? "" : "_" + position) + "." + fileExtension; + return fileName; + }; + + var createTemporalFile = function (fileName, buffer) { + + var filePath = ""; + return folder.createFileAsync(fileName, Windows.Storage.CreationCollisionOption.replaceExisting).then(function (file) { + filePath = file.path; + return Windows.Storage.FileIO.writeBufferAsync(file, buffer); + }).then(function(){ + return Windows.Storage.StorageFile.getFileFromPathAsync(filePath); + }); + }; var doShare = function (e) { e.request.data.properties.title = subject?subject: "Sharing"; @@ -19,25 +49,49 @@ module.exports = { var deferral = e.request.getDeferral(); var storageItems = []; var filesCount = fileOrFileArray.length; + + var completeFile = function () { + if (!--filesCount) { + storageItems.length && e.request.data.setStorageItems(storageItems); + deferral.complete(); + } + }; + for (var i = 0; i < fileOrFileArray.length; i++) { - Windows.Storage.StorageFile.getFileFromPathAsync(fileOrFileArray[i]).done( - function (file) { - storageItems.push(file); - if (!--filesCount) { - e.request.data.setStorageItems(storageItems); - deferral.complete(); - } - }, - function() { - if (!--filesCount) { - e.request.data.setStorageItems(storageItems); - deferral.complete(); + + var file = fileOrFileArray[i]; + if (file.indexOf("data:") >= 0) { + var fileName = getFileName(i, getExtension(file)); + var buffer = Windows.Security.Cryptography.CryptographicBuffer.decodeFromBase64String(file.split(',')[1]); + if (buffer) { + createTemporalFile(fileName, buffer).done( + function (file) { + storageItems.push(file); + completeFile(); + }, + function () { + completeFile(); + } + ); + } + else { + completeFile(); + } + } + else { + Windows.Storage.StorageFile.getFileFromPathAsync(file).done( + function (file) { + storageItems.push(file); + completeFile(); + }, + function () { + completeFile(); } - } - ); + ); + } } } - } + }; var dataTransferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrentView(); @@ -96,7 +150,7 @@ module.exports = { ); } } - } + }; var dataTransferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrentView(); diff --git a/src/wp8/Newtonsoft.Json.dll b/src/wp8/Newtonsoft.Json.dll new file mode 100755 index 00000000..b8194284 Binary files /dev/null and b/src/wp8/Newtonsoft.Json.dll differ diff --git a/src/wp8/SocialSharing.cs b/src/wp8/SocialSharing.cs index 351bac93..1a165127 100644 --- a/src/wp8/SocialSharing.cs +++ b/src/wp8/SocialSharing.cs @@ -4,6 +4,8 @@ using WPCordovaClassLib.Cordova.Commands; using WPCordovaClassLib.Cordova.JSON; +using Newtonsoft.Json; + namespace Cordova.Extension.Commands { public class SocialSharing : BaseCommand @@ -76,5 +78,26 @@ public void shareViaEmail(string jsonArgs) draft.Show(); DispatchCommandResult(new PluginResult(PluginResult.Status.OK, true)); } + + public void shareViaSMS(string jsonArgs) + { + var options = JsonHelper.Deserialize(jsonArgs); + + SmsComposeTask smsComposeTask = new SmsComposeTask(); + + smsComposeTask.To = options[1]; + SMSMessageClass m = JsonConvert.DeserializeObject(options[0]); + smsComposeTask.Body = m.message; + + smsComposeTask.Show(); + + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, true)); + } + } + + public class SMSMessageClass + { + public string message { get; set; } } + } \ No newline at end of file diff --git a/tests/plugin.xml b/tests/plugin.xml index 44bad99a..1ccb2217 100644 --- a/tests/plugin.xml +++ b/tests/plugin.xml @@ -9,5 +9,5 @@ Nicolas Oliver MIT - + \ No newline at end of file diff --git a/tests/test.js b/tests/test.js new file mode 100644 index 00000000..ad1a8229 --- /dev/null +++ b/tests/test.js @@ -0,0 +1,363 @@ +/** + * Jasmine Based test suites + * + * Several of SocialSharing APIs cannot be automatically tested, because + * they depend on user interaction in order to call success or fail + * handlers. For most of them, there is a basic test that assert the presence of + * the API. + * + * There are some cases that test automation can be applied, i.e in "canShareVia", + * "canShareViaEmail" and "available" methods. For those cases, there is some level + * of automatic test coverage. + */ +exports.defineAutoTests = function () { + 'use strict'; + + describe('socialsharing', function () { + it('should be defined', function () { + expect(window.plugins.socialsharing).toBeDefined(); + }); + + describe('share', function () { + it('should be defined', function () { + expect(window.plugins.socialsharing.share).toBeDefined(); + }); + + it('should be a function', function () { + expect(typeof window.plugins.socialsharing.share).toEqual('function'); + }); + }); + + describe('shareVia', function () { + it('should be defined', function () { + expect(window.plugins.socialsharing.shareVia).toBeDefined(); + }); + + it('should be a function', function () { + expect(typeof window.plugins.socialsharing.shareVia).toEqual('function'); + }); + }); + + describe('shareViaTwitter', function () { + it('should be defined', function () { + expect(window.plugins.socialsharing.shareViaTwitter).toBeDefined(); + }); + + it('should be a function', function () { + expect(typeof window.plugins.socialsharing.shareViaTwitter).toEqual('function'); + }); + }); + + describe('shareViaFacebook', function () { + it('should be defined', function () { + expect(window.plugins.socialsharing.shareViaFacebook).toBeDefined(); + }); + + it('should be a function', function () { + expect(typeof window.plugins.socialsharing.shareViaFacebook).toEqual('function'); + }); + }); + + describe('shareViaFacebookWithPasteMessageHint', function () { + it('should be defined', function () { + expect(window.plugins.socialsharing.shareViaFacebookWithPasteMessageHint).toBeDefined(); + }); + + it('should be a function', function () { + expect(typeof window.plugins.socialsharing.shareViaFacebookWithPasteMessageHint).toEqual('function'); + }); + }); + + describe('shareViaWhatsApp', function () { + it('should be defined', function () { + expect(window.plugins.socialsharing.shareViaWhatsApp).toBeDefined(); + }); + + it('should be a function', function () { + expect(typeof window.plugins.socialsharing.shareViaWhatsApp).toEqual('function'); + }); + }); + + describe('shareViaSMS', function () { + it('should be defined', function () { + expect(window.plugins.socialsharing.shareViaSMS).toBeDefined(); + }); + + it('should be a function', function () { + expect(typeof window.plugins.socialsharing.shareViaSMS).toEqual('function'); + }); + }); + + describe('shareViaEmail', function () { + it('should be defined', function () { + expect(window.plugins.socialsharing.shareViaEmail).toBeDefined(); + }); + + it('should be a function', function () { + expect(typeof window.plugins.socialsharing.shareViaEmail).toEqual('function'); + }); + }); + + describe('canShareVia', function () { + it('should be defined', function () { + expect(window.plugins.socialsharing.canShareVia).toBeDefined(); + }); + + it('should be a function', function () { + expect(typeof window.plugins.socialsharing.canShareVia).toEqual('function'); + }); + + it('should always call callback or error function', function (done) { + function onSuccess(data){ + expect(data).not.toEqual(null); + done(); + } + + function onError(error){ + expect(error).not.toEqual(null); + done(); + } + + window.plugins.socialsharing.canShareVia('dummytarget','dummymessage', null, null, null, onSuccess, onError); + }); + }); + + describe('canshareViaEmail', function(){ + it('should be defined', function () { + expect(window.plugins.socialsharing.canShareViaEmail).toBeDefined(); + }); + + it('should be a function', function () { + expect(typeof window.plugins.socialsharing.canShareViaEmail).toEqual('function'); + }); + + it('should always call callback or error function', function (done) { + function onSuccess(data){ + expect(data).not.toEqual(null); + done(); + } + + function onError(error){ + expect(error).not.toEqual(null); + done(); + } + + window.plugins.socialsharing.canShareViaEmail(onSuccess, onError); + }); + }); + + describe('availabe', function(){ + it('should be defined', function () { + expect(window.plugins.socialsharing.available).toBeDefined(); + }); + + it('should be a function', function () { + expect(typeof window.plugins.socialsharing.available).toEqual('function'); + }); + + it('should return a boolean when called', function(done){ + window.plugins.socialsharing.available(function(isAvailable) { + expect(typeof isAvailable).toEqual('boolean'); + done(); + }); + }); + }); + }); +}; + +/** + * Manual tests suites + * + * Some actions buttons to execute SocialSharing plugin methods + */ +exports.defineManualTests = function (contentEl, createActionButton) { + 'use strict'; + + /** helper function to log messages in the log div element */ + function logMessage(message, color) { + var log = document.getElementById('info'), + logLine = document.createElement('div'); + + if (color) { + logLine.style.color = color; + } + + logLine.innerHTML = message; + log.appendChild(logLine); + } + + /** helper function to clear the log div element */ + function clearLog() { + var log = document.getElementById('info'); + log.innerHTML = ''; + } + + /** helper function to declare a not implemented test */ + function testNotImplemented(testName) { + return function () { + console.error(testName, 'test not implemented'); + }; + } + + /** init method called on deviceready event */ + function init() {} + + /** object to hold properties and configs */ + var TestSuite = {}; + + TestSuite.getCanShareViaTarget = function(){ + return document.getElementById('inputCanShareVia').value; + }; + + TestSuite.$markup = '' + + '
' + + 'Available Tests' + + + '

Available

' + + '
' + + 'Expected result: Should log if the plugin is available or not' + + '
' + + + '
' + + 'Share Tests' + + + '

Share Message

' + + '
' + + 'Expected result: Should display share widget, and the message to share should contain "Message body"' + + + '

Share Message with Subject

' + + '
' + + 'Expected result: Should display share widget, and the message to share should contain "Message body", and the subject should be "Message subject"' + + + '

Share Link

' + + '' + + 'Expected result: Should display share widget, and the message to share should contain "http://www.x-services.nl"' + + + '

Share Message with Link

' + + '' + + 'Expected result: Should display share widget, and the message to share should contain "Message body http://www.x-services.nl"' + + + '

Share Image

' + + '
' + + 'Expected result: Should display share widget, and the message to share should contain an image' + + + '

Share Image in base 64

' + + '
' + + 'Expected result: Should display share widget, and the message to share should contain an image. The image is encoded in base 64' + + + '

Share Image with Message

' + + '
' + + 'Expected result: Should display share widget, and the message to share should contain "Message body" and an image' + + + '

Share Image with Message and Link

' + + '' + + 'Expected result: Should display share widget, and the message to share should contain "Message body http://www.x-services.nl" and an image' + + + '

Share Image with Message, Subject and Link

' + + '' + + 'Expected result: Should display share widget, and the message to share should contain "Message body http://www.x-services.nl", "Message subject" as subject, and an image' + + '
' + + + '
' + + 'Can Share Tests' + + + 'Target:
' + + + '

Can Share via

' + + '
' + + 'Expected result: should log OK if can share, or should log a list of available share targets' + + + '

Can Share via Email

' + + '
' + + 'Expected result: should log OK if can share' + + '
' + + ''; + + contentEl.innerHTML = '
' + TestSuite.$markup; + + createActionButton('availabe', function () { + clearLog(); + window.plugins.socialsharing.available(function(isAvailable) { + var message = 'is this plugin available? '; + message += isAvailable? 'Yes' : 'No'; + + logMessage(message, isAvailable? 'green' : 'red'); + }); + }, 'buttonIsAvailable'); + + createActionButton('share message', function () { + window.plugins.socialsharing.share('Message body'); + }, 'buttonShareMessage'); + + createActionButton('share message and subject', function () { + window.plugins.socialsharing.share('Message body', 'Message subject'); + }, 'buttonShareMessageWithSubject'); + + createActionButton('share link', function () { + window.plugins.socialsharing.share(null, null, null, 'http://www.x-services.nl'); + }, 'buttonShareLink'); + + createActionButton('share message and link', function () { + window.plugins.socialsharing.share('Message body', null, null, 'http://www.x-services.nl'); + }, 'buttonShareMessageAndLink'); + + createActionButton('share image', function () { + window.plugins.socialsharing.share(null, null, 'https://www.google.nl/images/srpr/logo4w.png', null); + }, 'buttonShareImage'); + + createActionButton('share image base 64', function () { + window.plugins.socialsharing.share(null, 'Android filename', 'data:image/png;base64,R0lGODlhDAAMALMBAP8AAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAUKAAEALAAAAAAMAAwAQAQZMMhJK7iY4p3nlZ8XgmNlnibXdVqolmhcRQA7', null); + }, 'buttonShareImageBase64'); + + createActionButton('share message with image', function () { + window.plugins.socialsharing.share('Message body', null, 'https://www.google.nl/images/srpr/logo4w.png', null); + }, 'buttonShareMessageImage'); + + createActionButton('share message, image, and link', function () { + window.plugins.socialsharing.share('Message body', null, 'https://www.google.nl/images/srpr/logo4w.png', 'http://www.x-services.nl'); + }, 'buttonShareMessageImageLink'); + + createActionButton('share message, subject, image, and link', function () { + window.plugins.socialsharing.share('Message body', 'Message subject', 'https://www.google.nl/images/srpr/logo4w.png', 'http://www.x-services.nl'); + }, 'buttonShareMessageSubjectImageLink'); + + createActionButton('can share via', function () { + var target = TestSuite.getCanShareViaTarget(); + + if(!target){ + console.error('must have a canShareVia target'); + } + else { + clearLog(); + window.plugins.socialsharing.canShareVia(target, 'msg', null, null, null, function(e){ + console.log('canShareVia success, see log for more information'); + logMessage('canShareVia: ' + e,'green'); + }, function(e){ + console.error('canShareVia fail, see log for more information'); + var message = "Share targets
"; + + message += ""; + logMessage(message,'red'); + }); + } + }, 'buttonCanShareVia'); + + createActionButton('can share via email', function () { + clearLog(); + window.plugins.socialsharing.canShareViaEmail(function(e){ + console.log('canShareViaEmail success, see log for more information'); + logMessage('canShareViaEmail: ' + e,'green'); + }, function(e){ + console.error('canShareViaEmail fail, see log for more information'); + logMessage('canShareViaEmail: ' + e,'red'); + }); + }, 'buttonCanShareViaEmail'); + + document.addEventListener('deviceready', init, false); +}; diff --git a/tests/tests.js b/tests/tests.js deleted file mode 100644 index bcf78b5f..00000000 --- a/tests/tests.js +++ /dev/null @@ -1,363 +0,0 @@ -/** - * Jasmine Based test suites - * - * Several of SocialSharing APIs cannot be automatically tested, because - * they depend on user interaction in order to call success or fail - * handlers. For most of them, there is a basic test that assert the presence of - * the API. - * - * There are some cases that test automation can be applied, i.e in "canShareVia", - * "canShareViaEmail" and "available" methods. For those cases, there is some level - * of automatic test coverage. - */ -exports.defineAutoTests = function () { - 'use strict'; - - describe('socialsharing', function () { - it('should be defined', function () { - expect(window.plugins.socialsharing).toBeDefined(); - }); - - describe('share', function () { - it('should be defined', function () { - expect(window.plugins.socialsharing.share).toBeDefined(); - }); - - it('should be a function', function () { - expect(typeof window.plugins.socialsharing.share).toEqual('function'); - }); - }); - - describe('shareVia', function () { - it('should be defined', function () { - expect(window.plugins.socialsharing.shareVia).toBeDefined(); - }); - - it('should be a function', function () { - expect(typeof window.plugins.socialsharing.shareVia).toEqual('function'); - }); - }); - - describe('shareViaTwitter', function () { - it('should be defined', function () { - expect(window.plugins.socialsharing.shareViaTwitter).toBeDefined(); - }); - - it('should be a function', function () { - expect(typeof window.plugins.socialsharing.shareViaTwitter).toEqual('function'); - }); - }); - - describe('shareViaFacebook', function () { - it('should be defined', function () { - expect(window.plugins.socialsharing.shareViaFacebook).toBeDefined(); - }); - - it('should be a function', function () { - expect(typeof window.plugins.socialsharing.shareViaFacebook).toEqual('function'); - }); - }); - - describe('shareViaFacebookWithPasteMessageHint', function () { - it('should be defined', function () { - expect(window.plugins.socialsharing.shareViaFacebookWithPasteMessageHint).toBeDefined(); - }); - - it('should be a function', function () { - expect(typeof window.plugins.socialsharing.shareViaFacebookWithPasteMessageHint).toEqual('function'); - }); - }); - - describe('shareViaWhatsApp', function () { - it('should be defined', function () { - expect(window.plugins.socialsharing.shareViaWhatsApp).toBeDefined(); - }); - - it('should be a function', function () { - expect(typeof window.plugins.socialsharing.shareViaWhatsApp).toEqual('function'); - }); - }); - - describe('shareViaSMS', function () { - it('should be defined', function () { - expect(window.plugins.socialsharing.shareViaSMS).toBeDefined(); - }); - - it('should be a function', function () { - expect(typeof window.plugins.socialsharing.shareViaSMS).toEqual('function'); - }); - }); - - describe('shareViaEmail', function () { - it('should be defined', function () { - expect(window.plugins.socialsharing.shareViaEmail).toBeDefined(); - }); - - it('should be a function', function () { - expect(typeof window.plugins.socialsharing.shareViaEmail).toEqual('function'); - }); - }); - - describe('canShareVia', function () { - it('should be defined', function () { - expect(window.plugins.socialsharing.canShareVia).toBeDefined(); - }); - - it('should be a function', function () { - expect(typeof window.plugins.socialsharing.canShareVia).toEqual('function'); - }); - - it('should always call callback or error function', function (done) { - function onSuccess(data){ - expect(data).not.toEqual(null); - done(); - } - - function onError(error){ - expect(error).not.toEqual(null); - done(); - } - - window.plugins.socialsharing.canShareVia('dummytarget','dummymessage', null, null, null, onSuccess, onError); - }); - }); - - describe('canshareViaEmail', function(){ - it('should be defined', function () { - expect(window.plugins.socialsharing.canShareViaEmail).toBeDefined(); - }); - - it('should be a function', function () { - expect(typeof window.plugins.socialsharing.canShareViaEmail).toEqual('function'); - }); - - it('should always call callback or error function', function (done) { - function onSuccess(data){ - expect(data).not.toEqual(null); - done(); - } - - function onError(error){ - expect(error).not.toEqual(null); - done(); - } - - window.plugins.socialsharing.canShareViaEmail(onSuccess, onError); - }); - }); - - describe('availabe', function(){ - it('should be defined', function () { - expect(window.plugins.socialsharing.available).toBeDefined(); - }); - - it('should be a function', function () { - expect(typeof window.plugins.socialsharing.available).toEqual('function'); - }); - - it('should return a boolean when called', function(done){ - window.plugins.socialsharing.available(function(isAvailable) { - expect(typeof isAvailable).toEqual('boolean'); - done(); - }); - }); - }); - }); -}; - -/** - * Manual tests suites - * - * Some actions buttons to execute SocialSharing plugin methods - */ -exports.defineManualTests = function (contentEl, createActionButton) { - 'use strict'; - - /** helper function to log messages in the log div element */ - function logMessage(message, color) { - var log = document.getElementById('info'), - logLine = document.createElement('div'); - - if (color) { - logLine.style.color = color; - } - - logLine.innerHTML = message; - log.appendChild(logLine); - } - - /** helper function to clear the log div element */ - function clearLog() { - var log = document.getElementById('info'); - log.innerHTML = ''; - } - - /** helper function to declare a not implemented test */ - function testNotImplemented(testName) { - return function () { - console.error(testName, 'test not implemented'); - }; - } - - /** init method called on deviceready event */ - function init() {} - - /** object to hold properties and configs */ - var TestSuite = {}; - - TestSuite.getCanShareViaTarget = function(){ - return document.getElementById('inputCanShareVia').value; - }; - - TestSuite.$markup = '' + - '
' + - 'Available Tests' + - - '

Available

' + - '
' + - 'Expected result: Should log if the plugin is available or not' + - '
' + - - '
' + - 'Share Tests' + - - '

Share Message

' + - '
' + - 'Expected result: Should display share widget, and the message to share should contain "Message body"' + - - '

Share Message with Subject

' + - '
' + - 'Expected result: Should display share widget, and the message to share should contain "Message body", and the subject should be "Message subject"' + - - '

Share Link

' + - '' + - 'Expected result: Should display share widget, and the message to share should contain "http://www.x-services.nl"' + - - '

Share Message with Link

' + - '' + - 'Expected result: Should display share widget, and the message to share should contain "Message body http://www.x-services.nl"' + - - '

Share Image

' + - '
' + - 'Expected result: Should display share widget, and the message to share should contain an image' + - - '

Share Image in base 64

' + - '
' + - 'Expected result: Should display share widget, and the message to share should contain an image. The image is encoded in base 64' + - - '

Share Image with Message

' + - '
' + - 'Expected result: Should display share widget, and the message to share should contain "Message body" and an image' + - - '

Share Image with Message and Link

' + - '' + - 'Expected result: Should display share widget, and the message to share should contain "Message body http://www.x-services.nl" and an image' + - - '

Share Image with Message, Subject and Link

' + - '' + - 'Expected result: Should display share widget, and the message to share should contain "Message body http://www.x-services.nl", "Message subject" as subject, and an image' + - '
' + - - '
' + - 'Can Share Tests' + - - 'Target:
' + - - '

Can Share via

' + - '
' + - 'Expected result: should log OK if can share, or should log a list of available share targets' + - - '

Can Share via Email

' + - '
' + - 'Expected result: should log OK if can share' + - '
' + - ''; - - contentEl.innerHTML = '
' + TestSuite.$markup; - - createActionButton('availabe', function () { - clearLog(); - window.plugins.socialsharing.available(function(isAvailable) { - var message = 'is this plugin available? '; - message += isAvailable? 'Yes' : 'No'; - - logMessage(message, isAvailable? 'green' : 'red'); - }); - }, 'buttonIsAvailable'); - - createActionButton('share message', function () { - window.plugins.socialsharing.share('Message body'); - }, 'buttonShareMessage'); - - createActionButton('share message and subject', function () { - window.plugins.socialsharing.share('Message body', 'Message subject'); - }, 'buttonShareMessageWithSubject'); - - createActionButton('share link', function () { - window.plugins.socialsharing.share(null, null, null, 'http://www.x-services.nl'); - }, 'buttonShareLink'); - - createActionButton('share message and link', function () { - window.plugins.socialsharing.share('Message body', null, null, 'http://www.x-services.nl'); - }, 'buttonShareMessageAndLink'); - - createActionButton('share image', function () { - window.plugins.socialsharing.share(null, null, 'https://www.google.nl/images/srpr/logo4w.png', null); - }, 'buttonShareImage'); - - createActionButton('share image base 64', function () { - window.plugins.socialsharing.share(null, 'Android filename', 'data:image/png;base64,R0lGODlhDAAMALMBAP8AAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAUKAAEALAAAAAAMAAwAQAQZMMhJK7iY4p3nlZ8XgmNlnibXdVqolmhcRQA7', null); - }, 'buttonShareImageBase64'); - - createActionButton('share message with image', function () { - window.plugins.socialsharing.share('Message body', null, 'https://www.google.nl/images/srpr/logo4w.png', null); - }, 'buttonShareMessageImage'); - - createActionButton('share message, image, and link', function () { - window.plugins.socialsharing.share('Message body', null, 'https://www.google.nl/images/srpr/logo4w.png', 'http://www.x-services.nl'); - }, 'buttonShareMessageImageLink'); - - createActionButton('share message, subject, image, and link', function () { - window.plugins.socialsharing.share('Message body', 'Message subject', 'https://www.google.nl/images/srpr/logo4w.png', 'http://www.x-services.nl'); - }, 'buttonShareMessageSubjectImageLink'); - - createActionButton('can share via', function () { - var target = TestSuite.getCanShareViaTarget(); - - if(!target){ - console.error('must have a canShareVia target'); - } - else { - clearLog(); - window.plugins.socialsharing.canShareVia(target, 'msg', null, null, null, function(e){ - console.log('canShareVia success, see log for more information'); - logMessage('canShareVia: ' + e,'green'); - }, function(e){ - console.error('canShareVia fail, see log for more information'); - var message = "Share targets
"; - - message += ""; - logMessage(message,'red'); - }); - } - }, 'buttonCanShareVia'); - - createActionButton('can share via email', function () { - clearLog(); - window.plugins.socialsharing.canShareViaEmail(function(e){ - console.log('canShareViaEmail success, see log for more information'); - logMessage('canShareViaEmail: ' + e,'green'); - }, function(e){ - console.error('canShareViaEmail fail, see log for more information'); - logMessage('canShareViaEmail: ' + e,'red'); - }); - }, 'buttonCanShareViaEmail'); - - document.addEventListener('deviceready', init, false); -}; \ No newline at end of file diff --git a/www/SocialSharing.js b/www/SocialSharing.js index 6ccd567b..f056c9fd 100644 --- a/www/SocialSharing.js +++ b/www/SocialSharing.js @@ -1,4 +1,4 @@ -var cordova = require('cordova'); +var cordova = require('cordova'); function SocialSharing() { } @@ -49,7 +49,11 @@ SocialSharing.prototype.shareViaFacebookWithPasteMessageHint = function (message }; SocialSharing.prototype.shareViaWhatsApp = function (message, fileOrFileArray, url, successCallback, errorCallback) { - cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaWhatsApp"), "SocialSharing", "shareViaWhatsApp", [message, null, this._asArray(fileOrFileArray), url]); + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaWhatsApp"), "SocialSharing", "shareViaWhatsApp", [message, null, this._asArray(fileOrFileArray), url, null]); +}; + +SocialSharing.prototype.shareViaWhatsAppToReceiver = function (receiver, message, fileOrFileArray, url, successCallback, errorCallback) { + cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaWhatsAppToReceiver"), "SocialSharing", "shareViaWhatsApp", [message, null, this._asArray(fileOrFileArray), url, receiver]); }; SocialSharing.prototype.shareViaSMS = function (options, phonenumbers, successCallback, errorCallback) {