community-cordova-plugin-nfc 1.8.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -1
- package/README.md +99 -5
- package/package.json +1 -1
- package/plugin.xml +1 -1
- package/src/ios/NfcPlugin.h +9 -1
- package/src/ios/NfcPlugin.m +292 -42
- package/types/index.d.ts +50 -5
- package/www/phonegap-nfc.js +47 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,26 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
-
## [1.
|
|
6
|
+
## [1.9.0](https://github.com/EYALIN/community-cordova-plugin-nfc/compare/v1.8.0...v1.9.0) (2026-10-09)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **iOS: `nfc.erase` and `nfc.makeReadOnly`** (PLU-257). Both were Android-only; on iOS 13+, call
|
|
11
|
+
them after `scanNdef`/`scanTag({keepSessionOpen: true})` resolves. `erase` writes an empty NDEF
|
|
12
|
+
record, failing with `READ_ONLY` or `NOT_SUPPORTED` as appropriate; `makeReadOnly` locks the tag
|
|
13
|
+
via `writeLockWithCompletionHandler:`, resolving `{alreadyLocked: true}` if the tag is already
|
|
14
|
+
locked and failing with `NOT_SUPPORTED` if the tag can't be locked. Both accept an optional
|
|
15
|
+
`{alertMessage}` on iOS. Below iOS 13, both fail with `NOT_SUPPORTED`.
|
|
16
|
+
* **iOS: `nfc.transceive` reaches MIFARE tags** (PLU-258), via `NFCMiFareTag.sendMiFareCommand` -
|
|
17
|
+
the response has no `sw1`/`sw2` appended, unlike the existing ISO 7816 path. As an experiment,
|
|
18
|
+
the same MIFARE path is also tried on a tag from an NDEF-session scan when it happens to conform
|
|
19
|
+
to the MIFARE protocol; that fallback is unverified on real hardware.
|
|
20
|
+
* **`nfc.capabilities()`** on every platform: `{erase, makeReadOnly, ntagPassword, platform}`.
|
|
21
|
+
`true`/`true` on Android; on iOS, `erase`/`makeReadOnly` are `true` from 13 onward. `ntagPassword`
|
|
22
|
+
is `true` on Android and **`false` on iOS** even on 13+ - the MIFARE `transceive` path those
|
|
23
|
+
helpers would need (PLU-255/PLU-258) is not yet confirmed against a real NTAG213/215.
|
|
24
|
+
|
|
25
|
+
## [1.8.0](https://github.com/EYALIN/community-cordova-plugin-nfc/compare/v1.7.1...v1.8.0) (2026-09-27)
|
|
7
26
|
|
|
8
27
|
### Fixes
|
|
9
28
|
|
package/README.md
CHANGED
|
@@ -20,6 +20,13 @@ or if you're asking for new features or priority bug fixes. Thank you!
|
|
|
20
20
|
- **Advanced Tag Analysis** (v1.5.0+) - Read raw memory, get NTAG version, counter, signature
|
|
21
21
|
- **TypeScript Support** - Full TypeScript definitions included
|
|
22
22
|
|
|
23
|
+
## What's New in 1.9.0
|
|
24
|
+
|
|
25
|
+
iOS: `nfc.erase` and `nfc.makeReadOnly` now work (13+, PLU-257). `nfc.transceive` can also reach a
|
|
26
|
+
MIFARE tag (PLU-258), the basis for the Android NTAG password helpers, though that path is not yet
|
|
27
|
+
verified on real hardware. New `nfc.capabilities()` on every platform reports which of these are
|
|
28
|
+
actually supported, instead of trying an operation and parsing the error.
|
|
29
|
+
|
|
23
30
|
## What's New in 1.8.0
|
|
24
31
|
|
|
25
32
|
Crash and connection fixes on Android, coded errors on both platforms, correct NTAG helpers, iOS
|
|
@@ -247,7 +254,10 @@ await nfc.close();
|
|
|
247
254
|
|
|
248
255
|
## Password protection, originality, ISO 15693, MIFARE Classic (1.8.0)
|
|
249
256
|
|
|
250
|
-
All Android-only
|
|
257
|
+
All Android-only, after `nfc.connect(tech)` on a scanned tag. On iOS, `nfc.transceive` can reach
|
|
258
|
+
a MIFARE tag via `NFCMiFareTag` (v1.9.0, PLU-258), which is what these helpers would need, but that
|
|
259
|
+
path is unverified on real NTAG213/215 hardware - `nfc.capabilities().ntagPassword` reports `false`
|
|
260
|
+
on iOS until it is proven, and these higher-level helpers themselves are not wired to iOS yet.
|
|
251
261
|
Bytes can be given as a hex string, a byte array or a `Uint8Array`.
|
|
252
262
|
|
|
253
263
|
```js
|
|
@@ -342,6 +352,10 @@ or a hook):
|
|
|
342
352
|
| `NFCReadOnlyTag` | Tag is read only. | writing to a locked tag |
|
|
343
353
|
| `NFCDataWriteFailed` | Write failed. | the write failed |
|
|
344
354
|
| `NFCUnknownNdefTag` | Unknown NDEF tag status. | unexpected NDEF status |
|
|
355
|
+
| `NFCTagAlreadyReadOnly` | Tag is already read only. | `makeReadOnly` on an already-locked tag (v1.9.0) |
|
|
356
|
+
| `NFCLockNotSupported` | This tag does not support being locked. | `makeReadOnly` on a tag whose hardware can't lock (v1.9.0) |
|
|
357
|
+
| `NFCLockFailed` | Failed to lock tag. | the lock write failed (v1.9.0) |
|
|
358
|
+
| `NFCTagLocked` | Tag is now read only. | a lock succeeded (v1.9.0) |
|
|
345
359
|
|
|
346
360
|
Example `he.lproj/Localizable.strings`:
|
|
347
361
|
|
|
@@ -377,6 +391,7 @@ English message.
|
|
|
377
391
|
- [nfc.scanNdef](#nfcscanndef)
|
|
378
392
|
- [nfc.scanTag](#nfcscanTag)
|
|
379
393
|
- [nfc.cancelScan](#nfccancelscan)
|
|
394
|
+
- [nfc.capabilities](#nfccapabilities)
|
|
380
395
|
|
|
381
396
|
## ReaderMode
|
|
382
397
|
|
|
@@ -642,12 +657,14 @@ On iOS you can optionally write to NFC tag using the read session
|
|
|
642
657
|
|
|
643
658
|
Makes a NFC tag read only. **Warning this is permanent.**
|
|
644
659
|
|
|
645
|
-
nfc.makeReadOnly([onSuccess], [onFailure]);
|
|
660
|
+
nfc.makeReadOnly([onSuccess], [onFailure], [options]);
|
|
646
661
|
|
|
647
662
|
### Parameters
|
|
648
663
|
|
|
649
664
|
- __onSuccess__: (Optional) The callback that is called when the tag is locked.
|
|
650
665
|
- __onFailure__: (Optional) The callback that is called if there was an error.
|
|
666
|
+
- __options__: (Optional, **iOS only**, v1.9.0) `{ alertMessage: "..." }` overrides the text shown
|
|
667
|
+
on the system NFC sheet while locking. Ignored on Android.
|
|
651
668
|
|
|
652
669
|
### Description
|
|
653
670
|
|
|
@@ -679,9 +696,22 @@ Example usage
|
|
|
679
696
|
|
|
680
697
|
},
|
|
681
698
|
|
|
699
|
+
On **iOS** (13+), call it after `nfc.scanNdef({keepSessionOpen: true})` or
|
|
700
|
+
`nfc.scanTag({keepSessionOpen: true})` resolves - there is no NDEF event handler, so lock the tag
|
|
701
|
+
from that resolved handler instead:
|
|
702
|
+
|
|
703
|
+
const tag = await nfc.scanNdef({ keepSessionOpen: true });
|
|
704
|
+
await nfc.makeReadOnly(null, null, { alertMessage: "Locking tag…" });
|
|
705
|
+
|
|
706
|
+
A tag that is already read only resolves successfully (`{alreadyLocked: true}`) rather than
|
|
707
|
+
failing. A tag whose hardware doesn't support locking fails with `NOT_SUPPORTED`. Below iOS 13,
|
|
708
|
+
`makeReadOnly` always fails with `NOT_SUPPORTED`. Check `nfc.capabilities().makeReadOnly` to
|
|
709
|
+
detect support ahead of time.
|
|
710
|
+
|
|
682
711
|
### Supported Platforms
|
|
683
712
|
|
|
684
713
|
- Android
|
|
714
|
+
- iOS 13+ (v1.9.0, PLU-257)
|
|
685
715
|
|
|
686
716
|
## nfc.share
|
|
687
717
|
|
|
@@ -734,22 +764,31 @@ Function `nfc.unshare` stops sharing data via peer-to-peer.
|
|
|
734
764
|
|
|
735
765
|
Erase a NDEF tag
|
|
736
766
|
|
|
737
|
-
nfc.erase([onSuccess], [onFailure]);
|
|
767
|
+
nfc.erase([onSuccess], [onFailure], [options]);
|
|
738
768
|
|
|
739
769
|
### Parameters
|
|
740
770
|
|
|
741
771
|
- __onSuccess__: (Optional) The callback that is called when sharing stops.
|
|
742
772
|
- __onFailure__: (Optional) The callback that is called if there was an error.
|
|
773
|
+
- __options__: (Optional, **iOS only**, v1.9.0) `{ alertMessage: "..." }` overrides the text shown
|
|
774
|
+
on the system NFC sheet while erasing. Ignored on Android.
|
|
743
775
|
|
|
744
776
|
### Description
|
|
745
777
|
|
|
746
778
|
Function `nfc.erase` erases a tag by writing an empty message. Will format unformatted tags before writing.
|
|
747
779
|
|
|
748
|
-
|
|
780
|
+
On **Android** this method *must* be called from within an NDEF Event Handler.
|
|
781
|
+
|
|
782
|
+
On **iOS** (13+), call it after `nfc.scanNdef({keepSessionOpen: true})` or
|
|
783
|
+
`nfc.scanTag({keepSessionOpen: true})` resolves, the same way as `nfc.makeReadOnly` above. A
|
|
784
|
+
read-only tag fails with `READ_ONLY`; a tag whose hardware doesn't support writing fails with
|
|
785
|
+
`NOT_SUPPORTED`. Below iOS 13, `erase` always fails with `NOT_SUPPORTED`. Check
|
|
786
|
+
`nfc.capabilities().erase` to detect support ahead of time.
|
|
749
787
|
|
|
750
788
|
### Supported Platforms
|
|
751
789
|
|
|
752
790
|
- Android
|
|
791
|
+
- iOS 13+ (v1.9.0, PLU-257)
|
|
753
792
|
## nfc.handover
|
|
754
793
|
|
|
755
794
|
Send a file to another device via NFC handover.
|
|
@@ -1009,6 +1048,47 @@ Function `cancelScan` stops the [NFCReaderSession](https://developer.apple.com/d
|
|
|
1009
1048
|
|
|
1010
1049
|
- iOS
|
|
1011
1050
|
|
|
1051
|
+
## nfc.capabilities
|
|
1052
|
+
|
|
1053
|
+
Reports which optional NFC features this platform actually implements, so the app can decide
|
|
1054
|
+
what to offer instead of trying an operation and parsing the error. Added v1.9.0 (PLU-258).
|
|
1055
|
+
|
|
1056
|
+
nfc.capabilities([onSuccess], [onFailure]);
|
|
1057
|
+
|
|
1058
|
+
### Parameters
|
|
1059
|
+
|
|
1060
|
+
- __onSuccess__: (Optional) Called with `{erase, makeReadOnly, ntagPassword, platform}`.
|
|
1061
|
+
- __onFailure__: (Optional) The callback that is called if there was an error.
|
|
1062
|
+
|
|
1063
|
+
### Returns
|
|
1064
|
+
|
|
1065
|
+
- Promise<{erase: boolean, makeReadOnly: boolean, ntagPassword: boolean, platform: string}> when
|
|
1066
|
+
called without callbacks.
|
|
1067
|
+
|
|
1068
|
+
### Description
|
|
1069
|
+
|
|
1070
|
+
- `erase` / `makeReadOnly`: `true` on Android always, and on iOS from 13 onward; `false` on iOS
|
|
1071
|
+
below 13 and on any other platform.
|
|
1072
|
+
- `ntagPassword`: whether `ntagAuthenticate` / `ntagSetPassword` / `ntagRemovePassword` (and the
|
|
1073
|
+
underlying raw `transceive` they use) are expected to work. `true` on Android. **`false` on
|
|
1074
|
+
iOS**, even on 13+ - the `NFCMiFareTag` raw-command path those helpers need (PLU-255/PLU-258)
|
|
1075
|
+
hasn't been confirmed against a real NTAG213/215 yet. Re-check this value after that is proven
|
|
1076
|
+
on device; it may become conditional on iOS version at that point.
|
|
1077
|
+
- `platform`: `cordova.platformId`.
|
|
1078
|
+
|
|
1079
|
+
### Quick Example
|
|
1080
|
+
|
|
1081
|
+
const caps = await nfc.capabilities();
|
|
1082
|
+
if (caps.makeReadOnly) {
|
|
1083
|
+
showLockButton();
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
### Supported Platforms
|
|
1087
|
+
|
|
1088
|
+
- Android
|
|
1089
|
+
- iOS
|
|
1090
|
+
- any other platform (always reports every feature as unsupported)
|
|
1091
|
+
|
|
1012
1092
|
|
|
1013
1093
|
# Reader Mode Functions
|
|
1014
1094
|
|
|
@@ -1183,10 +1263,23 @@ Send raw command to the tag and receive the response.
|
|
|
1183
1263
|
|
|
1184
1264
|
### Description
|
|
1185
1265
|
|
|
1186
|
-
Function `transceive` sends raw commands to the tag and receives the response. `nfc.connect` must be called before calling `transceive`. Data passed to transceive can be a hex string representation of bytes or an ArrayBuffer. The response is returned as an ArrayBuffer in the promise.
|
|
1266
|
+
Function `transceive` sends raw commands to the tag and receives the response. On **Android**, `nfc.connect` must be called before calling `transceive`. Data passed to transceive can be a hex string representation of bytes or an ArrayBuffer. The response is returned as an ArrayBuffer in the promise.
|
|
1187
1267
|
|
|
1188
1268
|
See Android's documentation [IsoDep.transceive()](https://developer.android.com/reference/android/nfc/tech/IsoDep.html#transceive(byte[])), [NfcV.transceive()](https://developer.android.com/reference/android/nfc/tech/NfcV.html#transceive(byte[])), [MifareUltralight.transceive()](https://developer.android.com/reference/android/nfc/tech/MifareUltralight.html#transceive(byte[])) for more info.
|
|
1189
1269
|
|
|
1270
|
+
On **iOS** (13+), there is no `connect` - call `transceive` after `nfc.scanTag({keepSessionOpen: true})`
|
|
1271
|
+
resolves. The tag is dispatched automatically:
|
|
1272
|
+
|
|
1273
|
+
- An **ISO 7816** tag (since 1.7.0): the response has `sw1`/`sw2` appended, matching Android's
|
|
1274
|
+
`IsoDep.transceive`.
|
|
1275
|
+
- A **MIFARE** tag (v1.9.0, PLU-258), via `NFCMiFareTag.sendMiFareCommand` - this is what
|
|
1276
|
+
`ntagAuthenticate` / `ntagSetPassword` / `ntagRemovePassword` need on iOS. The response is the
|
|
1277
|
+
raw bytes the tag sent back, with **no** `sw1`/`sw2` appended. As an experiment, this path is
|
|
1278
|
+
also tried on the tag from an NDEF-session scan when it happens to conform to the MIFARE
|
|
1279
|
+
protocol, since CoreNFC doesn't document whether that can occur; this fallback is **unverified
|
|
1280
|
+
on real hardware**. Check `nfc.capabilities().ntagPassword` before relying on it - it is `false`
|
|
1281
|
+
by default on iOS until proven.
|
|
1282
|
+
|
|
1190
1283
|
### Parameters
|
|
1191
1284
|
|
|
1192
1285
|
- __data__: a string of hex data or an ArrayBuffer
|
|
@@ -1210,6 +1303,7 @@ See Android's documentation [IsoDep.transceive()](https://developer.android.com/
|
|
|
1210
1303
|
### Supported Platforms
|
|
1211
1304
|
|
|
1212
1305
|
- Android
|
|
1306
|
+
- iOS 13+ (ISO 7816 since 1.7.0; MIFARE since v1.9.0, PLU-258)
|
|
1213
1307
|
|
|
1214
1308
|
## nfc.close
|
|
1215
1309
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "community-cordova-plugin-nfc",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Community Cordova NFC Plugin - Read and write NDEF messages, advanced tag analysis, raw memory access",
|
|
5
5
|
"main": "./types/index.d.ts",
|
|
6
6
|
"types": "./types/index.d.ts",
|
package/plugin.xml
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
xmlns="http://apache.org/cordova/ns/plugins/1.0"
|
|
8
8
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
9
9
|
id="community-cordova-plugin-nfc"
|
|
10
|
-
version="1.
|
|
10
|
+
version="1.9.0">
|
|
11
11
|
|
|
12
12
|
<name>NFC</name>
|
|
13
13
|
<description>Community Cordova NFC Plugin - Read and write NDEF messages, advanced tag analysis, raw memory access</description>
|
package/src/ios/NfcPlugin.h
CHANGED
|
@@ -32,9 +32,17 @@
|
|
|
32
32
|
- (void)enabled:(CDVInvokedUrlCommand *)command;
|
|
33
33
|
- (void)writeTag:(CDVInvokedUrlCommand *)command;
|
|
34
34
|
|
|
35
|
-
//
|
|
35
|
+
// erase / makeReadOnly (added v1.9.0, PLU-257) - mirror Android nfc.erase / nfc.makeReadOnly
|
|
36
|
+
- (void)eraseTag:(CDVInvokedUrlCommand *)command;
|
|
37
|
+
- (void)makeReadOnly:(CDVInvokedUrlCommand *)command;
|
|
38
|
+
|
|
39
|
+
// ISO 7816 APDU / MIFARE raw command exchange (v1.7.0, extended v1.9.0 for MIFARE - PLU-258) -
|
|
40
|
+
// mirrors Android nfc.transceive
|
|
36
41
|
- (void)transceive:(CDVInvokedUrlCommand *)command;
|
|
37
42
|
|
|
43
|
+
// Feature detection (added v1.9.0, PLU-258)
|
|
44
|
+
- (void)capabilities:(CDVInvokedUrlCommand *)command;
|
|
45
|
+
|
|
38
46
|
// Internal implementation
|
|
39
47
|
- (void)channel:(CDVInvokedUrlCommand *)command;
|
|
40
48
|
|
package/src/ios/NfcPlugin.m
CHANGED
|
@@ -7,6 +7,15 @@
|
|
|
7
7
|
#import "NfcPlugin.h"
|
|
8
8
|
#import <CoreNFC/CoreNFC.h>
|
|
9
9
|
|
|
10
|
+
// What the current/next NDEF session is for. Replaces overloading self.messageToWrite (e.g. using
|
|
11
|
+
// its presence/content as a signal) to tell a read apart from a write/erase/lock - PLU-257.
|
|
12
|
+
typedef NS_ENUM(NSInteger, NfcSessionMode) {
|
|
13
|
+
NfcSessionModeRead = 0,
|
|
14
|
+
NfcSessionModeWrite,
|
|
15
|
+
NfcSessionModeErase,
|
|
16
|
+
NfcSessionModeLock
|
|
17
|
+
};
|
|
18
|
+
|
|
10
19
|
@interface NfcPlugin() {
|
|
11
20
|
NSString* sessionCallbackId;
|
|
12
21
|
NSString* channelCallbackId;
|
|
@@ -15,8 +24,11 @@
|
|
|
15
24
|
// Held when an ISO 7816 tag is connected and keepSessionOpen=YES so that
|
|
16
25
|
// subsequent JS calls to nfc.transceive() can exchange APDUs. (v1.7.0)
|
|
17
26
|
id<NFCISO7816Tag> connectedISO7816Tag API_AVAILABLE(ios(13.0));
|
|
27
|
+
// Held when a MIFARE/NTAG tag is connected and keepSessionOpen=YES so that nfc.transceive()
|
|
28
|
+
// can send raw MIFARE commands (NTAG password helpers, GET_VERSION, ...) - PLU-258.
|
|
29
|
+
id<NFCMiFareTag> connectedMiFareTag API_AVAILABLE(ios(13.0));
|
|
18
30
|
}
|
|
19
|
-
@property (nonatomic, assign)
|
|
31
|
+
@property (nonatomic, assign) NfcSessionMode sessionMode;
|
|
20
32
|
@property (nonatomic, assign) BOOL shouldUseTagReaderSession;
|
|
21
33
|
@property (nonatomic, assign) BOOL sendCallbackOnSessionStart;
|
|
22
34
|
@property (nonatomic, assign) BOOL returnTagInCallback;
|
|
@@ -106,8 +118,8 @@
|
|
|
106
118
|
|
|
107
119
|
- (void)writeTag:(CDVInvokedUrlCommand*)command API_AVAILABLE(ios(13.0)){
|
|
108
120
|
NSLog(@"writeTag");
|
|
109
|
-
|
|
110
|
-
self.
|
|
121
|
+
|
|
122
|
+
self.sessionMode = NfcSessionModeWrite;
|
|
111
123
|
self.shouldUseTagReaderSession = NO;
|
|
112
124
|
BOOL reusingSession = NO;
|
|
113
125
|
|
|
@@ -168,6 +180,88 @@
|
|
|
168
180
|
}
|
|
169
181
|
}
|
|
170
182
|
|
|
183
|
+
// nfc.erase() on iOS (PLU-257): writes a single TNF_EMPTY record, same as Android's eraseTag -
|
|
184
|
+
// see com.chariotsolutions.nfc.plugin.NfcPlugin#eraseTag. Mirrors writeTag:'s session reuse.
|
|
185
|
+
- (void)eraseTag:(CDVInvokedUrlCommand*)command {
|
|
186
|
+
NSLog(@"eraseTag");
|
|
187
|
+
|
|
188
|
+
if (@available(iOS 13.0, *)) {
|
|
189
|
+
// continues below
|
|
190
|
+
} else {
|
|
191
|
+
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self errorWithCode:@"NOT_SUPPORTED" message:@"erase requires iOS 13 or later."]];
|
|
192
|
+
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
self.sessionMode = NfcSessionModeErase;
|
|
197
|
+
self.shouldUseTagReaderSession = NO;
|
|
198
|
+
BOOL reusingSession = NO;
|
|
199
|
+
|
|
200
|
+
NFCNDEFPayload *emptyRecord = [[NFCNDEFPayload alloc] initWithFormat:NFCTypeNameFormatEmpty type:[NSData data] identifier:[NSData data] payload:[NSData data]];
|
|
201
|
+
self.messageToWrite = [[NFCNDEFMessage alloc] initWithNDEFRecords:@[emptyRecord]];
|
|
202
|
+
|
|
203
|
+
NSDictionary *options = [command argumentAtIndex:0];
|
|
204
|
+
|
|
205
|
+
if (self.nfcSession && self.nfcSession.isReady) {
|
|
206
|
+
reusingSession = YES;
|
|
207
|
+
} else {
|
|
208
|
+
self.nfcSession = [[NFCNDEFReaderSession alloc]initWithDelegate:self queue:dispatch_get_main_queue() invalidateAfterFirstRead:FALSE];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
NSString *alertMessage = [options isKindOfClass:[NSDictionary class]] ? [options objectForKey:@"alertMessage"] : nil;
|
|
212
|
+
self.nfcSession.alertMessage = ([alertMessage isKindOfClass:[NSString class]] && alertMessage.length > 0)
|
|
213
|
+
? alertMessage
|
|
214
|
+
: [self localizeString:@"NFCHoldNearWritableTag" defaultValue:@"Hold near writable NFC tag to update."];
|
|
215
|
+
sessionCallbackId = [command.callbackId copy];
|
|
216
|
+
|
|
217
|
+
if (reusingSession) {
|
|
218
|
+
self.keepSessionOpen = NO;
|
|
219
|
+
[self eraseNDEFTag:self.nfcSession status:connectedTagStatus tag:connectedTag];
|
|
220
|
+
} else {
|
|
221
|
+
[self.nfcSession beginSession];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// nfc.makeReadOnly() on iOS (PLU-257): permanently locks the tag via NFCNDEFTag.writeLock
|
|
226
|
+
// (iOS 13+). Already-read-only counts as success, same as Android returning without error when
|
|
227
|
+
// the tag is already locked.
|
|
228
|
+
- (void)makeReadOnly:(CDVInvokedUrlCommand*)command {
|
|
229
|
+
NSLog(@"makeReadOnly");
|
|
230
|
+
|
|
231
|
+
if (@available(iOS 13.0, *)) {
|
|
232
|
+
// continues below
|
|
233
|
+
} else {
|
|
234
|
+
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self errorWithCode:@"NOT_SUPPORTED" message:@"makeReadOnly requires iOS 13 or later."]];
|
|
235
|
+
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
self.sessionMode = NfcSessionModeLock;
|
|
240
|
+
self.shouldUseTagReaderSession = NO;
|
|
241
|
+
BOOL reusingSession = NO;
|
|
242
|
+
|
|
243
|
+
NSDictionary *options = [command argumentAtIndex:0];
|
|
244
|
+
|
|
245
|
+
if (self.nfcSession && self.nfcSession.isReady) {
|
|
246
|
+
reusingSession = YES;
|
|
247
|
+
} else {
|
|
248
|
+
self.nfcSession = [[NFCNDEFReaderSession alloc]initWithDelegate:self queue:dispatch_get_main_queue() invalidateAfterFirstRead:FALSE];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
NSString *alertMessage = [options isKindOfClass:[NSDictionary class]] ? [options objectForKey:@"alertMessage"] : nil;
|
|
252
|
+
self.nfcSession.alertMessage = ([alertMessage isKindOfClass:[NSString class]] && alertMessage.length > 0)
|
|
253
|
+
? alertMessage
|
|
254
|
+
: [self localizeString:@"NFCHoldNearWritableTag" defaultValue:@"Hold near writable NFC tag to update."];
|
|
255
|
+
sessionCallbackId = [command.callbackId copy];
|
|
256
|
+
|
|
257
|
+
if (reusingSession) {
|
|
258
|
+
self.keepSessionOpen = NO;
|
|
259
|
+
[self lockNDEFTag:self.nfcSession status:connectedTagStatus tag:connectedTag];
|
|
260
|
+
} else {
|
|
261
|
+
[self.nfcSession beginSession];
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
171
265
|
- (void)cancelScan:(CDVInvokedUrlCommand*)command API_AVAILABLE(ios(11.0)){
|
|
172
266
|
NSLog(@"cancelScan");
|
|
173
267
|
[self rejectPendingScan];
|
|
@@ -178,11 +272,35 @@
|
|
|
178
272
|
connectedTagStatus = NFCNDEFStatusNotSupported;
|
|
179
273
|
if (@available(iOS 13.0, *)) {
|
|
180
274
|
connectedISO7816Tag = NULL;
|
|
275
|
+
connectedMiFareTag = NULL;
|
|
181
276
|
}
|
|
182
277
|
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
|
|
183
278
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
|
184
279
|
}
|
|
185
280
|
|
|
281
|
+
// nfc.capabilities() (PLU-258): lets an app feature-detect before calling erase/makeReadOnly/the
|
|
282
|
+
// NTAG password helpers instead of trying and parsing the error.
|
|
283
|
+
- (void)capabilities:(CDVInvokedUrlCommand *)command {
|
|
284
|
+
NSLog(@"capabilities");
|
|
285
|
+
BOOL ios13OrLater = NO;
|
|
286
|
+
if (@available(iOS 13.0, *)) {
|
|
287
|
+
ios13OrLater = YES;
|
|
288
|
+
}
|
|
289
|
+
NSDictionary *result = @{
|
|
290
|
+
@"erase": @(ios13OrLater),
|
|
291
|
+
@"makeReadOnly": @(ios13OrLater),
|
|
292
|
+
// PLU-255: scanTag's NFCTagReaderSession never delivered didDetectTags on the owner's
|
|
293
|
+
// iPhone 17 Pro / iOS 26, so raw MIFARE transceive (rawTransceive, the NTAG password
|
|
294
|
+
// helpers) is unproven on iOS. Keep this false until a real device confirms the tag-reader
|
|
295
|
+
// path (or the NDEF-session experiment in -transceive:) actually delivers a MIFARE tag,
|
|
296
|
+
// then flip it to ios13OrLater here.
|
|
297
|
+
@"ntagPassword": @NO,
|
|
298
|
+
@"platform": @"ios"
|
|
299
|
+
};
|
|
300
|
+
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result];
|
|
301
|
+
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
|
302
|
+
}
|
|
303
|
+
|
|
186
304
|
- (void)invalidateSession:(CDVInvokedUrlCommand*)command {
|
|
187
305
|
NSLog(@"invalidateSession");
|
|
188
306
|
NSLog(@"WARNING: invalidateSession is deprecated. Use cancelScan.");
|
|
@@ -226,6 +344,11 @@
|
|
|
226
344
|
// Requires an ISO 7816-compatible tag detected via scanTag({keepSessionOpen:true}).
|
|
227
345
|
// Resolves with an ArrayBuffer containing the APDU response data with sw1/sw2
|
|
228
346
|
// appended (Android IsoDep semantics).
|
|
347
|
+
//
|
|
348
|
+
// PLU-258: also supports a MIFARE/NTAG tag via NFCMiFareTag.sendMiFareCommand, which is what the
|
|
349
|
+
// www rawTransceive/rawAuthenticate/rawSetPassword NTAG password helpers need. Unlike the ISO 7816
|
|
350
|
+
// branch, the raw response has no sw1/sw2 appended - it is exactly what the tag sent back, matching
|
|
351
|
+
// Android's raw NTAG command responses.
|
|
229
352
|
- (void)transceive:(CDVInvokedUrlCommand *)command {
|
|
230
353
|
NSLog(@"transceive");
|
|
231
354
|
|
|
@@ -236,12 +359,6 @@
|
|
|
236
359
|
return;
|
|
237
360
|
}
|
|
238
361
|
|
|
239
|
-
if (connectedISO7816Tag == nil) {
|
|
240
|
-
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self errorWithCode:@"NOT_CONNECTED" message:@"No ISO 7816 tag connected. Only ISO 7816-compatible tags support transceive on iOS."]];
|
|
241
|
-
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
362
|
// Cordova-iOS converts a JS ArrayBuffer argument into an NSData.
|
|
246
363
|
id arg = [command argumentAtIndex:0];
|
|
247
364
|
NSData *apduData = nil;
|
|
@@ -252,37 +369,75 @@
|
|
|
252
369
|
apduData = [self uint8ArrayToNSData:(NSArray *)arg];
|
|
253
370
|
}
|
|
254
371
|
|
|
255
|
-
|
|
256
|
-
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self errorWithCode:@"INVALID_ARGUMENT" message:@"Invalid APDU: expected at least 4 bytes (CLA INS P1 P2)."]];
|
|
257
|
-
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
372
|
+
NSString *callbackId = [command.callbackId copy];
|
|
260
373
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
374
|
+
if (connectedISO7816Tag != nil) {
|
|
375
|
+
if (apduData == nil || apduData.length < 4) {
|
|
376
|
+
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self errorWithCode:@"INVALID_ARGUMENT" message:@"Invalid APDU: expected at least 4 bytes (CLA INS P1 P2)."]];
|
|
377
|
+
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
NFCISO7816APDU *apdu = [[NFCISO7816APDU alloc] initWithData:apduData];
|
|
382
|
+
if (apdu == nil) {
|
|
383
|
+
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self errorWithCode:@"INVALID_ARGUMENT" message:@"Invalid APDU encoding."]];
|
|
384
|
+
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
[connectedISO7816Tag sendCommandAPDU:apdu completionHandler:^(NSData * _Nonnull responseData, uint8_t sw1, uint8_t sw2, NSError * _Nullable error) {
|
|
389
|
+
if (error) {
|
|
390
|
+
NSLog(@"transceive error: %@", error);
|
|
391
|
+
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self errorFromNSError:error]];
|
|
392
|
+
[self.commandDelegate sendPluginResult:pluginResult callbackId:callbackId];
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Match Android IsoDep.transceive: response data with sw1/sw2 appended.
|
|
397
|
+
NSMutableData *full = [NSMutableData dataWithData:responseData ?: [NSData data]];
|
|
398
|
+
[full appendBytes:&sw1 length:1];
|
|
399
|
+
[full appendBytes:&sw2 length:1];
|
|
400
|
+
|
|
401
|
+
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArrayBuffer:full];
|
|
402
|
+
[self.commandDelegate sendPluginResult:pluginResult callbackId:callbackId];
|
|
403
|
+
}];
|
|
265
404
|
return;
|
|
266
405
|
}
|
|
267
406
|
|
|
268
|
-
|
|
407
|
+
id<NFCMiFareTag> mifareTag = connectedMiFareTag;
|
|
408
|
+
if (mifareTag == nil && self->connectedTag != nil && [self->connectedTag conformsToProtocol:@protocol(NFCMiFareTag)]) {
|
|
409
|
+
// Experiment (PLU-258): CoreNFC doesn't document the NDEF-session tag (from
|
|
410
|
+
// scanNdef/scanTag without the ISO7816 branch below) as also conforming to
|
|
411
|
+
// NFCMiFareTag, but a concrete tag can implement more than one CoreNFC protocol at
|
|
412
|
+
// once, so this runtime check is free to try and may let rawTransceive work on a tag
|
|
413
|
+
// read via scanNdef({keepSessionOpen:true}) even when the dedicated tag-reader-session
|
|
414
|
+
// path (PLU-255) doesn't fire. Unverified on a real device.
|
|
415
|
+
mifareTag = (id<NFCMiFareTag>)self->connectedTag;
|
|
416
|
+
}
|
|
269
417
|
|
|
270
|
-
|
|
271
|
-
if (
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
[self.commandDelegate sendPluginResult:pluginResult callbackId:callbackId];
|
|
418
|
+
if (mifareTag != nil) {
|
|
419
|
+
if (apduData == nil || apduData.length == 0) {
|
|
420
|
+
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self errorWithCode:@"INVALID_ARGUMENT" message:@"Invalid MIFARE command: expected at least 1 byte."]];
|
|
421
|
+
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
|
275
422
|
return;
|
|
276
423
|
}
|
|
277
424
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
425
|
+
[mifareTag sendMiFareCommand:apduData completionHandler:^(NSData * _Nonnull responseData, NSError * _Nullable error) {
|
|
426
|
+
if (error) {
|
|
427
|
+
NSLog(@"transceive (MIFARE) error: %@", error);
|
|
428
|
+
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self errorFromNSError:error]];
|
|
429
|
+
[self.commandDelegate sendPluginResult:pluginResult callbackId:callbackId];
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
282
432
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
433
|
+
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArrayBuffer:responseData ?: [NSData data]];
|
|
434
|
+
[self.commandDelegate sendPluginResult:pluginResult callbackId:callbackId];
|
|
435
|
+
}];
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self errorWithCode:@"NOT_CONNECTED" message:@"No ISO 7816 or MIFARE tag connected. Only ISO 7816-compatible and MIFARE/NTAG tags support transceive on iOS."]];
|
|
440
|
+
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
|
286
441
|
} else {
|
|
287
442
|
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:[self errorWithCode:@"NOT_SUPPORTED" message:@"transceive requires iOS 13 or later."]];
|
|
288
443
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
|
@@ -383,6 +538,15 @@
|
|
|
383
538
|
return;
|
|
384
539
|
}
|
|
385
540
|
|
|
541
|
+
// MIFARE/NTAG + keepSessionOpen (PLU-258): hold the tag for nfc.transceive() raw MIFARE
|
|
542
|
+
// commands (the NTAG password helpers), same shape as the ISO 7816 branch above.
|
|
543
|
+
if (tag.type == NFCTagTypeMiFare && self.keepSessionOpen) {
|
|
544
|
+
self->connectedMiFareTag = [tag asNFCMiFareTag];
|
|
545
|
+
session.alertMessage = [self localizeString:@"NFCTagRead" defaultValue:@"Tag successfully read."];
|
|
546
|
+
[self fireTagEvent:tagMetaData];
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
|
|
386
550
|
[self processNDEFTag:session tag:ndefTag metaData:tagMetaData];
|
|
387
551
|
}];
|
|
388
552
|
}
|
|
@@ -420,6 +584,7 @@
|
|
|
420
584
|
connectedTagStatus = NFCNDEFStatusNotSupported;
|
|
421
585
|
if (@available(iOS 13.0, *)) {
|
|
422
586
|
connectedISO7816Tag = NULL;
|
|
587
|
+
connectedMiFareTag = NULL;
|
|
423
588
|
}
|
|
424
589
|
if (old.isReady) {
|
|
425
590
|
[old invalidateSession];
|
|
@@ -527,8 +692,8 @@
|
|
|
527
692
|
|
|
528
693
|
- (void)beginScanSession:(CDVInvokedUrlCommand*)command {
|
|
529
694
|
|
|
530
|
-
self.
|
|
531
|
-
|
|
695
|
+
self.sessionMode = NfcSessionModeRead;
|
|
696
|
+
|
|
532
697
|
NSLog(@"shouldUseTagReaderSession %d", self.shouldUseTagReaderSession);
|
|
533
698
|
NSLog(@"callbackOnSessionStart %d", self.sendCallbackOnSessionStart);
|
|
534
699
|
NSLog(@"returnTagInCallback %d", self.returnTagInCallback);
|
|
@@ -584,15 +749,25 @@
|
|
|
584
749
|
return;
|
|
585
750
|
}
|
|
586
751
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
self
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
752
|
+
switch (self.sessionMode) {
|
|
753
|
+
case NfcSessionModeWrite:
|
|
754
|
+
[self writeNDEFTag:session status:status tag:tag];
|
|
755
|
+
break;
|
|
756
|
+
case NfcSessionModeErase:
|
|
757
|
+
[self eraseNDEFTag:session status:status tag:tag];
|
|
758
|
+
break;
|
|
759
|
+
case NfcSessionModeLock:
|
|
760
|
+
[self lockNDEFTag:session status:status tag:tag];
|
|
761
|
+
break;
|
|
762
|
+
case NfcSessionModeRead:
|
|
763
|
+
default:
|
|
764
|
+
// save tag & status so we can re-use in write
|
|
765
|
+
if (self.keepSessionOpen) {
|
|
766
|
+
self->connectedTagStatus = status;
|
|
767
|
+
self->connectedTag = tag;
|
|
768
|
+
}
|
|
769
|
+
[self readNDEFTag:session status:status tag:tag metaData:metaData];
|
|
770
|
+
break;
|
|
596
771
|
}
|
|
597
772
|
|
|
598
773
|
}];
|
|
@@ -660,6 +835,79 @@
|
|
|
660
835
|
}
|
|
661
836
|
}
|
|
662
837
|
|
|
838
|
+
// nfc.erase() (PLU-257): same write path as writeNDEFTag, but self.messageToWrite is the single
|
|
839
|
+
// TNF_EMPTY record built in -eraseTag:, and NOT_SUPPORTED (not NOT_NDEF) covers a tag that
|
|
840
|
+
// doesn't support NDEF at all - matching Android's eraseTag error code.
|
|
841
|
+
- (void)eraseNDEFTag:(NFCReaderSession * _Nonnull)session status:(NFCNDEFStatus)status tag:(id<NFCNDEFTag>)tag API_AVAILABLE(ios(13.0)) {
|
|
842
|
+
switch (status) {
|
|
843
|
+
case NFCNDEFStatusNotSupported:
|
|
844
|
+
[self closeSession:session withErrorCode:@"NOT_SUPPORTED" message:[self localizeString:@"NFCNotNdefCompliant" defaultValue:@"Tag is not NDEF compliant."]];
|
|
845
|
+
break;
|
|
846
|
+
case NFCNDEFStatusReadOnly:
|
|
847
|
+
[self closeSession:session withErrorCode:@"READ_ONLY" message:[self localizeString:@"NFCReadOnlyTag" defaultValue:@"Tag is read only."]];
|
|
848
|
+
break;
|
|
849
|
+
case NFCNDEFStatusReadWrite: {
|
|
850
|
+
|
|
851
|
+
[tag writeNDEF: self.messageToWrite completionHandler:^(NSError * _Nullable error) {
|
|
852
|
+
if (error) {
|
|
853
|
+
NSLog(@"%@", error);
|
|
854
|
+
[self closeSession:session withErrorCode:@"IO_ERROR" message:[self localizeString:@"NFCDataWriteFailed" defaultValue:@"Write failed."]];
|
|
855
|
+
} else {
|
|
856
|
+
session.alertMessage = [self localizeString:@"NFCDataWrote" defaultValue:@"Wrote data to NFC tag."];
|
|
857
|
+
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
|
|
858
|
+
[self.commandDelegate sendPluginResult:pluginResult callbackId:self->sessionCallbackId];
|
|
859
|
+
[self closeSession:session];
|
|
860
|
+
}
|
|
861
|
+
}];
|
|
862
|
+
break;
|
|
863
|
+
|
|
864
|
+
}
|
|
865
|
+
default:
|
|
866
|
+
[self closeSession:session withErrorCode:@"UNKNOWN" message:[self localizeString:@"NFCUnknownNdefTag" defaultValue:@"Unknown NDEF tag status."]];
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// nfc.makeReadOnly() (PLU-257): NFCNDEFTag.writeLockWithCompletionHandler (iOS 13+) permanently
|
|
871
|
+
// locks the tag. A tag that is already read-only is reported as success with alreadyLocked:true,
|
|
872
|
+
// same as Android returning without error when the tag can't be locked again.
|
|
873
|
+
- (void)lockNDEFTag:(NFCReaderSession * _Nonnull)session status:(NFCNDEFStatus)status tag:(id<NFCNDEFTag>)tag API_AVAILABLE(ios(13.0)) {
|
|
874
|
+
switch (status) {
|
|
875
|
+
case NFCNDEFStatusNotSupported:
|
|
876
|
+
[self closeSession:session withErrorCode:@"NOT_SUPPORTED" message:[self localizeString:@"NFCNotNdefCompliant" defaultValue:@"Tag is not NDEF compliant."]];
|
|
877
|
+
break;
|
|
878
|
+
case NFCNDEFStatusReadOnly: {
|
|
879
|
+
session.alertMessage = [self localizeString:@"NFCTagAlreadyReadOnly" defaultValue:@"Tag is already read only."];
|
|
880
|
+
NSDictionary *result = @{ @"alreadyLocked": @YES };
|
|
881
|
+
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result];
|
|
882
|
+
[self.commandDelegate sendPluginResult:pluginResult callbackId:self->sessionCallbackId];
|
|
883
|
+
[self closeSession:session];
|
|
884
|
+
break;
|
|
885
|
+
}
|
|
886
|
+
case NFCNDEFStatusReadWrite: {
|
|
887
|
+
if (![tag respondsToSelector:@selector(writeLockWithCompletionHandler:)]) {
|
|
888
|
+
[self closeSession:session withErrorCode:@"NOT_SUPPORTED" message:[self localizeString:@"NFCLockNotSupported" defaultValue:@"This tag does not support being locked."]];
|
|
889
|
+
break;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
[tag writeLockWithCompletionHandler:^(NSError * _Nullable error) {
|
|
893
|
+
if (error) {
|
|
894
|
+
NSLog(@"%@", error);
|
|
895
|
+
[self closeSession:session withErrorCode:@"IO_ERROR" message:[self localizeString:@"NFCLockFailed" defaultValue:@"Failed to lock tag."]];
|
|
896
|
+
} else {
|
|
897
|
+
session.alertMessage = [self localizeString:@"NFCTagLocked" defaultValue:@"Tag is now read only."];
|
|
898
|
+
NSDictionary *result = @{ @"alreadyLocked": @NO };
|
|
899
|
+
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result];
|
|
900
|
+
[self.commandDelegate sendPluginResult:pluginResult callbackId:self->sessionCallbackId];
|
|
901
|
+
[self closeSession:session];
|
|
902
|
+
}
|
|
903
|
+
}];
|
|
904
|
+
break;
|
|
905
|
+
}
|
|
906
|
+
default:
|
|
907
|
+
[self closeSession:session withErrorCode:@"UNKNOWN" message:[self localizeString:@"NFCUnknownNdefTag" defaultValue:@"Unknown NDEF tag status."]];
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
663
911
|
#pragma mark - Tag Reader Helper Functions
|
|
664
912
|
|
|
665
913
|
// Gets the tag meta data - type and uid
|
|
@@ -771,6 +1019,7 @@
|
|
|
771
1019
|
connectedTagStatus = NFCNDEFStatusNotSupported;
|
|
772
1020
|
if (@available(iOS 13.0, *)) {
|
|
773
1021
|
connectedISO7816Tag = NULL;
|
|
1022
|
+
connectedMiFareTag = NULL;
|
|
774
1023
|
}
|
|
775
1024
|
[self markSessionClosing:session];
|
|
776
1025
|
[session invalidateSession];
|
|
@@ -796,6 +1045,7 @@
|
|
|
796
1045
|
connectedTagStatus = NFCNDEFStatusNotSupported;
|
|
797
1046
|
if (@available(iOS 13.0, *)) {
|
|
798
1047
|
connectedISO7816Tag = NULL;
|
|
1048
|
+
connectedMiFareTag = NULL;
|
|
799
1049
|
}
|
|
800
1050
|
|
|
801
1051
|
[self markSessionClosing:session];
|
package/types/index.d.ts
CHANGED
|
@@ -275,6 +275,28 @@ export interface IWriteOptions {
|
|
|
275
275
|
[key: string]: unknown;
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
+
/** Options for erase() / makeReadOnly() on iOS (added v1.9.0, PLU-257) */
|
|
279
|
+
export interface IEraseOptions {
|
|
280
|
+
[key: string]: unknown;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** Result of nfc.capabilities() (added v1.9.0, PLU-258) */
|
|
284
|
+
export interface INfcCapabilities {
|
|
285
|
+
/** nfc.erase() is implemented on this platform */
|
|
286
|
+
erase: boolean;
|
|
287
|
+
/** nfc.makeReadOnly() is implemented on this platform */
|
|
288
|
+
makeReadOnly: boolean;
|
|
289
|
+
/**
|
|
290
|
+
* NTAG password set/remove is implemented on this platform. Always true on Android
|
|
291
|
+
* (ntagSetPassword/ntagRemovePassword). False on iOS by default (PLU-255): the
|
|
292
|
+
* NFCMiFareTag raw-command path transceive() uses for the password commands is
|
|
293
|
+
* unverified against real NTAG213/215 hardware.
|
|
294
|
+
*/
|
|
295
|
+
ntagPassword: boolean;
|
|
296
|
+
/** cordova.platformId */
|
|
297
|
+
platform: string;
|
|
298
|
+
}
|
|
299
|
+
|
|
278
300
|
/** Callback-style success/failure pair */
|
|
279
301
|
export type SuccessCallback<T = unknown> = (result?: T) => void;
|
|
280
302
|
export type FailureCallback = (error: NfcFailure) => void;
|
|
@@ -325,13 +347,19 @@ export interface INfcPlugin {
|
|
|
325
347
|
write(ndefMessage: INdefRecord[]): Promise<void>;
|
|
326
348
|
write(ndefMessage: INdefRecord[], win?: SuccessCallback, fail?: FailureCallback, options?: IWriteOptions): void;
|
|
327
349
|
|
|
328
|
-
/**
|
|
350
|
+
/**
|
|
351
|
+
* Make the last scanned tag read-only (permanent!) - Android, and iOS 13+ after
|
|
352
|
+
* scanNdef/scanTag({keepSessionOpen: true}) (v1.9.0, PLU-257; options only used on iOS).
|
|
353
|
+
*/
|
|
329
354
|
makeReadOnly(): Promise<void>;
|
|
330
|
-
makeReadOnly(win?: SuccessCallback, fail?: FailureCallback): void;
|
|
355
|
+
makeReadOnly(win?: SuccessCallback, fail?: FailureCallback, options?: IEraseOptions): void;
|
|
331
356
|
|
|
332
|
-
/**
|
|
357
|
+
/**
|
|
358
|
+
* Erase the last scanned tag (writes an empty NDEF record) - Android, and iOS 13+ after
|
|
359
|
+
* scanNdef/scanTag({keepSessionOpen: true}) (v1.9.0, PLU-257; options only used on iOS).
|
|
360
|
+
*/
|
|
333
361
|
erase(): Promise<void>;
|
|
334
|
-
erase(win?: SuccessCallback, fail?: FailureCallback): void;
|
|
362
|
+
erase(win?: SuccessCallback, fail?: FailureCallback, options?: IEraseOptions): void;
|
|
335
363
|
|
|
336
364
|
/** Android Beam was removed in Android 10: always fails with NOT_SUPPORTED */
|
|
337
365
|
share(ndefMessage: INdefRecord[]): Promise<void>;
|
|
@@ -386,9 +414,26 @@ export interface INfcPlugin {
|
|
|
386
414
|
/** Close the connect() session */
|
|
387
415
|
close(): Promise<void>;
|
|
388
416
|
|
|
389
|
-
/**
|
|
417
|
+
/**
|
|
418
|
+
* Send a raw command. Android: any connected tech. iOS: after scanTag({keepSessionOpen: true}),
|
|
419
|
+
* ISO 7816 APDUs against an ISO7816 tag, or (v1.9.0, PLU-258) MIFARE commands against a tag
|
|
420
|
+
* that reports as MIFARE. As an experiment covering tags the reader session classifies some
|
|
421
|
+
* other way, it also tries the MIFARE path, unverified on real hardware, whenever the connected
|
|
422
|
+
* tag happens to conform to NFCMiFareTag. Unlike the ISO 7816 path, the MIFARE response does
|
|
423
|
+
* NOT have SW1/SW2 appended.
|
|
424
|
+
*/
|
|
390
425
|
transceive(command: ArrayBuffer | ArrayBufferView | number[] | string): Promise<ArrayBuffer>;
|
|
391
426
|
|
|
427
|
+
// ========== Feature detection ==========
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Report which optional features this platform actually implements (added v1.9.0, PLU-258).
|
|
431
|
+
* Use this instead of `cordova.platformId` checks to decide whether to offer erase,
|
|
432
|
+
* makeReadOnly or NTAG password UI.
|
|
433
|
+
*/
|
|
434
|
+
capabilities(): Promise<INfcCapabilities>;
|
|
435
|
+
capabilities(win?: SuccessCallback<INfcCapabilities>, fail?: FailureCallback): void;
|
|
436
|
+
|
|
392
437
|
// ========== iOS error codes ==========
|
|
393
438
|
|
|
394
439
|
/** NFCReaderError codes carried by iOS reader-session errors (with nfc.useErrorObjects(true)) */
|
package/www/phonegap-nfc.js
CHANGED
|
@@ -170,6 +170,20 @@ function execOrPromise(win, fail, action, args) {
|
|
|
170
170
|
nfcExec(win, fail, action, args);
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
// Same callback-or-Promise duality as execOrPromise(), but for an API that answers from a plain
|
|
174
|
+
// JS value instead of a native exec call (capabilities() on Android / unsupported platforms).
|
|
175
|
+
function resolveOrCall(win, fail, value) {
|
|
176
|
+
if (typeof win === 'function') {
|
|
177
|
+
win(value);
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
// [PLU-257 round-1] No success callback was given (win is not a function) - whether or not a
|
|
181
|
+
// `fail` was passed, there is no failure channel for a synthesized answer like this one, so a
|
|
182
|
+
// fail-only call (win undefined, fail a function) must still behave like execOrPromise and
|
|
183
|
+
// hand back a Promise rather than silently dropping `value`.
|
|
184
|
+
return Promise.resolve(value);
|
|
185
|
+
}
|
|
186
|
+
|
|
173
187
|
var ndef = {
|
|
174
188
|
|
|
175
189
|
// see android.nfc.NdefRecord for documentation about constants
|
|
@@ -595,7 +609,10 @@ var nfc = {
|
|
|
595
609
|
return execOrPromise(win, fail, "writeTag", [ndefMessage]);
|
|
596
610
|
},
|
|
597
611
|
|
|
598
|
-
makeReadOnly: function (win, fail) {
|
|
612
|
+
makeReadOnly: function (win, fail, options) {
|
|
613
|
+
if (cordova.platformId === "ios") {
|
|
614
|
+
return execOrPromise(win, fail, "makeReadOnly", [options]);
|
|
615
|
+
}
|
|
599
616
|
return execOrPromise(win, fail, "makeReadOnly", []);
|
|
600
617
|
},
|
|
601
618
|
|
|
@@ -619,10 +636,38 @@ var nfc = {
|
|
|
619
636
|
return execOrPromise(win, fail, "stopHandover", []);
|
|
620
637
|
},
|
|
621
638
|
|
|
622
|
-
erase: function (win, fail) {
|
|
639
|
+
erase: function (win, fail, options) {
|
|
640
|
+
if (cordova.platformId === "ios") {
|
|
641
|
+
return execOrPromise(win, fail, "eraseTag", [options]);
|
|
642
|
+
}
|
|
623
643
|
return execOrPromise(win, fail, "eraseTag", [[]]);
|
|
624
644
|
},
|
|
625
645
|
|
|
646
|
+
// Feature detection (added v1.9.0, PLU-258). Android implements erase, makeReadOnly and the
|
|
647
|
+
// NTAG password helpers (ntagAuthenticate/ntagSetPassword/ntagRemovePassword) natively, so
|
|
648
|
+
// those report true unconditionally there. iOS reports its own support (erase/makeReadOnly
|
|
649
|
+
// need iOS 13+); ntagPassword is hardcoded false on iOS - the NFCMiFareTag raw-command path
|
|
650
|
+
// used for NTAG password commands (PLU-255) is unverified against real NTAG213/215 hardware.
|
|
651
|
+
capabilities: function (win, fail) {
|
|
652
|
+
if (cordova.platformId === "android") {
|
|
653
|
+
return resolveOrCall(win, fail, {
|
|
654
|
+
erase: true,
|
|
655
|
+
makeReadOnly: true,
|
|
656
|
+
ntagPassword: true,
|
|
657
|
+
platform: "android"
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
if (cordova.platformId === "ios") {
|
|
661
|
+
return execOrPromise(win, fail, "capabilities", []);
|
|
662
|
+
}
|
|
663
|
+
return resolveOrCall(win, fail, {
|
|
664
|
+
erase: false,
|
|
665
|
+
makeReadOnly: false,
|
|
666
|
+
ntagPassword: false,
|
|
667
|
+
platform: cordova.platformId
|
|
668
|
+
});
|
|
669
|
+
},
|
|
670
|
+
|
|
626
671
|
enabled: function (win, fail) {
|
|
627
672
|
return execOrPromise(win, fail, "enabled", [[]]);
|
|
628
673
|
},
|