@exxili/capacitor-nfc 0.0.13 → 0.0.14
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/ExxiliCapacitorNfc.podspec +17 -17
- package/Package.swift +28 -28
- package/README.md +597 -595
- package/android/build.gradle +67 -67
- package/android/src/main/AndroidManifest.xml +2 -2
- package/android/src/main/kotlin/com/exxili/capacitornfc/MainActivity.kt +11 -11
- package/android/src/main/kotlin/com/exxili/capacitornfc/NFCPlugin.kt +492 -464
- package/dist/esm/definitions.d.ts +13 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +4 -1
- package/dist/esm/web.js +3 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +4 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +4 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/NFCPlugin/NFCPlugin.swift +181 -169
- package/ios/Sources/NFCPlugin/NFCReader.swift +507 -507
- package/ios/Sources/NFCPlugin/NFCWriter.swift +89 -89
- package/ios/Tests/NFCPluginTests/NFCPluginTests.swift +15 -15
- package/package.json +90 -90
|
@@ -1,465 +1,493 @@
|
|
|
1
|
-
package com.exxili.capacitornfc
|
|
2
|
-
|
|
3
|
-
import android.app.ActivityOptions
|
|
4
|
-
import android.app.PendingIntent
|
|
5
|
-
import android.content.Intent
|
|
6
|
-
import android.content.IntentFilter
|
|
7
|
-
import android.nfc.NdefMessage
|
|
8
|
-
import android.nfc.NdefRecord
|
|
9
|
-
import android.nfc.NfcAdapter
|
|
10
|
-
import android.nfc.NfcAdapter.ACTION_NDEF_DISCOVERED
|
|
11
|
-
import android.nfc.NfcAdapter.ACTION_TAG_DISCOVERED
|
|
12
|
-
import android.nfc.NfcAdapter.ACTION_TECH_DISCOVERED
|
|
13
|
-
import android.nfc.NfcAdapter.EXTRA_NDEF_MESSAGES
|
|
14
|
-
import android.nfc.NfcAdapter.getDefaultAdapter
|
|
15
|
-
import android.nfc.Tag
|
|
16
|
-
import android.nfc.tech.IsoDep
|
|
17
|
-
import android.nfc.tech.MifareClassic
|
|
18
|
-
import android.nfc.tech.MifareUltralight
|
|
19
|
-
import android.nfc.tech.Ndef
|
|
20
|
-
import android.nfc.tech.NdefFormatable
|
|
21
|
-
import android.nfc.tech.NfcA
|
|
22
|
-
import android.nfc.tech.NfcB
|
|
23
|
-
import android.nfc.tech.NfcBarcode
|
|
24
|
-
import android.nfc.tech.NfcF
|
|
25
|
-
import android.nfc.tech.NfcV
|
|
26
|
-
import android.os.Build
|
|
27
|
-
import android.os.Bundle
|
|
28
|
-
import android.util.Log
|
|
29
|
-
import
|
|
30
|
-
import com.getcapacitor.
|
|
31
|
-
import com.getcapacitor.
|
|
32
|
-
import com.getcapacitor.
|
|
33
|
-
import com.getcapacitor.
|
|
34
|
-
import com.getcapacitor.
|
|
35
|
-
import
|
|
36
|
-
import
|
|
37
|
-
import java.io.
|
|
38
|
-
import java.
|
|
39
|
-
import java.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
private var
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
"
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (ndef.
|
|
290
|
-
notifyListeners(
|
|
291
|
-
"nfcError",
|
|
292
|
-
JSObject().put(
|
|
293
|
-
"error",
|
|
294
|
-
"
|
|
295
|
-
)
|
|
296
|
-
)
|
|
297
|
-
return
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
)
|
|
315
|
-
}
|
|
316
|
-
catch (e:
|
|
317
|
-
Log.e("NFC", "
|
|
318
|
-
notifyListeners(
|
|
319
|
-
"nfcError",
|
|
320
|
-
JSObject().put(
|
|
321
|
-
"error",
|
|
322
|
-
"
|
|
323
|
-
)
|
|
324
|
-
)
|
|
325
|
-
}
|
|
326
|
-
catch (e:
|
|
327
|
-
Log.e("NFC", "
|
|
328
|
-
notifyListeners(
|
|
329
|
-
"nfcError",
|
|
330
|
-
JSObject().put(
|
|
331
|
-
"error",
|
|
332
|
-
"
|
|
333
|
-
)
|
|
334
|
-
)
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
val
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
val
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
1
|
+
package com.exxili.capacitornfc
|
|
2
|
+
|
|
3
|
+
import android.app.ActivityOptions
|
|
4
|
+
import android.app.PendingIntent
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.content.IntentFilter
|
|
7
|
+
import android.nfc.NdefMessage
|
|
8
|
+
import android.nfc.NdefRecord
|
|
9
|
+
import android.nfc.NfcAdapter
|
|
10
|
+
import android.nfc.NfcAdapter.ACTION_NDEF_DISCOVERED
|
|
11
|
+
import android.nfc.NfcAdapter.ACTION_TAG_DISCOVERED
|
|
12
|
+
import android.nfc.NfcAdapter.ACTION_TECH_DISCOVERED
|
|
13
|
+
import android.nfc.NfcAdapter.EXTRA_NDEF_MESSAGES
|
|
14
|
+
import android.nfc.NfcAdapter.getDefaultAdapter
|
|
15
|
+
import android.nfc.Tag
|
|
16
|
+
import android.nfc.tech.IsoDep
|
|
17
|
+
import android.nfc.tech.MifareClassic
|
|
18
|
+
import android.nfc.tech.MifareUltralight
|
|
19
|
+
import android.nfc.tech.Ndef
|
|
20
|
+
import android.nfc.tech.NdefFormatable
|
|
21
|
+
import android.nfc.tech.NfcA
|
|
22
|
+
import android.nfc.tech.NfcB
|
|
23
|
+
import android.nfc.tech.NfcBarcode
|
|
24
|
+
import android.nfc.tech.NfcF
|
|
25
|
+
import android.nfc.tech.NfcV
|
|
26
|
+
import android.os.Build
|
|
27
|
+
import android.os.Bundle
|
|
28
|
+
import android.util.Log
|
|
29
|
+
import com.getcapacitor.JSArray
|
|
30
|
+
import com.getcapacitor.JSObject
|
|
31
|
+
import com.getcapacitor.Plugin
|
|
32
|
+
import com.getcapacitor.PluginCall
|
|
33
|
+
import com.getcapacitor.PluginMethod
|
|
34
|
+
import com.getcapacitor.annotation.CapacitorPlugin
|
|
35
|
+
import org.json.JSONObject
|
|
36
|
+
import java.io.IOException
|
|
37
|
+
import java.io.UnsupportedEncodingException
|
|
38
|
+
import java.nio.charset.Charset
|
|
39
|
+
import java.util.Base64
|
|
40
|
+
|
|
41
|
+
@CapacitorPlugin(name = "NFC")
|
|
42
|
+
class NFCPlugin : Plugin() {
|
|
43
|
+
private var writeMode = false
|
|
44
|
+
private var recordsBuffer: JSArray? = null
|
|
45
|
+
|
|
46
|
+
private val techListsArray = arrayOf(arrayOf<String>(
|
|
47
|
+
IsoDep::class.java.name,
|
|
48
|
+
MifareClassic::class.java.name,
|
|
49
|
+
MifareUltralight::class.java.name,
|
|
50
|
+
Ndef::class.java.name,
|
|
51
|
+
NdefFormatable::class.java.name,
|
|
52
|
+
NfcBarcode::class.java.name,
|
|
53
|
+
NfcA::class.java.name,
|
|
54
|
+
NfcB::class.java.name,
|
|
55
|
+
NfcF::class.java.name,
|
|
56
|
+
NfcV::class.java.name
|
|
57
|
+
))
|
|
58
|
+
|
|
59
|
+
public override fun handleOnNewIntent(intent: Intent?) {
|
|
60
|
+
super.handleOnNewIntent(intent)
|
|
61
|
+
|
|
62
|
+
if (intent == null || intent.action.isNullOrBlank()) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (writeMode) {
|
|
67
|
+
Log.d("NFC", "WRITE MODE START")
|
|
68
|
+
handleWriteTag(intent)
|
|
69
|
+
writeMode = false
|
|
70
|
+
recordsBuffer = null
|
|
71
|
+
}
|
|
72
|
+
else if (ACTION_NDEF_DISCOVERED == intent.action || ACTION_TAG_DISCOVERED == intent.action || ACTION_TECH_DISCOVERED == intent.action) {
|
|
73
|
+
Log.d("NFC", "READ MODE START")
|
|
74
|
+
handleReadTag(intent)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@PluginMethod
|
|
79
|
+
fun isSupported(call: PluginCall) {
|
|
80
|
+
val adapter = NfcAdapter.getDefaultAdapter(this.activity)
|
|
81
|
+
val ret = JSObject()
|
|
82
|
+
ret.put("supported", adapter != null)
|
|
83
|
+
call.resolve(ret)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@PluginMethod
|
|
87
|
+
fun cancelWriteAndroid(call: PluginCall) {
|
|
88
|
+
this.writeMode = false
|
|
89
|
+
call.resolve()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@PluginMethod
|
|
93
|
+
fun startScan(call: PluginCall) {
|
|
94
|
+
print("startScan called")
|
|
95
|
+
call.reject("Android NFC scanning does not require 'startScan' method.")
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@PluginMethod
|
|
99
|
+
fun writeNDEF(call: PluginCall) {
|
|
100
|
+
print("writeNDEF called")
|
|
101
|
+
|
|
102
|
+
writeMode = true
|
|
103
|
+
recordsBuffer = call.getArray("records")
|
|
104
|
+
|
|
105
|
+
call.resolve()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@PluginMethod
|
|
109
|
+
fun getStatus(call: PluginCall) {
|
|
110
|
+
val adapter = NfcAdapter.getDefaultAdapter(this.activity)
|
|
111
|
+
val ret = JSObject()
|
|
112
|
+
val status = when {
|
|
113
|
+
adapter == null -> "NOT_SUPPORTED"
|
|
114
|
+
adapter.isEnabled -> "ENABLED"
|
|
115
|
+
else -> "DISABLED"
|
|
116
|
+
}
|
|
117
|
+
ret.put("status", status)
|
|
118
|
+
call.resolve(ret)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
override fun handleOnPause() {
|
|
122
|
+
super.handleOnPause()
|
|
123
|
+
getDefaultAdapter(this.activity)?.disableForegroundDispatch(this.activity)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
override fun handleOnResume() {
|
|
127
|
+
super.handleOnResume()
|
|
128
|
+
if(getDefaultAdapter(this.activity) == null) return;
|
|
129
|
+
|
|
130
|
+
val intent = Intent(context, this.activity.javaClass).apply {
|
|
131
|
+
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
val pendingIntentFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
135
|
+
PendingIntent.FLAG_MUTABLE
|
|
136
|
+
} else {
|
|
137
|
+
PendingIntent.FLAG_UPDATE_CURRENT
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
var activityOptionsBundle: Bundle? = null
|
|
141
|
+
|
|
142
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // API 35 (Android 15)
|
|
143
|
+
activityOptionsBundle = ActivityOptions.makeBasic().apply {
|
|
144
|
+
setPendingIntentCreatorBackgroundActivityStartMode(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
|
|
145
|
+
}.toBundle()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
val pendingIntent =
|
|
149
|
+
PendingIntent.getActivity(
|
|
150
|
+
this.activity,
|
|
151
|
+
0,
|
|
152
|
+
intent,
|
|
153
|
+
pendingIntentFlags,
|
|
154
|
+
activityOptionsBundle
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
val intentFilter: Array<IntentFilter> =
|
|
158
|
+
arrayOf(
|
|
159
|
+
IntentFilter(ACTION_NDEF_DISCOVERED).apply {
|
|
160
|
+
try {
|
|
161
|
+
addDataType("text/plain")
|
|
162
|
+
} catch (e: IntentFilter.MalformedMimeTypeException) {
|
|
163
|
+
throw RuntimeException("failed", e)
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
IntentFilter(ACTION_TECH_DISCOVERED),
|
|
167
|
+
IntentFilter(ACTION_TAG_DISCOVERED)
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
getDefaultAdapter(this.activity).enableForegroundDispatch(
|
|
171
|
+
this.activity,
|
|
172
|
+
pendingIntent,
|
|
173
|
+
intentFilter,
|
|
174
|
+
techListsArray
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private fun handleWriteTag(intent: Intent) {
|
|
179
|
+
val records = recordsBuffer?.toList<JSONObject>()
|
|
180
|
+
if(records != null) {
|
|
181
|
+
val ndefRecords = mutableListOf<NdefRecord>()
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
for (record in records) {
|
|
185
|
+
val payload = record.getJSONArray("payload")
|
|
186
|
+
val type: String? = record.getString("type")
|
|
187
|
+
|
|
188
|
+
if (payload.length() == 0 || type == null) {
|
|
189
|
+
notifyListeners(
|
|
190
|
+
"nfcError",
|
|
191
|
+
JSObject().put(
|
|
192
|
+
"error",
|
|
193
|
+
"Invalid record: payload or type is missing."
|
|
194
|
+
)
|
|
195
|
+
)
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
val payloadBytes = ByteArray(payload.length())
|
|
200
|
+
for(i in 0 until payload.length()) {
|
|
201
|
+
payloadBytes[i] = payload.getInt(i).toByte()
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
val (tnf, typeBytes) = when {
|
|
205
|
+
type == "T" || type == "U" -> Pair(
|
|
206
|
+
NdefRecord.TNF_WELL_KNOWN,
|
|
207
|
+
type.toByteArray(Charsets.UTF_8)
|
|
208
|
+
)
|
|
209
|
+
type.contains("/") -> Pair(
|
|
210
|
+
NdefRecord.TNF_MIME_MEDIA,
|
|
211
|
+
type.toByteArray(Charsets.US_ASCII)
|
|
212
|
+
)
|
|
213
|
+
else -> Pair(
|
|
214
|
+
NdefRecord.TNF_EXTERNAL_TYPE,
|
|
215
|
+
type.toByteArray(Charsets.UTF_8)
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
val record = if (tnf == NdefRecord.TNF_MIME_MEDIA) {
|
|
220
|
+
try {
|
|
221
|
+
NdefRecord.createMime(type, payloadBytes)
|
|
222
|
+
} catch (e: IllegalArgumentException) {
|
|
223
|
+
notifyListeners(
|
|
224
|
+
"nfcError",
|
|
225
|
+
JSObject().put(
|
|
226
|
+
"error",
|
|
227
|
+
"Invalid MIME type for record"
|
|
228
|
+
)
|
|
229
|
+
)
|
|
230
|
+
return
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
NdefRecord(
|
|
234
|
+
tnf,
|
|
235
|
+
typeBytes,
|
|
236
|
+
ByteArray(0),
|
|
237
|
+
payloadBytes
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
ndefRecords.add(record)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
val ndefMessage = NdefMessage(ndefRecords.toTypedArray())
|
|
245
|
+
val tag = getTagFromIntent(intent)
|
|
246
|
+
var ndef = Ndef.get(tag)
|
|
247
|
+
|
|
248
|
+
if (ndef == null) {
|
|
249
|
+
val formatable = NdefFormatable.get(tag)
|
|
250
|
+
if (formatable != null) {
|
|
251
|
+
try {
|
|
252
|
+
formatable.connect()
|
|
253
|
+
val mimeRecord = NdefRecord.createMime("text/plain", "INIT".toByteArray(
|
|
254
|
+
Charset.forName("US-ASCII")))
|
|
255
|
+
val msg = NdefMessage(mimeRecord)
|
|
256
|
+
formatable.format(msg)
|
|
257
|
+
// Success!
|
|
258
|
+
// Emit event to Capacitor plugin for success
|
|
259
|
+
println("Successfully formatted and wrote NDEF message to tag!")
|
|
260
|
+
} catch (e: IOException) {
|
|
261
|
+
// Error connecting or formatting
|
|
262
|
+
// Emit event to Capacitor plugin for error
|
|
263
|
+
println("Error formatting or writing to NDEF-formatable tag: ${e.message}")
|
|
264
|
+
} catch (e: Exception) { // Catch other potential exceptions during format, like TagLostException
|
|
265
|
+
println("Error during NDEF formatting: ${e.message}")
|
|
266
|
+
} finally {
|
|
267
|
+
try {
|
|
268
|
+
formatable.close()
|
|
269
|
+
} catch (e: IOException) {
|
|
270
|
+
println("Error closing NdefFormatable connection: ${e.message}")
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
ndef = Ndef.get(formatable.tag)
|
|
275
|
+
} else {
|
|
276
|
+
notifyListeners(
|
|
277
|
+
"nfcError",
|
|
278
|
+
JSObject().put(
|
|
279
|
+
"error",
|
|
280
|
+
"Tag does not support NDEF writing."
|
|
281
|
+
)
|
|
282
|
+
)
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
ndef.use { // Use block ensures ndef.close() is called
|
|
288
|
+
ndef.connect()
|
|
289
|
+
if (!ndef.isWritable) {
|
|
290
|
+
notifyListeners(
|
|
291
|
+
"nfcError",
|
|
292
|
+
JSObject().put(
|
|
293
|
+
"error",
|
|
294
|
+
"NFC tag is not writable"
|
|
295
|
+
)
|
|
296
|
+
)
|
|
297
|
+
return
|
|
298
|
+
}
|
|
299
|
+
if (ndef.maxSize < ndefMessage.toByteArray().size) {
|
|
300
|
+
notifyListeners(
|
|
301
|
+
"nfcError",
|
|
302
|
+
JSObject().put(
|
|
303
|
+
"error",
|
|
304
|
+
"Message too large for this NFC Tag (max ${ndef.maxSize} bytes)."
|
|
305
|
+
)
|
|
306
|
+
)
|
|
307
|
+
return
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
ndef.writeNdefMessage(ndefMessage)
|
|
311
|
+
Log.d("NFC", "NDEF message successfully written to tag.")
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
notifyListeners("nfcWriteSuccess", JSObject().put("success", true))
|
|
315
|
+
}
|
|
316
|
+
catch (e: UnsupportedEncodingException) {
|
|
317
|
+
Log.e("NFC", "Encoding error during NDEF record creation: ${e.message}")
|
|
318
|
+
notifyListeners(
|
|
319
|
+
"nfcError",
|
|
320
|
+
JSObject().put(
|
|
321
|
+
"error",
|
|
322
|
+
"Encoding error: ${e.message}"
|
|
323
|
+
)
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
catch (e: IOException) {
|
|
327
|
+
Log.e("NFC", "I/O error during NFC write: ${e.message}")
|
|
328
|
+
notifyListeners(
|
|
329
|
+
"nfcError",
|
|
330
|
+
JSObject().put(
|
|
331
|
+
"error",
|
|
332
|
+
"NFC I/O error: ${e.message}"
|
|
333
|
+
)
|
|
334
|
+
)
|
|
335
|
+
}
|
|
336
|
+
catch (e: Exception) {
|
|
337
|
+
Log.e("NFC", "Error writing NDEF message: ${e.message}", e)
|
|
338
|
+
notifyListeners(
|
|
339
|
+
"nfcError",
|
|
340
|
+
JSObject().put(
|
|
341
|
+
"error",
|
|
342
|
+
"Failed to write NDEF message: ${e.message}"
|
|
343
|
+
)
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
notifyListeners("nfcError", JSObject().put("error", "Failed to write NFC tag"))
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
private fun handleReadTag(intent: Intent) {
|
|
353
|
+
val jsResponse = JSObject()
|
|
354
|
+
val ndefMessages = JSArray()
|
|
355
|
+
|
|
356
|
+
// Get tag information regardless of NDEF content
|
|
357
|
+
val tag: Tag? = getTagFromIntent(intent)
|
|
358
|
+
val tagInfo = tag?.let { extractTagInfo(it) }
|
|
359
|
+
|
|
360
|
+
// Try to obtain raw NDEF messages first (ACTION_NDEF_DISCOVERED path)
|
|
361
|
+
val receivedMessages = getNdefMessagesFromIntent(intent)
|
|
362
|
+
|
|
363
|
+
if (receivedMessages != null && receivedMessages.isNotEmpty()) {
|
|
364
|
+
// Standard NDEF-discovered path
|
|
365
|
+
for (message in receivedMessages) {
|
|
366
|
+
ndefMessages.put(ndefMessageToJS(message))
|
|
367
|
+
}
|
|
368
|
+
} else {
|
|
369
|
+
// For ACTION_TAG_DISCOVERED or ACTION_TECH_DISCOVERED we may still have an NDEF tag.
|
|
370
|
+
var added = false
|
|
371
|
+
if (tag != null) {
|
|
372
|
+
val ndef = Ndef.get(tag)
|
|
373
|
+
if (ndef != null) {
|
|
374
|
+
try {
|
|
375
|
+
ndef.connect()
|
|
376
|
+
// Prefer cached message to avoid additional IO if available
|
|
377
|
+
val message: NdefMessage? = ndef.cachedNdefMessage ?: try {
|
|
378
|
+
ndef.ndefMessage
|
|
379
|
+
} catch (e: Exception) { null }
|
|
380
|
+
if (message != null) {
|
|
381
|
+
ndefMessages.put(ndefMessageToJS(message))
|
|
382
|
+
added = true
|
|
383
|
+
}
|
|
384
|
+
} catch (e: Exception) {
|
|
385
|
+
Log.w("NFC", "Failed to read NDEF message from TECH/TAG intent: ${e.message}")
|
|
386
|
+
} finally {
|
|
387
|
+
try { ndef.close() } catch (_: Exception) {}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// If no NDEF message found, fallback to tag ID (legacy behavior)
|
|
392
|
+
if (!added) {
|
|
393
|
+
val tagId = intent.getByteArrayExtra(NfcAdapter.EXTRA_ID) ?: tag.id
|
|
394
|
+
val result = if (tagId != null) byteArrayToHexString(tagId) else ""
|
|
395
|
+
val rec = JSObject()
|
|
396
|
+
rec.put("type", "ID")
|
|
397
|
+
rec.put("payload", Base64.getEncoder().encodeToString(result.toByteArray()))
|
|
398
|
+
val ndefRecords = JSArray().apply { put(rec) }
|
|
399
|
+
val msg = JSObject().apply { put("records", ndefRecords) }
|
|
400
|
+
ndefMessages.put(msg)
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
jsResponse.put("messages", ndefMessages)
|
|
406
|
+
// Always include tag information if available
|
|
407
|
+
if (tagInfo != null) {
|
|
408
|
+
jsResponse.put("tagInfo", tagInfo)
|
|
409
|
+
}
|
|
410
|
+
this.notifyListeners("nfcTag", jsResponse)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
private fun extractTagInfo(tag: Tag): JSObject {
|
|
414
|
+
val tagInfo = JSObject()
|
|
415
|
+
|
|
416
|
+
// Always include UID
|
|
417
|
+
val uid = byteArrayToHexString(tag.id)
|
|
418
|
+
tagInfo.put("uid", uid)
|
|
419
|
+
|
|
420
|
+
// Include technology types
|
|
421
|
+
val techTypes = JSArray()
|
|
422
|
+
for (tech in tag.techList) {
|
|
423
|
+
techTypes.put(tech)
|
|
424
|
+
}
|
|
425
|
+
tagInfo.put("techTypes", techTypes)
|
|
426
|
+
|
|
427
|
+
// Try to get NDEF-specific information
|
|
428
|
+
val ndef = Ndef.get(tag)
|
|
429
|
+
if (ndef != null) {
|
|
430
|
+
try {
|
|
431
|
+
ndef.connect()
|
|
432
|
+
tagInfo.put("maxSize", ndef.maxSize)
|
|
433
|
+
tagInfo.put("isWritable", ndef.isWritable)
|
|
434
|
+
tagInfo.put("type", ndef.type)
|
|
435
|
+
} catch (e: Exception) {
|
|
436
|
+
Log.w("NFC", "Failed to read NDEF tag info: ${e.message}")
|
|
437
|
+
} finally {
|
|
438
|
+
try { ndef.close() } catch (_: Exception) {}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return tagInfo
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
private fun ndefMessageToJS(message: NdefMessage): JSObject {
|
|
446
|
+
val ndefRecords = JSArray()
|
|
447
|
+
for (record in message.records) {
|
|
448
|
+
val rec = JSObject()
|
|
449
|
+
rec.put("type", String(record.type, Charsets.UTF_8))
|
|
450
|
+
rec.put("payload", Base64.getEncoder().encodeToString(record.payload))
|
|
451
|
+
ndefRecords.put(rec)
|
|
452
|
+
}
|
|
453
|
+
val msg = JSObject()
|
|
454
|
+
msg.put("records", ndefRecords)
|
|
455
|
+
return msg
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Intent.getParcelableExtra(String, Class) and getParcelableArrayExtra(String, Class)
|
|
459
|
+
// only exist since API 33 (Android 13). Calling them on older devices throws
|
|
460
|
+
// NoSuchMethodError and crashes the app, so fall back to the legacy overloads.
|
|
461
|
+
private fun getTagFromIntent(intent: Intent): Tag? {
|
|
462
|
+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
463
|
+
intent.getParcelableExtra(NfcAdapter.EXTRA_TAG, Tag::class.java)
|
|
464
|
+
} else {
|
|
465
|
+
@Suppress("DEPRECATION")
|
|
466
|
+
intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
private fun getNdefMessagesFromIntent(intent: Intent): Array<NdefMessage>? {
|
|
471
|
+
val raw = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
472
|
+
intent.getParcelableArrayExtra(EXTRA_NDEF_MESSAGES, NdefMessage::class.java)
|
|
473
|
+
} else {
|
|
474
|
+
@Suppress("DEPRECATION")
|
|
475
|
+
intent.getParcelableArrayExtra(EXTRA_NDEF_MESSAGES)
|
|
476
|
+
} ?: return null
|
|
477
|
+
return raw.mapNotNull { it as? NdefMessage }.toTypedArray()
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
private fun byteArrayToHexString(inarray: ByteArray): String {
|
|
481
|
+
val hex = arrayOf("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F")
|
|
482
|
+
var out = ""
|
|
483
|
+
|
|
484
|
+
for (j in inarray.indices) {
|
|
485
|
+
val `in` = inarray[j].toInt() and 0xff
|
|
486
|
+
val i1 = (`in` shr 4) and 0x0f
|
|
487
|
+
out += hex[i1]
|
|
488
|
+
val i2 = `in` and 0x0f
|
|
489
|
+
out += hex[i2]
|
|
490
|
+
}
|
|
491
|
+
return out
|
|
492
|
+
}
|
|
465
493
|
}
|