@capgo/capacitor-nfc 8.0.9 → 8.0.11
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/README.md +38 -5
- package/dist/docs.json +7 -0
- package/dist/esm/definitions.d.ts +8 -0
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/NfcPlugin/NfcPlugin.swift +230 -45
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -44,6 +44,8 @@ Remember to add the required platform configuration:
|
|
|
44
44
|
|
|
45
45
|
## Usage
|
|
46
46
|
|
|
47
|
+
### Reading NDEF tags (default behavior)
|
|
48
|
+
|
|
47
49
|
```ts
|
|
48
50
|
import { CapacitorNfc } from '@capgo/capacitor-nfc';
|
|
49
51
|
|
|
@@ -79,6 +81,36 @@ await listener.remove();
|
|
|
79
81
|
await CapacitorNfc.stopScanning();
|
|
80
82
|
```
|
|
81
83
|
|
|
84
|
+
### Reading raw tags (iOS) - Get UID from unformatted tags
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { CapacitorNfc } from '@capgo/capacitor-nfc';
|
|
88
|
+
|
|
89
|
+
// Use 'tag' session type to read raw (non-NDEF) tags
|
|
90
|
+
await CapacitorNfc.startScanning({
|
|
91
|
+
iosSessionType: 'tag', // Enable raw tag reading on iOS
|
|
92
|
+
alertMessage: 'Hold your card near the device',
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const listener = await CapacitorNfc.addListener('nfcEvent', (event) => {
|
|
96
|
+
console.info('Tag detected:', event.type); // 'tag' or 'ndef'
|
|
97
|
+
|
|
98
|
+
// Read the UID (identifier) - works for both NDEF and raw tags
|
|
99
|
+
if (event.tag?.id) {
|
|
100
|
+
const uid = event.tag.id.map(byte => byte.toString(16).padStart(2, '0').toUpperCase()).join(':');
|
|
101
|
+
console.info('Tag UID:', uid); // e.g., "04:A1:B2:C3:D4:E5:F6"
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// If the tag has NDEF data, it will also be available
|
|
105
|
+
if (event.tag?.ndefMessage) {
|
|
106
|
+
console.info('NDEF records:', event.tag.ndefMessage);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await listener.remove();
|
|
111
|
+
await CapacitorNfc.stopScanning();
|
|
112
|
+
```
|
|
113
|
+
|
|
82
114
|
## API
|
|
83
115
|
|
|
84
116
|
<docgen-index>
|
|
@@ -290,11 +322,12 @@ addListener(eventName: 'nfcStateChange', listenerFunc: (event: NfcStateChangeEve
|
|
|
290
322
|
|
|
291
323
|
Options controlling the behaviour of {@link CapacitorNfcPlugin.startScanning}.
|
|
292
324
|
|
|
293
|
-
| Prop | Type
|
|
294
|
-
| ------------------------------ |
|
|
295
|
-
| **`invalidateAfterFirstRead`** | <code>boolean</code>
|
|
296
|
-
| **`alertMessage`** | <code>string</code>
|
|
297
|
-
| **`
|
|
325
|
+
| Prop | Type | Description |
|
|
326
|
+
| ------------------------------ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
327
|
+
| **`invalidateAfterFirstRead`** | <code>boolean</code> | iOS-only: closes the NFC session automatically after the first successful tag read. Defaults to `true`. |
|
|
328
|
+
| **`alertMessage`** | <code>string</code> | iOS-only: custom message displayed in the NFC system sheet while scanning. |
|
|
329
|
+
| **`iosSessionType`** | <code>'tag' \| 'ndef'</code> | iOS-only: session type to use for NFC scanning. - `'ndef'`: Uses NFCNDEFReaderSession (default). Only detects NDEF-formatted tags. - `'tag'`: Uses NFCTagReaderSession. Detects both NDEF and non-NDEF tags (e.g., raw MIFARE tags). Allows reading UID from unformatted tags. Defaults to `'ndef'` for backward compatibility. |
|
|
330
|
+
| **`androidReaderModeFlags`** | <code>number</code> | Android-only: raw flags passed to `NfcAdapter.enableReaderMode`. Defaults to enabling all tag types with skipping NDEF checks. |
|
|
298
331
|
|
|
299
332
|
|
|
300
333
|
#### WriteTagOptions
|
package/dist/docs.json
CHANGED
|
@@ -228,6 +228,13 @@
|
|
|
228
228
|
"complexTypes": [],
|
|
229
229
|
"type": "string | undefined"
|
|
230
230
|
},
|
|
231
|
+
{
|
|
232
|
+
"name": "iosSessionType",
|
|
233
|
+
"tags": [],
|
|
234
|
+
"docs": "iOS-only: session type to use for NFC scanning.\n- `'ndef'`: Uses NFCNDEFReaderSession (default). Only detects NDEF-formatted tags.\n- `'tag'`: Uses NFCTagReaderSession. Detects both NDEF and non-NDEF tags (e.g., raw MIFARE tags).\n Allows reading UID from unformatted tags.\nDefaults to `'ndef'` for backward compatibility.",
|
|
235
|
+
"complexTypes": [],
|
|
236
|
+
"type": "'tag' | 'ndef' | undefined"
|
|
237
|
+
},
|
|
231
238
|
{
|
|
232
239
|
"name": "androidReaderModeFlags",
|
|
233
240
|
"tags": [],
|
|
@@ -97,6 +97,14 @@ export interface StartScanningOptions {
|
|
|
97
97
|
* iOS-only: custom message displayed in the NFC system sheet while scanning.
|
|
98
98
|
*/
|
|
99
99
|
alertMessage?: string;
|
|
100
|
+
/**
|
|
101
|
+
* iOS-only: session type to use for NFC scanning.
|
|
102
|
+
* - `'ndef'`: Uses NFCNDEFReaderSession (default). Only detects NDEF-formatted tags.
|
|
103
|
+
* - `'tag'`: Uses NFCTagReaderSession. Detects both NDEF and non-NDEF tags (e.g., raw MIFARE tags).
|
|
104
|
+
* Allows reading UID from unformatted tags.
|
|
105
|
+
* Defaults to `'ndef'` for backward compatibility.
|
|
106
|
+
*/
|
|
107
|
+
iosSessionType?: 'ndef' | 'tag';
|
|
100
108
|
/**
|
|
101
109
|
* Android-only: raw flags passed to `NfcAdapter.enableReaderMode`.
|
|
102
110
|
* Defaults to enabling all tag types with skipping NDEF checks.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\n/**\n * Possible NFC adapter states returned by {@link CapacitorNfcPlugin.getStatus}.\n *\n * Matches the constants provided by the original PhoneGap NFC plugin for\n * compatibility with existing applications.\n */\nexport type NfcStatus = 'NFC_OK' | 'NO_NFC' | 'NFC_DISABLED' | 'NDEF_PUSH_DISABLED';\n\n/**\n * Event type describing the kind of NFC discovery that happened.\n *\n * - `tag`: A generic NFC tag (no NDEF payload).\n * - `ndef`: A tag exposing an NDEF payload.\n * - `ndef-mime`: An NDEF tag that matched one of the MIME type filters.\n * - `ndef-formatable`: A tag that can be formatted to NDEF.\n */\nexport type NfcEventType = 'tag' | 'ndef' | 'ndef-mime' | 'ndef-formatable';\n\n/**\n * JSON structure representing a single NDEF record.\n *\n * Mirrors the data format returned by the legacy Cordova implementation and\n * uses integer arrays instead of strings to preserve the original payload\n * bytes.\n */\nexport interface NdefRecord {\n /**\n * Type Name Format identifier.\n */\n tnf: number;\n /**\n * Type field expressed as an array of byte values.\n */\n type: number[];\n /**\n * Record identifier expressed as an array of byte values.\n */\n id: number[];\n /**\n * Raw payload expressed as an array of byte values.\n */\n payload: number[];\n}\n\n/**\n * Representation of the full tag information returned by the native layers.\n *\n * Supports standard NFC Forum tags as well as MIFARE Ultralight cards (including\n * EV1 and NTAG variants). NDEF data is automatically extracted from MIFARE Ultralight\n * tags when available.\n */\nexport interface NfcTag {\n /**\n * Raw identifier bytes for the tag.\n */\n id?: number[];\n /**\n * List of Android tech strings (e.g. `android.nfc.tech.Ndef`).\n */\n techTypes?: string[];\n /**\n * Human readable tag type when available (e.g. `NFC Forum Type 2`, `MIFARE Ultralight`).\n */\n type?: string | null;\n /**\n * Maximum writable size in bytes for tags that expose NDEF information.\n */\n maxSize?: number | null;\n /**\n * Indicates whether the tag can be written to.\n */\n isWritable?: boolean | null;\n /**\n * Indicates whether the tag can be permanently locked.\n */\n canMakeReadOnly?: boolean | null;\n /**\n * Array of NDEF records discovered on the tag.\n */\n ndefMessage?: NdefRecord[] | null;\n}\n\n/**\n * Generic NFC discovery event dispatched by the plugin.\n */\nexport interface NfcEvent {\n type: NfcEventType;\n tag: NfcTag;\n}\n\n/**\n * Options controlling the behaviour of {@link CapacitorNfcPlugin.startScanning}.\n */\nexport interface StartScanningOptions {\n /**\n * iOS-only: closes the NFC session automatically after the first successful tag read.\n * Defaults to `true`.\n */\n invalidateAfterFirstRead?: boolean;\n /**\n * iOS-only: custom message displayed in the NFC system sheet while scanning.\n */\n alertMessage?: string;\n /**\n * Android-only: raw flags passed to `NfcAdapter.enableReaderMode`.\n * Defaults to enabling all tag types with skipping NDEF checks.\n */\n androidReaderModeFlags?: number;\n}\n\n/**\n * Options used when writing an NDEF message on the current tag.\n */\nexport interface WriteTagOptions {\n /**\n * Array of records that compose the NDEF message to be written.\n */\n records: NdefRecord[];\n /**\n * When `true`, the plugin attempts to format NDEF-formattable tags before writing.\n * Defaults to `true`.\n */\n allowFormat?: boolean;\n}\n\n/**\n * Options used when sharing an NDEF message with another device using Android Beam / P2P mode.\n */\nexport interface ShareTagOptions {\n records: NdefRecord[];\n}\n\n/**\n * Event emitted whenever the NFC adapter availability changes.\n */\nexport interface NfcStateChangeEvent {\n status: NfcStatus;\n enabled: boolean;\n}\n\n/**\n * Public API surface for the Capacitor NFC plugin.\n *\n * The interface intentionally mirrors the behaviour of the reference PhoneGap\n * implementation to ease migration while embracing idiomatic Capacitor APIs.\n */\nexport interface CapacitorNfcPlugin {\n /**\n * Starts listening for NFC tags.\n */\n startScanning(options?: StartScanningOptions): Promise<void>;\n /**\n * Stops the ongoing NFC scanning session.\n */\n stopScanning(): Promise<void>;\n /**\n * Writes the provided NDEF records to the last discovered tag.\n */\n write(options: WriteTagOptions): Promise<void>;\n /**\n * Attempts to erase the last discovered tag by writing an empty NDEF message.\n */\n erase(): Promise<void>;\n /**\n * Attempts to make the last discovered tag read-only.\n */\n makeReadOnly(): Promise<void>;\n /**\n * Shares an NDEF message with another device via peer-to-peer (Android only).\n */\n share(options: ShareTagOptions): Promise<void>;\n /**\n * Stops sharing previously provided NDEF message (Android only).\n */\n unshare(): Promise<void>;\n /**\n * Returns the current NFC adapter status.\n */\n getStatus(): Promise<{ status: NfcStatus }>;\n /**\n * Opens the system settings page where the user can enable NFC.\n */\n showSettings(): Promise<void>;\n /**\n * Returns the version string baked into the native plugin.\n */\n getPluginVersion(): Promise<{ version: string }>;\n\n addListener(eventName: 'nfcEvent', listenerFunc: (event: NfcEvent) => void): Promise<PluginListenerHandle>;\n addListener(\n eventName: 'tagDiscovered' | 'ndefDiscovered' | 'ndefMimeDiscovered' | 'ndefFormatableDiscovered',\n listenerFunc: (event: NfcEvent) => void,\n ): Promise<PluginListenerHandle>;\n addListener(\n eventName: 'nfcStateChange',\n listenerFunc: (event: NfcStateChangeEvent) => void,\n ): Promise<PluginListenerHandle>;\n}\n\nexport type { PluginListenerHandle } from '@capacitor/core';\n"]}
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\n/**\n * Possible NFC adapter states returned by {@link CapacitorNfcPlugin.getStatus}.\n *\n * Matches the constants provided by the original PhoneGap NFC plugin for\n * compatibility with existing applications.\n */\nexport type NfcStatus = 'NFC_OK' | 'NO_NFC' | 'NFC_DISABLED' | 'NDEF_PUSH_DISABLED';\n\n/**\n * Event type describing the kind of NFC discovery that happened.\n *\n * - `tag`: A generic NFC tag (no NDEF payload).\n * - `ndef`: A tag exposing an NDEF payload.\n * - `ndef-mime`: An NDEF tag that matched one of the MIME type filters.\n * - `ndef-formatable`: A tag that can be formatted to NDEF.\n */\nexport type NfcEventType = 'tag' | 'ndef' | 'ndef-mime' | 'ndef-formatable';\n\n/**\n * JSON structure representing a single NDEF record.\n *\n * Mirrors the data format returned by the legacy Cordova implementation and\n * uses integer arrays instead of strings to preserve the original payload\n * bytes.\n */\nexport interface NdefRecord {\n /**\n * Type Name Format identifier.\n */\n tnf: number;\n /**\n * Type field expressed as an array of byte values.\n */\n type: number[];\n /**\n * Record identifier expressed as an array of byte values.\n */\n id: number[];\n /**\n * Raw payload expressed as an array of byte values.\n */\n payload: number[];\n}\n\n/**\n * Representation of the full tag information returned by the native layers.\n *\n * Supports standard NFC Forum tags as well as MIFARE Ultralight cards (including\n * EV1 and NTAG variants). NDEF data is automatically extracted from MIFARE Ultralight\n * tags when available.\n */\nexport interface NfcTag {\n /**\n * Raw identifier bytes for the tag.\n */\n id?: number[];\n /**\n * List of Android tech strings (e.g. `android.nfc.tech.Ndef`).\n */\n techTypes?: string[];\n /**\n * Human readable tag type when available (e.g. `NFC Forum Type 2`, `MIFARE Ultralight`).\n */\n type?: string | null;\n /**\n * Maximum writable size in bytes for tags that expose NDEF information.\n */\n maxSize?: number | null;\n /**\n * Indicates whether the tag can be written to.\n */\n isWritable?: boolean | null;\n /**\n * Indicates whether the tag can be permanently locked.\n */\n canMakeReadOnly?: boolean | null;\n /**\n * Array of NDEF records discovered on the tag.\n */\n ndefMessage?: NdefRecord[] | null;\n}\n\n/**\n * Generic NFC discovery event dispatched by the plugin.\n */\nexport interface NfcEvent {\n type: NfcEventType;\n tag: NfcTag;\n}\n\n/**\n * Options controlling the behaviour of {@link CapacitorNfcPlugin.startScanning}.\n */\nexport interface StartScanningOptions {\n /**\n * iOS-only: closes the NFC session automatically after the first successful tag read.\n * Defaults to `true`.\n */\n invalidateAfterFirstRead?: boolean;\n /**\n * iOS-only: custom message displayed in the NFC system sheet while scanning.\n */\n alertMessage?: string;\n /**\n * iOS-only: session type to use for NFC scanning.\n * - `'ndef'`: Uses NFCNDEFReaderSession (default). Only detects NDEF-formatted tags.\n * - `'tag'`: Uses NFCTagReaderSession. Detects both NDEF and non-NDEF tags (e.g., raw MIFARE tags).\n * Allows reading UID from unformatted tags.\n * Defaults to `'ndef'` for backward compatibility.\n */\n iosSessionType?: 'ndef' | 'tag';\n /**\n * Android-only: raw flags passed to `NfcAdapter.enableReaderMode`.\n * Defaults to enabling all tag types with skipping NDEF checks.\n */\n androidReaderModeFlags?: number;\n}\n\n/**\n * Options used when writing an NDEF message on the current tag.\n */\nexport interface WriteTagOptions {\n /**\n * Array of records that compose the NDEF message to be written.\n */\n records: NdefRecord[];\n /**\n * When `true`, the plugin attempts to format NDEF-formattable tags before writing.\n * Defaults to `true`.\n */\n allowFormat?: boolean;\n}\n\n/**\n * Options used when sharing an NDEF message with another device using Android Beam / P2P mode.\n */\nexport interface ShareTagOptions {\n records: NdefRecord[];\n}\n\n/**\n * Event emitted whenever the NFC adapter availability changes.\n */\nexport interface NfcStateChangeEvent {\n status: NfcStatus;\n enabled: boolean;\n}\n\n/**\n * Public API surface for the Capacitor NFC plugin.\n *\n * The interface intentionally mirrors the behaviour of the reference PhoneGap\n * implementation to ease migration while embracing idiomatic Capacitor APIs.\n */\nexport interface CapacitorNfcPlugin {\n /**\n * Starts listening for NFC tags.\n */\n startScanning(options?: StartScanningOptions): Promise<void>;\n /**\n * Stops the ongoing NFC scanning session.\n */\n stopScanning(): Promise<void>;\n /**\n * Writes the provided NDEF records to the last discovered tag.\n */\n write(options: WriteTagOptions): Promise<void>;\n /**\n * Attempts to erase the last discovered tag by writing an empty NDEF message.\n */\n erase(): Promise<void>;\n /**\n * Attempts to make the last discovered tag read-only.\n */\n makeReadOnly(): Promise<void>;\n /**\n * Shares an NDEF message with another device via peer-to-peer (Android only).\n */\n share(options: ShareTagOptions): Promise<void>;\n /**\n * Stops sharing previously provided NDEF message (Android only).\n */\n unshare(): Promise<void>;\n /**\n * Returns the current NFC adapter status.\n */\n getStatus(): Promise<{ status: NfcStatus }>;\n /**\n * Opens the system settings page where the user can enable NFC.\n */\n showSettings(): Promise<void>;\n /**\n * Returns the version string baked into the native plugin.\n */\n getPluginVersion(): Promise<{ version: string }>;\n\n addListener(eventName: 'nfcEvent', listenerFunc: (event: NfcEvent) => void): Promise<PluginListenerHandle>;\n addListener(\n eventName: 'tagDiscovered' | 'ndefDiscovered' | 'ndefMimeDiscovered' | 'ndefFormatableDiscovered',\n listenerFunc: (event: NfcEvent) => void,\n ): Promise<PluginListenerHandle>;\n addListener(\n eventName: 'nfcStateChange',\n listenerFunc: (event: NfcStateChangeEvent) => void,\n ): Promise<PluginListenerHandle>;\n}\n\nexport type { PluginListenerHandle } from '@capacitor/core';\n"]}
|
|
@@ -4,7 +4,7 @@ import UIKit
|
|
|
4
4
|
|
|
5
5
|
@objc(NfcPlugin)
|
|
6
6
|
public class NfcPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
7
|
-
private let pluginVersion: String = "8.0.
|
|
7
|
+
private let pluginVersion: String = "8.0.11"
|
|
8
8
|
|
|
9
9
|
public let identifier = "NfcPlugin"
|
|
10
10
|
public let jsName = "CapacitorNfc"
|
|
@@ -21,10 +21,12 @@ public class NfcPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
21
21
|
CAPPluginMethod(name: "getPluginVersion", returnType: CAPPluginReturnPromise)
|
|
22
22
|
]
|
|
23
23
|
|
|
24
|
-
private var
|
|
24
|
+
private var ndefReaderSession: NFCNDEFReaderSession?
|
|
25
|
+
private var tagReaderSession: NFCTagReaderSession?
|
|
25
26
|
private let sessionQueue = DispatchQueue(label: "app.capgo.nfc.session")
|
|
26
27
|
private var currentTag: NFCNDEFTag?
|
|
27
28
|
private var invalidateAfterFirstRead = true
|
|
29
|
+
private var sessionType: String = "ndef"
|
|
28
30
|
|
|
29
31
|
@objc public func startScanning(_ call: CAPPluginCall) {
|
|
30
32
|
#if targetEnvironment(simulator)
|
|
@@ -38,14 +40,38 @@ public class NfcPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
38
40
|
|
|
39
41
|
invalidateAfterFirstRead = call.getBool("invalidateAfterFirstRead", true)
|
|
40
42
|
let alertMessage = call.getString("alertMessage")
|
|
43
|
+
sessionType = call.getString("iosSessionType", "ndef")
|
|
41
44
|
|
|
42
45
|
DispatchQueue.main.async {
|
|
43
|
-
|
|
44
|
-
self.
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
// Invalidate any existing sessions
|
|
47
|
+
self.ndefReaderSession?.invalidate()
|
|
48
|
+
self.ndefReaderSession = nil
|
|
49
|
+
self.tagReaderSession?.invalidate()
|
|
50
|
+
self.tagReaderSession = nil
|
|
51
|
+
|
|
52
|
+
if self.sessionType == "tag" {
|
|
53
|
+
// Use NFCTagReaderSession for raw tag support
|
|
54
|
+
self.tagReaderSession = NFCTagReaderSession(
|
|
55
|
+
pollingOption: [.iso14443, .iso15693, .iso18092],
|
|
56
|
+
delegate: self,
|
|
57
|
+
queue: self.sessionQueue
|
|
58
|
+
)
|
|
59
|
+
if let alertMessage, !alertMessage.isEmpty {
|
|
60
|
+
self.tagReaderSession?.alertMessage = alertMessage
|
|
61
|
+
}
|
|
62
|
+
self.tagReaderSession?.begin()
|
|
63
|
+
} else {
|
|
64
|
+
// Use NFCNDEFReaderSession (default behavior)
|
|
65
|
+
self.ndefReaderSession = NFCNDEFReaderSession(
|
|
66
|
+
delegate: self,
|
|
67
|
+
queue: self.sessionQueue,
|
|
68
|
+
invalidateAfterFirstRead: self.invalidateAfterFirstRead
|
|
69
|
+
)
|
|
70
|
+
if let alertMessage, !alertMessage.isEmpty {
|
|
71
|
+
self.ndefReaderSession?.alertMessage = alertMessage
|
|
72
|
+
}
|
|
73
|
+
self.ndefReaderSession?.begin()
|
|
47
74
|
}
|
|
48
|
-
self.readerSession?.begin()
|
|
49
75
|
}
|
|
50
76
|
|
|
51
77
|
call.resolve()
|
|
@@ -54,15 +80,17 @@ public class NfcPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
54
80
|
|
|
55
81
|
@objc public func stopScanning(_ call: CAPPluginCall) {
|
|
56
82
|
DispatchQueue.main.async {
|
|
57
|
-
self.
|
|
58
|
-
self.
|
|
83
|
+
self.ndefReaderSession?.invalidate()
|
|
84
|
+
self.ndefReaderSession = nil
|
|
85
|
+
self.tagReaderSession?.invalidate()
|
|
86
|
+
self.tagReaderSession = nil
|
|
59
87
|
self.currentTag = nil
|
|
60
88
|
}
|
|
61
89
|
call.resolve()
|
|
62
90
|
}
|
|
63
91
|
|
|
64
92
|
@objc public func write(_ call: CAPPluginCall) {
|
|
65
|
-
guard
|
|
93
|
+
guard currentTag != nil else {
|
|
66
94
|
call.reject("No active NFC session or tag. Call startScanning and present a tag before writing.")
|
|
67
95
|
return
|
|
68
96
|
}
|
|
@@ -74,21 +102,40 @@ public class NfcPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
74
102
|
|
|
75
103
|
do {
|
|
76
104
|
let message = try buildMessage(from: rawRecords)
|
|
77
|
-
|
|
105
|
+
performWriteToCurrentTag(message: message, call: call)
|
|
78
106
|
} catch {
|
|
79
107
|
call.reject("Invalid NDEF records payload.", nil, error)
|
|
80
108
|
}
|
|
81
109
|
}
|
|
82
110
|
|
|
83
111
|
@objc public func erase(_ call: CAPPluginCall) {
|
|
84
|
-
guard
|
|
112
|
+
guard currentTag != nil else {
|
|
85
113
|
call.reject("No active NFC session or tag. Call startScanning and present a tag before erasing.")
|
|
86
114
|
return
|
|
87
115
|
}
|
|
88
116
|
|
|
89
117
|
let emptyRecord = NFCNDEFPayload(format: .empty, type: Data(), identifier: Data(), payload: Data())
|
|
90
118
|
let message = NFCNDEFMessage(records: [emptyRecord])
|
|
91
|
-
|
|
119
|
+
performWriteToCurrentTag(message: message, call: call)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private func performWriteToCurrentTag(message: NFCNDEFMessage, call: CAPPluginCall) {
|
|
123
|
+
guard let tag = currentTag else {
|
|
124
|
+
call.reject("No active NFC session or tag.")
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if let ndefSession = ndefReaderSession {
|
|
129
|
+
// For NDEF session, we need to connect to the tag first
|
|
130
|
+
performWrite(message: message, on: tag, session: ndefSession, call: call)
|
|
131
|
+
} else if tagReaderSession != nil {
|
|
132
|
+
// For Tag session, tag remains connected from discovery
|
|
133
|
+
// Note: If the tag is removed and re-presented, the session will detect it as a new tag
|
|
134
|
+
// and performWriteToTag may fail. Users should keep the tag in place after detection.
|
|
135
|
+
performWriteToTag(message: message, on: tag, call: call)
|
|
136
|
+
} else {
|
|
137
|
+
call.reject("No active NFC session.")
|
|
138
|
+
}
|
|
92
139
|
}
|
|
93
140
|
|
|
94
141
|
@objc public func makeReadOnly(_ call: CAPPluginCall) {
|
|
@@ -142,44 +189,48 @@ public class NfcPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
142
189
|
return
|
|
143
190
|
}
|
|
144
191
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
192
|
+
self.performWriteToTag(message: message, on: tag, call: call)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private func performWriteToTag(message: NFCNDEFMessage, on tag: NFCNDEFTag, call: CAPPluginCall) {
|
|
197
|
+
tag.queryNDEFStatus { status, capacity, statusError in
|
|
198
|
+
if let statusError {
|
|
199
|
+
DispatchQueue.main.async {
|
|
200
|
+
call.reject("Failed to query tag status.", nil, statusError)
|
|
151
201
|
}
|
|
202
|
+
return
|
|
203
|
+
}
|
|
152
204
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
DispatchQueue.main.async {
|
|
157
|
-
call.reject("Tag capacity is insufficient for the provided message.")
|
|
158
|
-
}
|
|
159
|
-
return
|
|
160
|
-
}
|
|
161
|
-
tag.writeNDEF(message) { writeError in
|
|
162
|
-
DispatchQueue.main.async {
|
|
163
|
-
if let writeError {
|
|
164
|
-
call.reject("Failed to write NDEF message.", nil, writeError)
|
|
165
|
-
} else {
|
|
166
|
-
call.resolve()
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
case .readOnly:
|
|
171
|
-
DispatchQueue.main.async {
|
|
172
|
-
call.reject("Tag is read only.")
|
|
173
|
-
}
|
|
174
|
-
case .notSupported:
|
|
205
|
+
switch status {
|
|
206
|
+
case .readWrite:
|
|
207
|
+
if capacity < message.length {
|
|
175
208
|
DispatchQueue.main.async {
|
|
176
|
-
call.reject("Tag
|
|
209
|
+
call.reject("Tag capacity is insufficient for the provided message.")
|
|
177
210
|
}
|
|
178
|
-
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
tag.writeNDEF(message) { writeError in
|
|
179
214
|
DispatchQueue.main.async {
|
|
180
|
-
|
|
215
|
+
if let writeError {
|
|
216
|
+
call.reject("Failed to write NDEF message.", nil, writeError)
|
|
217
|
+
} else {
|
|
218
|
+
call.resolve()
|
|
219
|
+
}
|
|
181
220
|
}
|
|
182
221
|
}
|
|
222
|
+
case .readOnly:
|
|
223
|
+
DispatchQueue.main.async {
|
|
224
|
+
call.reject("Tag is read only.")
|
|
225
|
+
}
|
|
226
|
+
case .notSupported:
|
|
227
|
+
DispatchQueue.main.async {
|
|
228
|
+
call.reject("Tag does not support NDEF.")
|
|
229
|
+
}
|
|
230
|
+
@unknown default:
|
|
231
|
+
DispatchQueue.main.async {
|
|
232
|
+
call.reject("Unknown tag status.")
|
|
233
|
+
}
|
|
183
234
|
}
|
|
184
235
|
}
|
|
185
236
|
}
|
|
@@ -343,7 +394,7 @@ extension NfcPlugin: NFCNDEFReaderSessionDelegate {
|
|
|
343
394
|
self.notifyListeners("nfcStateChange", data: payload, retainUntilConsumed: true)
|
|
344
395
|
}
|
|
345
396
|
}
|
|
346
|
-
|
|
397
|
+
ndefReaderSession = nil
|
|
347
398
|
}
|
|
348
399
|
|
|
349
400
|
public func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
|
|
@@ -402,6 +453,140 @@ extension NfcPlugin: NFCNDEFReaderSessionDelegate {
|
|
|
402
453
|
}
|
|
403
454
|
}
|
|
404
455
|
|
|
456
|
+
// MARK: - NFCTagReaderSessionDelegate
|
|
457
|
+
extension NfcPlugin: NFCTagReaderSessionDelegate {
|
|
458
|
+
public func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {
|
|
459
|
+
// Session became active, ready to detect tags
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
public func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
|
|
463
|
+
currentTag = nil
|
|
464
|
+
let nfcError = error as NSError
|
|
465
|
+
|
|
466
|
+
// Don't emit state change for normal session completion (user canceled)
|
|
467
|
+
// Also check for successful read completion
|
|
468
|
+
if nfcError.code != NFCReaderError.readerSessionInvalidationErrorUserCanceled.rawValue {
|
|
469
|
+
DispatchQueue.main.async {
|
|
470
|
+
let payload: [String: Any] = [
|
|
471
|
+
"status": NFCNDEFReaderSession.readingAvailable ? "NFC_OK" : "NO_NFC",
|
|
472
|
+
"enabled": NFCNDEFReaderSession.readingAvailable
|
|
473
|
+
]
|
|
474
|
+
self.notifyListeners("nfcStateChange", data: payload, retainUntilConsumed: true)
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
tagReaderSession = nil
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
public func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
|
|
481
|
+
// Handle multiple tags case - CoreNFC recommends invalidating with a message
|
|
482
|
+
if tags.count > 1 {
|
|
483
|
+
session.invalidate(errorMessage: "More than one tag detected. Please present only one tag.")
|
|
484
|
+
return
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
guard let firstTag = tags.first else {
|
|
488
|
+
return
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
session.connect(to: firstTag) { [weak self] error in
|
|
492
|
+
guard let self else {
|
|
493
|
+
return
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if let error {
|
|
497
|
+
session.invalidate(errorMessage: "Failed to connect to the tag: \(error.localizedDescription)")
|
|
498
|
+
return
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Handle different tag types
|
|
502
|
+
switch firstTag {
|
|
503
|
+
case .miFare(let mifareTag):
|
|
504
|
+
self.processTag(mifareTag, session: session)
|
|
505
|
+
case .iso7816(let iso7816Tag):
|
|
506
|
+
self.processTag(iso7816Tag, session: session)
|
|
507
|
+
case .iso15693(let iso15693Tag):
|
|
508
|
+
self.processTag(iso15693Tag, session: session)
|
|
509
|
+
case .feliCa(let feliCaTag):
|
|
510
|
+
self.processTag(feliCaTag, session: session)
|
|
511
|
+
@unknown default:
|
|
512
|
+
session.invalidate(errorMessage: "Unsupported tag type")
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
private func processTag(_ tag: NFCNDEFTag, session: NFCTagReaderSession) {
|
|
518
|
+
// Try to read NDEF if available, otherwise emit tag with UID only
|
|
519
|
+
tag.queryNDEFStatus { [weak self] status, capacity, error in
|
|
520
|
+
guard let self else {
|
|
521
|
+
return
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if error == nil && status != .notSupported {
|
|
525
|
+
// Tag supports NDEF, try to read it
|
|
526
|
+
tag.readNDEF { [weak self] message, _ in
|
|
527
|
+
guard let self else {
|
|
528
|
+
return
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if message == nil {
|
|
532
|
+
// NDEF read failed, still emit tag with UID
|
|
533
|
+
self.emitTagEvent(tag: tag, message: nil, session: session)
|
|
534
|
+
} else {
|
|
535
|
+
// Successfully read NDEF
|
|
536
|
+
self.currentTag = tag
|
|
537
|
+
let event = self.buildEvent(tag: tag, status: status, capacity: capacity, message: message)
|
|
538
|
+
self.notify(event: event)
|
|
539
|
+
if self.invalidateAfterFirstRead {
|
|
540
|
+
session.invalidate()
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
} else {
|
|
545
|
+
// Tag doesn't support NDEF or query failed - just emit UID
|
|
546
|
+
self.emitTagEvent(tag: tag, message: nil, session: session)
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
private func emitTagEvent(tag: NFCNDEFTag, message: NFCNDEFMessage?, session: NFCTagReaderSession) {
|
|
552
|
+
// Save the current tag for writing
|
|
553
|
+
currentTag = tag
|
|
554
|
+
|
|
555
|
+
var tagInfo: [String: Any] = [:]
|
|
556
|
+
|
|
557
|
+
// Extract and add the tag ID (UID)
|
|
558
|
+
if let identifierData = extractIdentifier(from: tag) {
|
|
559
|
+
tagInfo["id"] = array(from: identifierData)
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
tagInfo["techTypes"] = detectTechTypes(for: tag)
|
|
563
|
+
tagInfo["type"] = translateType(for: tag)
|
|
564
|
+
|
|
565
|
+
if let message {
|
|
566
|
+
tagInfo["isWritable"] = true
|
|
567
|
+
tagInfo["ndefMessage"] = message.records.map { record in
|
|
568
|
+
[
|
|
569
|
+
"tnf": NSNumber(value: record.typeNameFormat.rawValue),
|
|
570
|
+
"type": array(from: record.type),
|
|
571
|
+
"id": array(from: record.identifier),
|
|
572
|
+
"payload": array(from: record.payload)
|
|
573
|
+
].compactMapValues { $0 }
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
let event: [String: Any] = [
|
|
578
|
+
"type": message != nil ? "ndef" : "tag",
|
|
579
|
+
"tag": tagInfo
|
|
580
|
+
]
|
|
581
|
+
|
|
582
|
+
notify(event: event)
|
|
583
|
+
|
|
584
|
+
if invalidateAfterFirstRead {
|
|
585
|
+
session.invalidate()
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
405
590
|
enum NfcPluginError: Error {
|
|
406
591
|
case invalidPayload
|
|
407
592
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capgo/capacitor-nfc",
|
|
3
|
-
"version": "8.0.
|
|
3
|
+
"version": "8.0.11",
|
|
4
4
|
"description": "Native NFC tag discovery, reading and writing for Capacitor apps on iOS and Android.",
|
|
5
5
|
"main": "dist/plugin.cjs.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
"build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
|
|
48
48
|
"clean": "rimraf ./dist",
|
|
49
49
|
"watch": "tsc --watch",
|
|
50
|
-
"prepublishOnly": "npm run build"
|
|
50
|
+
"prepublishOnly": "npm run build",
|
|
51
|
+
"check:wiring": "node scripts/check-capacitor-plugin-wiring.mjs"
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
53
54
|
"@capacitor/android": "^8.0.0",
|