@exxili/capacitor-nfc 0.0.10 → 0.0.12

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 CHANGED
@@ -9,32 +9,35 @@ A Capacitor plugin for reading and writing NFC tags on iOS and Android devices.
9
9
 
10
10
  ## Table of Contents
11
11
 
12
- - [Installation](#installation)
13
- - [iOS Setup](#ios-setup)
14
- - [Android Setup](#android-setup)
15
- - [Usage](#usage)
16
- - [Reading NFC Tags](#reading-nfc-tags)
17
- - [Writing NFC Tags](#writing-nfc-tags)
18
- - [API](#api)
19
- - [Methods](#methods)
20
- - [`isSupported()`](#issupported)
21
- - [`startScan()`](#startscan)
22
- - [`writeNDEF(options)`](#writendefoptions-ndefwriteoptionst-extends-string--number--uint8array--string)
23
- - [`cancelWriteAndroid`](#cancelwriteandroid)
24
- - [Listeners](#listeners)
25
- - [`onRead(listener)`](#onreadlistener-data-ndefmessagestransformable--void)
26
- - [`onError('listener)`](#onerrorlistener-error-nfcerror--void)
27
- - [`onWrite(listener)`](#onwritelistener---void)
28
- - [Interfaces](#interfaces)
29
- - [`NDEFWriteOptions`](#ndefwriteoptions)
30
- - [`NDEFWriteOptions`](#ndefmessagestransformable)
31
- - [`NDEFMessages`](#ndefmessages)
32
- - [`NDEFMessage`](#ndefmessage)
33
- - [`NDEFRecord`](#ndefrecord)
34
- - [`NFCError`](#nfcerror)
35
- - [Integration into a Capacitor App](#integration-into-a-capacitor-app)
36
- - [Example](#example)
37
- - [License](#license)
12
+ - [Capacitor NFC Plugin (@exxili/capacitor-nfc)](#capacitor-nfc-plugin-exxilicapacitor-nfc)
13
+ - [Table of Contents](#table-of-contents)
14
+ - [Installation](#installation)
15
+ - [iOS Setup](#ios-setup)
16
+ - [1. Enable NFC Capability](#1-enable-nfc-capability)
17
+ - [2. Add Usage Description](#2-add-usage-description)
18
+ - [Android Setup](#android-setup)
19
+ - [Usage](#usage)
20
+ - [Reading NFC Tags](#reading-nfc-tags)
21
+ - [Writing NFC Tags](#writing-nfc-tags)
22
+ - [API](#api)
23
+ - [Methods](#methods)
24
+ - [`isSupported()`](#issupported)
25
+ - [`startScan()`](#startscan)
26
+ - [`cancelScan()`](#cancelscan)
27
+ - [`writeNDEF(options: NDEFWriteOptions<T extends string | number[] | Uint8Array = string>)`](#writendefoptions-ndefwriteoptionst-extends-string--number--uint8array--string)
28
+ - [`cancelWriteAndroid()`](#cancelwriteandroid)
29
+ - [Listeners](#listeners)
30
+ - [`onRead(listener: (data: NDEFMessagesTransformable) => void)`](#onreadlistener-data-ndefmessagestransformable--void)
31
+ - [Interfaces](#interfaces)
32
+ - [`NDEFWriteOptions`](#ndefwriteoptions)
33
+ - [`NDEFMessagesTransformable`](#ndefmessagestransformable)
34
+ - [`NDEFMessages`](#ndefmessages)
35
+ - [`NDEFMessage`](#ndefmessage)
36
+ - [`NDEFRecord`](#ndefrecord)
37
+ - [`NFCError`](#nfcerror)
38
+ - [Integration into a Capacitor App](#integration-into-a-capacitor-app)
39
+ - [Example](#example)
40
+ - [License](#license)
38
41
 
39
42
  ## Installation
40
43
 
@@ -95,20 +98,37 @@ import { NFC } from '@exxili/capacitor-nfc';
95
98
  To read NFC tags, you need to listen for `nfcTag` events. On iOS, you must also start the NFC scanning session using `startScan()`.
96
99
 
97
100
  ```typescript
98
- import {NFC, NDEFMessagesTransformable, NFCError} from '@exxili/capacitor-nfc';
101
+ import { NFC, NDEFMessagesTransformable, NFCError } from '@exxili/capacitor-nfc';
99
102
 
100
- // Start NFC scanning
103
+ // Start NFC scanning (iOS only)
101
104
  NFC.startScan().catch((error) => {
102
105
  console.error('Error starting NFC scan:', error);
103
106
  });
104
107
 
105
108
  // Listen for NFC tag detection
106
109
  NFC.onRead((data: NDEFMessagesTransformable) => {
107
- console.log('Received NFC tag:', data.string());
110
+ // Text (T) and URI (U) records decoded; others best-effort UTF-8
111
+ const asString = data.string();
112
+ console.log('First record text payload:', asString.messages[0]?.records[0]?.payload);
113
+
114
+ // Raw bytes
115
+ const asUint8 = data.uint8Array();
116
+ console.log('First record raw bytes length:', asUint8.messages[0]?.records[0]?.payload.length);
117
+
118
+ // Access tag information (UID, tech types, etc.)
119
+ if (asString.tagInfo) {
120
+ console.log('Tag UID:', asString.tagInfo.uid);
121
+ console.log('Tag technologies:', asString.tagInfo.techTypes);
122
+ console.log('Tag type:', asString.tagInfo.type);
123
+ if (asString.tagInfo.maxSize) {
124
+ console.log('Max NDEF size:', asString.tagInfo.maxSize);
125
+ }
126
+ console.log('Is writable:', asString.tagInfo.isWritable);
127
+ }
108
128
  });
109
129
 
110
130
  // Handle NFC errors
111
- NFC.onError('nfcError', (error: NFCError) => {
131
+ NFC.onError((error: NFCError) => {
112
132
  console.error('NFC Error:', error);
113
133
  });
114
134
  ```
@@ -123,9 +143,17 @@ import { NFC, NDEFWriteOptions, NFCError } from '@exxili/capacitor-nfc';
123
143
  const message: NDEFWriteOptions = {
124
144
  records: [
125
145
  {
126
- type: 'T', // Text record type
146
+ type: 'T', // Well Known Text record. String payload will be encoded as: [status][lang='en'][UTF-8 text]
127
147
  payload: 'Hello, NFC!',
128
148
  },
149
+ {
150
+ type: 'U', // Well Known URI record. String payload encoded as: [0x00][URI bytes]
151
+ payload: 'https://example.com',
152
+ },
153
+ {
154
+ type: 'T',
155
+ payload: new Uint8Array([0x01, 0x65, 0x48, 0x69]), // Raw bytes preserved (DO NOT re-format)
156
+ },
129
157
  ],
130
158
  };
131
159
 
@@ -161,7 +189,7 @@ Returns if NFC is supported on the scanning device.
161
189
 
162
190
  #### `startScan()`
163
191
 
164
- Starts the NFC scanning session on ***iOS only***. Android devices are always in reading mode, so setting up the `nfcTag` listener is sufficient to handle tag reads on Android.
192
+ Starts the NFC scanning session on **_iOS only_**. Android devices are always in reading mode, so setting up the `nfcTag` listener is sufficient to handle tag reads on Android.
165
193
 
166
194
  **Returns**: `Promise<void>`
167
195
 
@@ -175,17 +203,45 @@ NFC.startScan()
175
203
  });
176
204
  ```
177
205
 
178
- #### `writeNDEF(options: NDEFWriteOptions<T extends string | number[] | Uint8Array = string)`
206
+ #### `cancelScan()`
207
+
208
+ Immediately invalidates the active iOS CoreNFC reader session (if any). Useful when you want to abort a scan early instead of waiting for the user to cancel or for a tag detection timeout.
209
+
210
+ Platform notes:
211
+
212
+ - iOS: Actively ends the session. Calling `startScan()` again after the returned promise resolves is safe.
213
+ - Android: No-op (Android is always passively listening via foreground dispatch; you generally just ignore future events or control UI state).
214
+
215
+ ```typescript
216
+ await NFC.cancelScan(); // iOS: ends session; Android: no-op
217
+ // Optionally restart
218
+ await NFC.startScan();
219
+ ```
220
+
221
+ **Returns**: `Promise<void>`
222
+
223
+ #### `writeNDEF(options: NDEFWriteOptions<T extends string | number[] | Uint8Array = string>)`
179
224
 
180
225
  Writes an NDEF message to an NFC tag.
181
226
 
182
- Payload may be provided as a string, `Uint8Array`, or an array of numbers. The plugin will automatically convert the payload to a byte array for storage on the NFC tag.
227
+ Payload may be provided as a string, `Uint8Array`, or an array of numbers.
228
+
229
+ Automatic formatting rules (to aid interoperability):
230
+
231
+ - Text (`type: 'T'` + string payload): encoded per NFC Forum RTD Text spec `[status][lang=en][UTF-8 text]`.
232
+ - URI (`type: 'U'` + string payload): encoded as `[0x00][UTF-8 URI bytes]` (prefix compression not yet applied).
233
+ - Any other `type` + string payload: UTF-8 bytes only (no extra framing).
234
+ - `Uint8Array` or `number[]` payloads are treated as raw bytes and written verbatim (never altered).
235
+
236
+ If you need full manual control of a Text or URI record, supply raw bytes (number[] / Uint8Array) and the plugin will not modify them.
237
+
238
+ If you attempt to write zero records the promise rejects with `Error("At least one NDEF record is required")`.
183
239
 
184
240
  Android use: since Android has no default UI for reading and writing NFC tags, it is recommended that you add a UI indicator to your application when calling `writeNDEF` and remove it in the `nfcWriteSuccess` listener callback and the `nfcError` listener callback. This will prevent accidental writes to tags that your users intended to read from.
185
241
 
186
242
  **Parameters**:
187
243
 
188
- - `options: NDEFWriteOptions<T extends string | number[] | Uint8Array = string>` - The NDEF message to write.
244
+ - `options: NDEFWriteOptions<T extends string | number[] | Uint8Array = string>` - The NDEF message to write. Must include at least one record.
189
245
 
190
246
  **Returns**: `Promise<void>`
191
247
 
@@ -209,24 +265,35 @@ Cancels an Android NFC write operation. Android does not have a native UI for NF
209
265
 
210
266
  Adds a listener for NFC tag detection events. Returns type `NDEFMessagesTransformable`, which returns the following methods to provide the payload:
211
267
 
212
- * `string()`: Returns `NDEFMessages<string>`, where all payloads are strings.
213
- * `base64()`: Returns `NDEFMessages<string>`, where all payloads are the base64-encoded payloads read from the NFC tag.
214
- * `uint8Array()`: Returns `NDEFMessages<Uint8Array>`, where all payloads are the `Uint8Array` bytes from the NFC tag.
215
- * `numberArray()`: Returns `NDEFMessages<number[]>`, where all payloads bytes from the NFC tag represented as a `number[]`.
268
+ - `string()`: Returns `NDEFMessages<string>`, where all payloads are strings.
269
+ - `base64()`: Returns `NDEFMessages<string>`, where all payloads are the base64-encoded payloads read from the NFC tag.
270
+ - `uint8Array()`: Returns `NDEFMessages<Uint8Array>`, where all payloads are the `Uint8Array` bytes from the NFC tag.
271
+ - `numberArray()`: Returns `NDEFMessages<number[]>`, where all payloads' bytes from the NFC tag are represented as a `number[]`.
216
272
 
217
273
  **Parameters**:
218
274
 
219
275
  - `listener: (data: NDEFMessagesTransformable) => void` - The function to call when an NFC tag is detected.
220
276
 
221
- **Returns**: `void`
277
+ **Returns**: `() => void` Unsubscribe function to remove just this listener.
222
278
 
223
279
  ```typescript
224
- NFC.onRead((data: NDEFMessages) => {
225
- console.log('Received NFC tag:', data);
280
+ const offRead = NFC.onRead((data) => {
281
+ const textRecords = data.string(); // Decoded string representation
282
+ const base64Records = data.base64(); // Original base64 payloads
283
+ const bytesRecords = data.uint8Array(); // Uint8Array payloads
284
+ const numArrayRecords = data.numberArray(); // number[] representation
285
+ console.log(textRecords);
226
286
  });
287
+
288
+ // Later (component unmount / cleanup)
289
+ offRead();
227
290
  ```
228
291
 
229
- #### `onError(listener: (error: NFCError) => void)`
292
+ > Tip: Register listeners once per screen/component and always dispose them when unmounting to avoid duplicate callbacks.
293
+
294
+ ````
295
+
296
+ #### On Error
230
297
 
231
298
  Adds a listener for NFC error events.
232
299
 
@@ -234,29 +301,23 @@ Adds a listener for NFC error events.
234
301
 
235
302
  - `listener: (error: NFCError) => void` - The function to call when an NFC error occurs.
236
303
 
237
- **Returns**: `PluginListenerHandle`
238
-
239
- ```typescript
240
- NFC.onError((error: NFCError) => {
241
- console.error('NFC Error:', error);
242
- });
243
- ```
304
+ **Returns**: `() => void` Unsubscribe function.
244
305
 
245
- #### `onWrite(listener: () => void)`
306
+ #### On Error
246
307
 
247
- Adds a listener for NFC write success events.
308
+ Adds a listener for NFC error events.
248
309
 
249
310
  **Parameters**:
250
311
 
251
- - `listener: () => void` - The function to call when an NDEF message has been written successfully.
312
+ - `listener: (error: NFCError) => void` - The function to call when an NFC error occurs.
252
313
 
253
- **Returns**: `PluginListenerHandle`
314
+ **Returns**: `() => void` Unsubscribe function.
254
315
 
255
316
  ```typescript
256
- NFC.onWrite('nfcWriteSuccess', () => {
257
- console.log('NDEF message written successfully.');
258
- });
259
- ```
317
+ const offError = NFC.onError(err => console.error('NFC Error:', err));
318
+ // later
319
+ offError();
320
+ ````
260
321
 
261
322
  ### Interfaces
262
323
 
@@ -274,17 +335,17 @@ interface NDEFWriteOptions<T extends string | number[] | Uint8Array = string> {
274
335
 
275
336
  Returned by `onRead` and includes the following methods to provide the payload:
276
337
 
277
- * `string()`: Returns `NDEFMessages<string>`, where all payloads are strings.
278
- * `base64()`: Returns `NDEFMessages<string>`, where all payloads are the base64-encoded payloads read from the NFC tag.
279
- * `uint8Array()`: Returns `NDEFMessages<Uint8Array>`, where all payloads are the `Uint8Array` bytes from the NFC tag.
280
- * `numberArray()`: Returns `NDEFMessages<number[]>`, where all payloads bytes from the NFC tag represented as a `number[]`.
338
+ - `string()`: Returns `NDEFMessages<string>`, where all payloads are strings.
339
+ - `base64()`: Returns `NDEFMessages<string>`, where all payloads are the base64-encoded payloads read from the NFC tag.
340
+ - `uint8Array()`: Returns `NDEFMessages<Uint8Array>`, where all payloads are the `Uint8Array` bytes from the NFC tag.
341
+ - `numberArray()`: Returns `NDEFMessages<number[]>`, where all payloads bytes from the NFC tag represented as a `number[]`.
281
342
 
282
343
  ```typescript
283
344
  interface NDEFMessagesTransformable {
284
- base64: ()=> NDEFMessages;
285
- uint8Array: ()=> NDEFMessages<Uint8Array>;
286
- string: ()=> NDEFMessages;
287
- numberArray: ()=> NDEFMessages<number[]>;
345
+ base64: () => NDEFMessages<string>; // Original base64 strings
346
+ uint8Array: () => NDEFMessages<Uint8Array>; // Raw bytes
347
+ string: () => NDEFMessages<string>; // Decoded (T & U handled, others UTF-8 best-effort)
348
+ numberArray: () => NDEFMessages<number[]>; // Raw bytes as number[]
288
349
  }
289
350
  ```
290
351
 
@@ -295,6 +356,40 @@ Data received from an NFC tag.
295
356
  ```typescript
296
357
  interface NDEFMessages {
297
358
  messages: NDEFMessage[];
359
+ tagInfo?: TagInfo;
360
+ }
361
+ ```
362
+
363
+ #### `TagInfo`
364
+
365
+ Information about the NFC tag that was read.
366
+
367
+ ```typescript
368
+ interface TagInfo {
369
+ /**
370
+ * The unique identifier of the tag (UID) as a hex string
371
+ */
372
+ uid: string;
373
+
374
+ /**
375
+ * The NFC tag technology types supported
376
+ */
377
+ techTypes: string[];
378
+
379
+ /**
380
+ * The maximum size of NDEF message that can be written to this tag (if applicable)
381
+ */
382
+ maxSize?: number;
383
+
384
+ /**
385
+ * Whether the tag is writable
386
+ */
387
+ isWritable?: boolean;
388
+
389
+ /**
390
+ * The tag type (e.g., "ISO14443-4", "MifareClassic", etc.)
391
+ */
392
+ type?: string;
298
393
  }
299
394
  ```
300
395
 
@@ -324,7 +419,7 @@ interface NDEFRecord<T = number[]> {
324
419
  */
325
420
  payload: T;
326
421
  }
327
- ````
422
+ ```
328
423
 
329
424
  #### `NFCError`
330
425
 
@@ -363,54 +458,47 @@ To integrate this plugin into your Capacitor app:
363
458
  Here's a complete example of how to read and write NFC tags in your app:
364
459
 
365
460
  ```typescript
366
- import { NFC, NDEFMessages, NDEFWriteOptions, NFCError } from '@exxili/capacitor-nfc';
461
+ import { NFC, NDEFWriteOptions, NFCError, NDEFMessagesTransformable } from '@exxili/capacitor-nfc';
367
462
 
368
- // Check if NFC is supported
463
+ // Check if NFC is supported (optional gating logic)
369
464
  const { supported } = await NFC.isSupported();
465
+ if (!supported) {
466
+ console.warn('NFC not supported on this device');
467
+ }
370
468
 
371
- // Start NFC scanning -- iOS only
372
- NFC.startScan().catch((error) => {
373
- console.error('Error starting NFC scan:', error);
374
- });
469
+ // Start NFC scanning (needed on iOS only)
470
+ NFC.startScan().catch((err) => console.error('Failed to start scan', err));
375
471
 
376
- // Listen for NFC tag detection
377
- NFC.onRead((data: NDEFMessages) => {
378
- const stringMessages: NDEFMessage<string> = data.string();
379
- const uint8ArrayMessages: NDEFMessage<Uint8Array> = data.uint8Array();
380
-
381
- // Print all Uint8Array payloads
382
- console.log('Received NFC tag:', stringMessages.messages?.at(0)?.records?.at(0).payload); // prints string[]
383
- console.log('Received NFC tag:', uint8ArrayPayloads.messages?.at(0)?.records?.at(0).payload); // prints Uint8Array[]
384
- });
472
+ // Read listener returns a transformable wrapper
473
+ const offRead = NFC.onRead((data: NDEFMessagesTransformable) => {
474
+ const textView = data.string(); // NDEFMessages<string>
475
+ const rawBytesView = data.uint8Array(); // NDEFMessages<Uint8Array>
385
476
 
386
- // Handle NFC errors
387
- NFC.onError((error: NFCError) => {
388
- console.error('NFC Error:', error);
477
+ const firstText = textView.messages[0]?.records[0]?.payload;
478
+ const firstLength = rawBytesView.messages[0]?.records[0]?.payload.length;
479
+ console.log('First text record:', firstText);
480
+ console.log('First record byte length:', firstLength);
389
481
  });
390
482
 
391
- // Prepare an NDEF message to write
483
+ // Error listener (covers read & write errors)
484
+ const offError = NFC.onError((error: NFCError) => console.error('NFC Error:', error));
485
+
486
+ // Prepare an NDEF message to write (auto-formats Text/URI if payload is string)
392
487
  const message: NDEFWriteOptions = {
393
488
  records: [
394
- {
395
- type: 'T', // Text record type
396
- payload: 'Hello, NFC!',
397
- },
489
+ { type: 'T', payload: 'Hello, NFC!' },
490
+ { type: 'U', payload: 'https://example.com' },
398
491
  ],
399
492
  };
400
493
 
401
- // Write NDEF message to NFC tag
402
- NFC.writeNDEF(message)
403
- .then(() => {
404
- console.log('Write initiated');
405
- })
406
- .catch((error) => {
407
- console.error('Error writing to NFC tag:', error);
408
- });
494
+ await NFC.writeNDEF(message).catch((err) => console.error('Write failed', err));
409
495
 
410
- // Listen for write success
411
- NFC.onWrite('nfcWriteSuccess', () => {
412
- console.log('NDEF message written successfully.');
413
- });
496
+ const offWrite = NFC.onWrite(() => console.log('Write success'));
497
+
498
+ // Later (cleanup)
499
+ offRead();
500
+ offError();
501
+ offWrite();
414
502
  ```
415
503
 
416
504
  ## License
@@ -1,5 +1,6 @@
1
1
  package com.exxili.capacitornfc
2
2
 
3
+ import android.app.ActivityOptions
3
4
  import android.app.PendingIntent
4
5
  import android.content.Intent
5
6
  import android.content.IntentFilter
@@ -22,7 +23,10 @@ import android.nfc.tech.NfcB
22
23
  import android.nfc.tech.NfcBarcode
23
24
  import android.nfc.tech.NfcF
24
25
  import android.nfc.tech.NfcV
26
+ import android.os.Build
27
+ import android.os.Bundle
25
28
  import android.util.Log
29
+ import androidx.annotation.RequiresApi
26
30
  import com.getcapacitor.JSArray
27
31
  import com.getcapacitor.JSObject
28
32
  import com.getcapacitor.Plugin
@@ -53,6 +57,7 @@ class NFCPlugin : Plugin() {
53
57
  NfcV::class.java.name
54
58
  ))
55
59
 
60
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
56
61
  public override fun handleOnNewIntent(intent: Intent?) {
57
62
  super.handleOnNewIntent(intent)
58
63
 
@@ -66,9 +71,9 @@ class NFCPlugin : Plugin() {
66
71
  writeMode = false
67
72
  recordsBuffer = null
68
73
  }
69
- else if (ACTION_NDEF_DISCOVERED == intent.action) {
74
+ else if (ACTION_NDEF_DISCOVERED == intent.action || ACTION_TAG_DISCOVERED == intent.action || ACTION_TECH_DISCOVERED == intent.action) {
70
75
  Log.d("NFC", "READ MODE START")
71
- handleReadTag(intent)
76
+ handleReadTag(intent)
72
77
  }
73
78
  }
74
79
 
@@ -115,8 +120,28 @@ class NFCPlugin : Plugin() {
115
120
  addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
116
121
  }
117
122
 
123
+ val pendingIntentFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
124
+ PendingIntent.FLAG_MUTABLE
125
+ } else {
126
+ PendingIntent.FLAG_UPDATE_CURRENT
127
+ }
128
+
129
+ var activityOptionsBundle: Bundle? = null
130
+
131
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // API 35 (Android 15)
132
+ activityOptionsBundle = ActivityOptions.makeBasic().apply {
133
+ setPendingIntentCreatorBackgroundActivityStartMode(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
134
+ }.toBundle()
135
+ }
136
+
118
137
  val pendingIntent =
119
- PendingIntent.getActivity(this.activity, 0, intent, PendingIntent.FLAG_MUTABLE)
138
+ PendingIntent.getActivity(
139
+ this.activity,
140
+ 0,
141
+ intent,
142
+ pendingIntentFlags,
143
+ activityOptionsBundle
144
+ )
120
145
 
121
146
  val intentFilter: Array<IntentFilter> =
122
147
  arrayOf(
@@ -139,6 +164,7 @@ class NFCPlugin : Plugin() {
139
164
  )
140
165
  }
141
166
 
167
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
142
168
  private fun handleWriteTag(intent: Intent) {
143
169
  val records = recordsBuffer?.toList<JSONObject>()
144
170
  if(records != null) {
@@ -284,32 +310,127 @@ class NFCPlugin : Plugin() {
284
310
  }
285
311
  }
286
312
 
313
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
287
314
  private fun handleReadTag(intent: Intent) {
288
315
  val jsResponse = JSObject()
289
-
290
316
  val ndefMessages = JSArray()
317
+
318
+ // Get tag information regardless of NDEF content
319
+ val tag: Tag? = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG, Tag::class.java)
320
+ val tagInfo = tag?.let { extractTagInfo(it) }
321
+
322
+ // Try to obtain raw NDEF messages first (ACTION_NDEF_DISCOVERED path)
291
323
  val receivedMessages = intent.getParcelableArrayExtra(
292
324
  EXTRA_NDEF_MESSAGES,
293
325
  NdefMessage::class.java
294
326
  )
295
327
 
296
- receivedMessages?.also { rawMessages ->
297
- for (message in rawMessages) {
298
- val ndefRecords = JSArray()
299
- for (record in message.records) {
300
- val rec = JSObject()
301
- rec.put("type", String(record.type, Charsets.UTF_8))
302
- rec.put("payload", Base64.getEncoder().encodeToString(record.payload))
303
- ndefRecords.put(rec)
328
+ if (receivedMessages != null && receivedMessages.isNotEmpty()) {
329
+ // Standard NDEF-discovered path
330
+ for (message in receivedMessages) {
331
+ ndefMessages.put(ndefMessageToJS(message))
332
+ }
333
+ } else {
334
+ // For ACTION_TAG_DISCOVERED or ACTION_TECH_DISCOVERED we may still have an NDEF tag.
335
+ var added = false
336
+ if (tag != null) {
337
+ val ndef = Ndef.get(tag)
338
+ if (ndef != null) {
339
+ try {
340
+ ndef.connect()
341
+ // Prefer cached message to avoid additional IO if available
342
+ val message: NdefMessage? = ndef.cachedNdefMessage ?: try {
343
+ ndef.ndefMessage
344
+ } catch (e: Exception) { null }
345
+ if (message != null) {
346
+ ndefMessages.put(ndefMessageToJS(message))
347
+ added = true
348
+ }
349
+ } catch (e: Exception) {
350
+ Log.w("NFC", "Failed to read NDEF message from TECH/TAG intent: ${e.message}")
351
+ } finally {
352
+ try { ndef.close() } catch (_: Exception) {}
353
+ }
304
354
  }
305
355
 
306
- val msg = JSObject()
307
- msg.put("records", ndefRecords)
308
- ndefMessages.put(msg)
356
+ // If no NDEF message found, fallback to tag ID (legacy behavior)
357
+ if (!added) {
358
+ val tagId = intent.getByteArrayExtra(NfcAdapter.EXTRA_ID) ?: tag.id
359
+ val result = if (tagId != null) byteArrayToHexString(tagId) else ""
360
+ val rec = JSObject()
361
+ rec.put("type", "ID")
362
+ rec.put("payload", Base64.getEncoder().encodeToString(result.toByteArray()))
363
+ val ndefRecords = JSArray().apply { put(rec) }
364
+ val msg = JSObject().apply { put("records", ndefRecords) }
365
+ ndefMessages.put(msg)
366
+ }
309
367
  }
310
368
  }
311
369
 
312
370
  jsResponse.put("messages", ndefMessages)
371
+ // Always include tag information if available
372
+ if (tagInfo != null) {
373
+ jsResponse.put("tagInfo", tagInfo)
374
+ }
313
375
  this.notifyListeners("nfcTag", jsResponse)
314
376
  }
377
+
378
+ private fun extractTagInfo(tag: Tag): JSObject {
379
+ val tagInfo = JSObject()
380
+
381
+ // Always include UID
382
+ val uid = byteArrayToHexString(tag.id)
383
+ tagInfo.put("uid", uid)
384
+
385
+ // Include technology types
386
+ val techTypes = JSArray()
387
+ for (tech in tag.techList) {
388
+ techTypes.put(tech)
389
+ }
390
+ tagInfo.put("techTypes", techTypes)
391
+
392
+ // Try to get NDEF-specific information
393
+ val ndef = Ndef.get(tag)
394
+ if (ndef != null) {
395
+ try {
396
+ ndef.connect()
397
+ tagInfo.put("maxSize", ndef.maxSize)
398
+ tagInfo.put("isWritable", ndef.isWritable)
399
+ tagInfo.put("type", ndef.type)
400
+ } catch (e: Exception) {
401
+ Log.w("NFC", "Failed to read NDEF tag info: ${e.message}")
402
+ } finally {
403
+ try { ndef.close() } catch (_: Exception) {}
404
+ }
405
+ }
406
+
407
+ return tagInfo
408
+ }
409
+
410
+ private fun ndefMessageToJS(message: NdefMessage): JSObject {
411
+ val ndefRecords = JSArray()
412
+ for (record in message.records) {
413
+ val rec = JSObject()
414
+ rec.put("type", String(record.type, Charsets.UTF_8))
415
+ rec.put("payload", Base64.getEncoder().encodeToString(record.payload))
416
+ ndefRecords.put(rec)
417
+ }
418
+ val msg = JSObject()
419
+ msg.put("records", ndefRecords)
420
+ return msg
421
+ }
422
+
423
+ private fun byteArrayToHexString(inarray: ByteArray): String {
424
+ val hex = arrayOf("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F")
425
+ var out = ""
426
+
427
+ for (j in inarray.indices) {
428
+ val `in` = inarray[j].toInt() and 0xff
429
+ val i1 = (`in` shr 4) and 0x0f
430
+ out += hex[i1]
431
+ val i2 = `in` and 0x0f
432
+ out += hex[i2]
433
+ }
434
+ return out
435
+ }
315
436
  }