From d13a287f68cdfc7fe8260e2d056658c994d493a4 Mon Sep 17 00:00:00 2001 From: Don Coleman Date: Fri, 10 Jul 2020 14:58:57 -0400 Subject: [PATCH] Allow iOS to read tag before writing (#420) * write can reuse session from read see #419 * Update README.md * cleanup connectedTag --- README.md | 64 ++++++++++++++++++++++++++++++++++++++++++++- src/ios/NfcPlugin.m | 61 +++++++++++++++++++++++++++++++++--------- www/phonegap-nfc.js | 6 ++--- 3 files changed, 115 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 0cdd9c28..4df50fb7 100644 --- a/README.md +++ b/README.md @@ -307,12 +307,74 @@ Function `nfc.write` writes an NdefMessage to a NFC tag. On **Android** this method *must* be called from within an NDEF Event Handler. -On **iOS** this method should be called outside the NDEF Event Handler, it will start a new scanning session. +On **iOS** this method can be called outside the NDEF Event Handler, it will start a new scanning session. Optionally you can reuse the read session to write data. See example below. On **Windows** this method *may* be called from within the NDEF Event Handler. On **Windows Phone 8.1** this method should be called outside the NDEF Event Handler, otherwise Windows tries to read the tag contents as you are writing to the tag. +### Examples + +#### Android + +On Android, write must be called inside an event handler + + function onNfc(nfcEvent) { + + console.log(nfcEvent.tag); + + var message = [ + ndef.textRecord(new String(new Date())) + ]; + + nfc.write( + message, + success => console.log('wrote data to tag'), + error => console.log(error) + ); + + nfc.addNdefListener(onNfc); + + +#### iOS - Simple + +Calling `nfc.write` on iOS will create a new session and write data when the user taps a NFC tag + + var message = [ + ndef.textRecord("Hello, world") + ]; + + nfc.write( + message, + success => console.log('wrote data to tag'), + error => console.log(error) + ); + +#### iOS - Read and Write + +On iOS you can optionally write to NFC tag using the read session + + try { + let tag = await nfc.scanNdef({ keepSessionOpen: true}); + + // you can read tag data here + console.log(tag); + + // this example writes a new message with a timestamp + var message = [ + ndef.textRecord(new String(new Date())) + ]; + + nfc.write( + message, + success => console.log('wrote data to tag'), + error => console.log(error) + ); + + } catch (err) { + console.log(err); + } + ### Supported Platforms - Android diff --git a/src/ios/NfcPlugin.m b/src/ios/NfcPlugin.m index cc72ab8f..9b40f251 100644 --- a/src/ios/NfcPlugin.m +++ b/src/ios/NfcPlugin.m @@ -9,12 +9,15 @@ @interface NfcPlugin() { NSString* sessionCallbackId; NSString* channelCallbackId; + id connectedTag API_AVAILABLE(ios(13.0)); + NFCNDEFStatus connectedTagStatus API_AVAILABLE(ios(13.0)); } @property (nonatomic, assign) BOOL writeMode; @property (nonatomic, assign) BOOL shouldUseTagReaderSession; @property (nonatomic, assign) BOOL sendCallbackOnSessionStart; @property (nonatomic, assign) BOOL returnTagInCallback; @property (nonatomic, assign) BOOL returnTagInEvent; +@property (nonatomic, assign) BOOL keepSessionOpen; @property (strong, nonatomic) NFCReaderSession *nfcSession API_AVAILABLE(ios(11.0)); @property (strong, nonatomic) NFCNDEFMessage *messageToWrite API_AVAILABLE(ios(11.0)); @end @@ -52,7 +55,8 @@ - (void)beginSession:(CDVInvokedUrlCommand*)command { self.sendCallbackOnSessionStart = YES; // Not sure why we were doing this self.returnTagInCallback = NO; self.returnTagInEvent = YES; - + self.keepSessionOpen = NO; + [self startScanSession:command]; } @@ -64,6 +68,9 @@ - (void)scanNdef:(CDVInvokedUrlCommand*)command { self.returnTagInCallback = YES; self.returnTagInEvent = NO; + NSArray *options = [command argumentAtIndex:0]; + self.keepSessionOpen = [options valueForKey:@"keepSessionOpen"]; + [self startScanSession:command]; } @@ -75,6 +82,9 @@ - (void)scanTag:(CDVInvokedUrlCommand*)command { self.returnTagInCallback = YES; self.returnTagInEvent = NO; + NSArray *options = [command argumentAtIndex:0]; + self.keepSessionOpen = [options valueForKey:@"keepSessionOpen"]; + [self startScanSession:command]; } @@ -83,6 +93,7 @@ - (void)writeTag:(CDVInvokedUrlCommand*)command API_AVAILABLE(ios(13.0)){ self.writeMode = YES; self.shouldUseTagReaderSession = NO; + BOOL reusingSession = NO; NSArray *ndefData = [command argumentAtIndex:0]; @@ -107,23 +118,32 @@ - (void)writeTag:(CDVInvokedUrlCommand*)command API_AVAILABLE(ios(13.0)){ [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; return; } - - // Start the NFC Session - if (self.shouldUseTagReaderSession) { - NSLog(@"Using NFCTagReaderSession"); - self.nfcSession = [[NFCTagReaderSession new] - initWithPollingOption:(NFCPollingISO14443 | NFCPollingISO15693) - delegate:self queue:dispatch_get_main_queue()]; + if (self.nfcSession && self.nfcSession.isReady) { // reuse existing session + reusingSession = YES; + } else { // create a new session + if (self.shouldUseTagReaderSession) { + NSLog(@"Using NFCTagReaderSession"); - } else { - NSLog(@"Using NFCTagReaderSession"); - self.nfcSession = [[NFCNDEFReaderSession new]initWithDelegate:self queue:nil invalidateAfterFirstRead:FALSE]; + self.nfcSession = [[NFCTagReaderSession new] + initWithPollingOption:(NFCPollingISO14443 | NFCPollingISO15693) + delegate:self queue:dispatch_get_main_queue()]; + + } else { + NSLog(@"Using NFCTagReaderSession"); + self.nfcSession = [[NFCNDEFReaderSession new]initWithDelegate:self queue:nil invalidateAfterFirstRead:FALSE]; + } } self.nfcSession.alertMessage = @"Hold near writable NFC tag to update."; sessionCallbackId = [command.callbackId copy]; - [self.nfcSession beginSession]; + + if (reusingSession) { // reusing a read session to write + self.keepSessionOpen = NO; // close session after writing + [self writeNDEFTag:self.nfcSession status:connectedTagStatus tag:connectedTag]; + } else { + [self.nfcSession beginSession]; + } } - (void)cancelScan:(CDVInvokedUrlCommand*)command API_AVAILABLE(ios(11.0)){ @@ -131,6 +151,8 @@ - (void)cancelScan:(CDVInvokedUrlCommand*)command API_AVAILABLE(ios(11.0)){ if (self.nfcSession) { [self.nfcSession invalidateSession]; } + connectedTag = NULL; + connectedTagStatus = NFCNDEFStatusNotSupported; CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } @@ -326,6 +348,11 @@ - (void)processNDEFTag: (NFCReaderSession *)session tag:(__kindof id if (self.writeMode) { [self writeNDEFTag:session status:status tag:tag]; } else { + // save tag & status so we can re-use in write + if (self.keepSessionOpen) { + self->connectedTagStatus = status; + self->connectedTag = tag; + } [self readNDEFTag:session status:status tag:tag metaData:metaData]; } @@ -456,8 +483,16 @@ - (void) sessionDidBecomeActive:(NFCReaderSession *) session API_AVAILABLE(ios( } - (void) closeSession:(NFCReaderSession *) session API_AVAILABLE(ios(11.0)){ + + // this is a hack to keep a read session open to allow writing + if (self.keepSessionOpen) { + return; + } + // kill the callback so the Cordova doesn't get "Session invalidated by user" sessionCallbackId = NULL; + connectedTag = NULL; + connectedTagStatus = NFCNDEFStatusNotSupported; [session invalidateSession]; } @@ -466,6 +501,8 @@ - (void) closeSession:(NFCReaderSession *) session withError:(NSString *) errorM // kill the callback so Cordova doesn't get "Session invalidated by user" sessionCallbackId = NULL; + connectedTag = NULL; + connectedTagStatus = NFCNDEFStatusNotSupported; if (@available(iOS 13.0, *)) { [session invalidateSessionWithErrorMessage:errorMessage]; diff --git a/www/phonegap-nfc.js b/www/phonegap-nfc.js index 52d82c83..7349bb7a 100644 --- a/www/phonegap-nfc.js +++ b/www/phonegap-nfc.js @@ -501,16 +501,16 @@ var nfc = { }, // iOS only - scan for NFC NDEF tag using NFCNDEFReaderSession - scanNdef: function () { + scanNdef: function (options) { return new Promise(function(resolve, reject) { - cordova.exec(resolve, reject, "NfcPlugin", "scanNdef", []); + cordova.exec(resolve, reject, "NfcPlugin", "scanNdef", [options]); }); }, // iOS only - scan for NFC Tag using NFCTagReaderSession scanTag: function (options) { return new Promise(function(resolve, reject) { - cordova.exec(resolve, reject, "NfcPlugin", "scanTag", []); + cordova.exec(resolve, reject, "NfcPlugin", "scanTag", [options]); }); },