diff --git a/CHANGES.txt b/CHANGES.txt index 9d49078a..3450b670 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,18 @@ += 0.7.3 = +Bump version for npm issues + += 0.7.2 = +Add support for WKWebView #288 Thanks andreamaioli + += 0.7.1 = +Automatically Add NFC entitlement for iOS #285 Thanks andreamaioli + += 0.7.0 = +Add iOS support #139 +Fixed language code field length detection #219 Thanks homer-jay +Fixed java.util.ConcurrentModificationException #231 Thanks João Gonçalves (Chuckytuh) +Documentation fixes #224 Thanks Tom Brückner (derwaldgeist) + = 0.6.6 = Update Windows platforms (includes Windows Phone 8.1) * tag event contains nested ndefMessage object #215 diff --git a/LICENSE.txt b/LICENSE.txt index 20e34ab9..69a241c6 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2011-2015 Chariot Solutions +Copyright (c) 2011-2017 Chariot Solutions Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 0cf62583..67ff098a 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ This plugin uses NDEF (NFC Data Exchange Format) for maximum compatibilty betwee Supported Platforms ------------------- * Android +* [iOS 11](#ios-notes) * Windows (includes Windows Phone 8.1, Windows 8.1, Windows 10) * BlackBerry 10 * Windows Phone 8 @@ -59,6 +60,16 @@ BlackBerry 7 support is only available for Cordova 2.x. For applications targeti See [Getting Started](https://github.com/chariotsolutions/phonegap-nfc/blob/master/doc/GettingStartedCLI.md) and [Getting Started BlackBerry 10](https://github.com/chariotsolutions/phonegap-nfc/blob/master/doc/GettingStartedBlackberry10.md)for more details. +## iOS Notes + +Reading NFC NDEF tags is supported on iPhone 7 and iPhone 7 Plus running iOS 11. To enable your app to detect NFC tags, the plugin adds the Near Field Communication Tag Reading capability in your Xcode project. You must build your application with XCode 9. See the [Apple Documentation](http://help.apple.com/xcode/mac/current/#/dev88ff319e7) for more info. + +Use [nfc.addNdefListener](#nfcaddndeflistener) to read NDEF NFC tags with iOS. Unfortunately, iOS also requires you to begin a session before scanning NFC tag. The JavaScript API contains two new iOS specific functions [nfc.beginSession](#nfcbeginsession) and [nfc.invalidateSession](#nfcinvalidatesession). + +You must call [nfc.beginSession](#nfcbeginsession) before every scan. + +The initial iOS version plugin does not support scanning multiple tags (invalidateAfterFirstRead:FALSE) or setting the alertMessage. If you have use cases or suggestions on the best way to support multi-read or alert messages, open a ticket for discussion. + # NFC > The nfc object provides access to the device's NFC sensor. @@ -81,6 +92,8 @@ See [Getting Started](https://github.com/chariotsolutions/phonegap-nfc/blob/mast - [nfc.stopHandover](#nfcstophandover) - [nfc.enabled](#nfcenabled) - [nfc.showSettings](#nfcshowsettings) +- [nfc.beginSession](#nfcbeginsession) +- [nfc.invalidateSession](#nfcinvalidatesession) ## nfc.addNdefListener @@ -104,9 +117,12 @@ For BlackBerry 10, you must configure the type of tags your application will rea On Android registered [mimeTypeListeners](#nfcaddmimetypelistener) takes precedence over this more generic NDEF listener. +On iOS you must call [beingSession](#nfcbeginsession) before scanning a tag. + ### Supported Platforms - Android +- iOS - Windows - BlackBerry 7 - BlackBerry 10 @@ -127,6 +143,7 @@ Removes the previously registered event listener for NDEF tags added via `nfc.ad ### Supported Platforms - Android +- iOS - Windows - BlackBerry 7 @@ -154,6 +171,8 @@ This event occurs when any tag is detected by the phone. - Windows - BlackBerry 7 +Note that Windows Phones need the newere NXP PN427 chipset to read non-NDEF tags. That tag will be read, but no tag meta-data is available. + ## nfc.removeTagDiscoveredListener Removes the previously registered event listener added via `nfc.addTagDiscoveredListener`. @@ -201,7 +220,6 @@ On Android, MIME types for filtering should always be lower case. (See [IntentFi ### Supported Platforms - Android -- Windows - BlackBerry 7 ## nfc.removeMimeTypeListener @@ -506,8 +524,56 @@ Windows will return **NO_NFC_OR_NFC_DISABLED** when NFC is not present or disabl ### Supported Platforms - Android +- iOS - Windows +## nfc.beginSession + +iOS requires you to begin a session before scanning a NFC tag. + + nfc.beginSession(success, failure); + +### Description + +Function `beginSession` starts the [NFCNDEFReaderSession](https://developer.apple.com/documentation/corenfc/nfcndefreadersession) allowing iOS to scan NFC tags. + +### Parameters + +- __success__: Success callback function called when the session begins [optional] +- __failure__: Error callback function, invoked when error occurs. [optional] + +### Quick Example + + nfc.beginSession(); + +### Supported Platforms + +- iOS + +## nfc.invalidateSession + +Invalidate the NFC session. + + nfc.invalidateSession(success, failure); + +### Description + +Function `invalidateSession` stops the [NFCNDEFReaderSession](https://developer.apple.com/documentation/corenfc/nfcndefreadersession) returning control to your app. + +### Parameters + +- __success__: Success callback function called when the session in invalidated [optional] +- __failure__: Error callback function, invoked when error occurs. [optional] + +### Quick Example + + nfc.invalidateSession(); + +### Supported Platforms + +- iOS + + # NDEF > The `ndef` object provides NDEF constants, functions for creating NdefRecords, and functions for converting data. @@ -588,7 +654,7 @@ The tag contents are platform dependent. `id` and `techTypes` may be included when scanning a tag on Android. `serialNumber` may be included on BlackBerry 7. -`id` and `serialNumber` are different names for the same value. `id` is typically displayed as a hex string `ndef.bytesToHexString(tag.id)`. +`id` and `serialNumber` are different names for the same value. `id` is typically displayed as a hex string `nfc.bytesToHexString(tag.id)`. Windows, Windows Phone 8, and BlackBerry 10 read the NDEF information from a tag, but do not have access to the tag id or other meta data like capacity, read-only status or tag technologies. @@ -663,7 +729,7 @@ You can also log the tag contents in your event handlers. `console.log(JSON.str ## Non-NDEF Tags -Only Android and BlackBerry 7 can read data from non-NDEF NFC tags. +Only Android and BlackBerry 7 can read data from non-NDEF NFC tags. Newer Windows Phones with NXP PN427 chipset can read non-NDEF tags, but can not get any tag meta data. ## Mifare Classic Tags @@ -833,7 +899,7 @@ License The MIT License -Copyright (c) 2011-2015 Chariot Solutions +Copyright (c) 2011-2017 Chariot Solutions Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/package.json b/package.json index 1d1e3653..fd8bf98d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "phonegap-nfc", - "version": "0.6.6", + "version": "0.7.3", "description": "Near Field Communication (NFC) Plugin. Read and write NDEF messages to NFC tags and share NDEF messages with peers.", "cordova": { "id": "phonegap-nfc", @@ -8,7 +8,8 @@ "android", "wp8", "windows", - "blackberry10" + "blackberry10", + "ios" ] }, "repository": { @@ -23,7 +24,8 @@ "cordova-android", "cordova-wp8", "cordova-windows", - "cordova-blackberry10" + "cordova-blackberry10", + "cordova-ios" ], "author": "Don Coleman ", "license": "MIT", diff --git a/plugin.xml b/plugin.xml index 1a1407ca..4f40be21 100644 --- a/plugin.xml +++ b/plugin.xml @@ -3,7 +3,7 @@ xmlns="http://www.phonegap.com/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android" id="phonegap-nfc" - version="0.6.6"> + version="0.7.3"> NFC @@ -103,5 +103,40 @@ + + + + + + + + + + + + + + NDEF + + + + + + NDEF + + + + + + + + + + + + $NFC_USAGE_DESCRIPTION + + + diff --git a/src/ios/NfcPlugin.h b/src/ios/NfcPlugin.h new file mode 100644 index 00000000..e74f679b --- /dev/null +++ b/src/ios/NfcPlugin.h @@ -0,0 +1,28 @@ +// +// NfcPlugin.h +// PhoneGap NFC - Cordova Plugin +// +// (c) 2107 Don Coleman + +#ifndef NfcPlugin_h +#define NfcPlugin_h + +#import +#import +#import + +@interface NfcPlugin : CDVPlugin { +} + +// iOS Specific API +- (void)beginSession:(CDVInvokedUrlCommand *)command; +- (void)invalidateSession:(CDVInvokedUrlCommand *)command; + +// Standard PhoneGap NFC API +- (void)registerNdef:(CDVInvokedUrlCommand *)command; +- (void)removeNdef:(CDVInvokedUrlCommand *)command; +- (void)enabled:(CDVInvokedUrlCommand *)command; + +@end + +#endif diff --git a/src/ios/NfcPlugin.m b/src/ios/NfcPlugin.m new file mode 100644 index 00000000..742e1ca3 --- /dev/null +++ b/src/ios/NfcPlugin.m @@ -0,0 +1,173 @@ +// +// NfcPlugin.m +// PhoneGap NFC - Cordova Plugin +// +// (c) 2107 Don Coleman + +#import "NfcPlugin.h" + +@interface NfcPlugin() { + NSString* ndefStartSessionCallbackId; +} +@property (strong, nonatomic) NFCNDEFReaderSession *nfcSession; +@end + +@implementation NfcPlugin + +- (void)pluginInitialize { + + NSLog(@"PhoneGap NFC - Cordova Plugin"); + NSLog(@"(c)2017 Don Coleman"); + + [super pluginInitialize]; + + // TODO fail quickly if not supported + if (![NFCNDEFReaderSession readingAvailable]) { + NSLog(@"NFC Support is NOT available"); + } +} + +#pragma mark -= Cordova Plugin Methods + +// Unfortunately iOS users need to start a session to read tags +- (void)beginSession:(CDVInvokedUrlCommand*)command { + NSLog(@"beginSession"); + + _nfcSession = [[NFCNDEFReaderSession new]initWithDelegate:self queue:nil invalidateAfterFirstRead:TRUE]; + ndefStartSessionCallbackId = [command.callbackId copy]; + [_nfcSession beginSession]; +} + +- (void)invalidateSession:(CDVInvokedUrlCommand*)command { + NSLog(@"invalidateSession"); + if (_nfcSession) { + [_nfcSession invalidateSession]; + } + // Always return OK. Alternately could send status from the NFCNDEFReaderSessionDelegate + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + +// Nothing happens here, the event listener is registered in JavaScript +- (void)registerNdef:(CDVInvokedUrlCommand *)command { + NSLog(@"registerNdef"); + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + +// Nothing happens here, the event listener is removed in JavaScript +- (void)removeNdef:(CDVInvokedUrlCommand *)command { + NSLog(@"removeNdef"); + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + +- (void)enabled:(CDVInvokedUrlCommand *)command { + NSLog(@"enabled"); + CDVPluginResult *pluginResult; + if ([NFCNDEFReaderSession readingAvailable]) { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + } else { + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"NO_NFC"]; + } + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + +#pragma mark - NFCNDEFReaderSessionDelegate + +- (void) readerSession:(NFCNDEFReaderSession *)session didDetectNDEFs:(NSArray *)messages { + NSLog(@"NFCNDEFReaderSession didDetectNDEFs"); + + for (NFCNDEFMessage *message in messages) { + [self fireNdefEvent: message]; + } +} + +- (void) readerSession:(NFCNDEFReaderSession *)session didInvalidateWithError:(NSError *)error { + NSLog(@"didInvalidateWithError %@ %@", error.localizedDescription, error.localizedFailureReason); + if (ndefStartSessionCallbackId) { + NSString* errorMessage = [NSString stringWithFormat:@"error: %@", error.localizedDescription]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errorMessage]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:ndefStartSessionCallbackId]; + } +} + +- (void) readerSessionDidBecomeActive:(nonnull NFCReaderSession *)session { + NSLog(@"readerSessionDidBecomeActive"); + if (ndefStartSessionCallbackId) { + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + //[pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:ndefStartSessionCallbackId]; + ndefStartSessionCallbackId = NULL; + } +} + +#pragma mark - internal implementation + +// Create a JSON description of the NFC NDEF tag and call a JavaScript function fireNfcTagEvent. +// The event handler registered by addNdefListener will handle the JavaScript event fired by fireNfcTagEvent(). +// This is a bit convoluted and based on how PhoneGap 0.9 worked. A new implementation would send the data +// in a success callback. +-(void) fireNdefEvent:(NFCNDEFMessage *) ndefMessage { + NSString *ndefMessageAsJSONString = [self ndefMessagetoJSONString:ndefMessage]; + NSLog(@"%@", ndefMessageAsJSONString); + + // construct string to call JavaScript function fireNfcTagEvent(eventType, tagAsJson); + NSString *function = [NSString stringWithFormat:@"fireNfcTagEvent('ndef', '%@')", ndefMessageAsJSONString]; + dispatch_async(dispatch_get_main_queue(), ^{ + if ([[self webView] isKindOfClass:WKWebView.class]) + [(WKWebView*)[self webView] evaluateJavaScript:function completionHandler:^(id result, NSError *error) {}]; + else + [(UIWebView*)[self webView] stringByEvaluatingJavaScriptFromString: function]; + }); +} + +-(NSString *) ndefMessagetoJSONString:(NFCNDEFMessage *) ndefMessage { + + NSMutableArray *array = [NSMutableArray new]; + for (NFCNDEFPayload *record in ndefMessage.records){ + NSDictionary* recordDictionary = [self ndefRecordToNSDictionary:record]; + [array addObject:recordDictionary]; + } + + // The JavaScript tag object expects a key with ndefMessage + NSMutableDictionary *wrapper = [NSMutableDictionary new]; + [wrapper setObject:array forKey:@"ndefMessage"]; + return dictionaryAsJSONString(wrapper); +} + +-(NSDictionary *) ndefRecordToNSDictionary:(NFCNDEFPayload *) ndefRecord { + NSMutableDictionary *dict = [NSMutableDictionary new]; + dict[@"tnf"] = [NSNumber numberWithInt:(int)ndefRecord.typeNameFormat]; + dict[@"type"] = uint8ArrayFromNSData(ndefRecord.type); + dict[@"id"] = uint8ArrayFromNSData(ndefRecord.identifier); + dict[@"payload"] = uint8ArrayFromNSData(ndefRecord.payload); + NSDictionary *copy = [dict copy]; + return copy; +} + +// returns an NSArray of uint8_t representing the bytes in the NSData object. +NSArray *uint8ArrayFromNSData(NSData *data) { + const void *bytes = [data bytes]; + NSMutableArray *array = [NSMutableArray array]; + for (NSUInteger i = 0; i < [data length]; i += sizeof(uint8_t)) { + uint8_t elem = OSReadLittleInt(bytes, i); + [array addObject:[NSNumber numberWithInt:elem]]; + } + return array; +} + +NSString* dictionaryAsJSONString(NSDictionary *dict) { + NSError *error; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:0 error:&error]; + NSString *jsonString; + if (! jsonData) { + jsonString = [NSString stringWithFormat:@"Error creating JSON for NDEF Message: %@", error]; + NSLog(@"%@", jsonString); + } else { + jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + } + return jsonString; +} + +@end diff --git a/www/phonegap-nfc.js b/www/phonegap-nfc.js index 5e8132b2..11197f55 100644 --- a/www/phonegap-nfc.js +++ b/www/phonegap-nfc.js @@ -485,6 +485,16 @@ var nfc = { showSettings: function (win, fail) { cordova.exec(win, fail, "NfcPlugin", "showSettings", []); + }, + + // iOS only + beginSession: function (win, fail) { + cordova.exec(win, fail, "NfcPlugin", "beginSession", []); + }, + + // iOS only + invalidateSession: function (win, fail) { + cordova.exec(win, fail, "NfcPlugin", "invalidateSession", []); } }; @@ -641,7 +651,7 @@ var textHelper = { decodePayload: function (data) { - var languageCodeLength = (data[0] & 0x1F), // 5 bits + var languageCodeLength = (data[0] & 0x3F), // 6 LSBs languageCode = data.slice(1, 1 + languageCodeLength), utf16 = (data[0] & 0x80) !== 0; // assuming UTF-16BE @@ -712,7 +722,7 @@ var uriHelper = { } }; -// added since WP8 must call a named function +// added since WP8 must call a named function, also used by iOS // TODO consider switching NFC events from JS events to using the PG callbacks function fireNfcTagEvent(eventType, tagAsJson) { setTimeout(function () {