@ukeyfe/react-native-nfc-litecard 1.0.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/dist/utils.js ADDED
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ /**
3
+ * Pure utility functions – no NFC or platform dependencies.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.bytesToHex = bytesToHex;
7
+ exports.hexToBytes = hexToBytes;
8
+ exports.calculateCRC16 = calculateCRC16;
9
+ exports.crc16ToBytes = crc16ToBytes;
10
+ exports.extractCRC16 = extractCRC16;
11
+ // ---------------------------------------------------------------------------
12
+ // Hex ↔ bytes
13
+ // ---------------------------------------------------------------------------
14
+ function bytesToHex(bytes) {
15
+ let hex = '';
16
+ for (let i = 0; i < bytes.length; i++) {
17
+ hex += bytes[i].toString(16).padStart(2, '0');
18
+ }
19
+ return hex;
20
+ }
21
+ function hexToBytes(hex) {
22
+ const bytes = new Uint8Array(hex.length / 2);
23
+ for (let i = 0; i < hex.length; i += 2) {
24
+ bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
25
+ }
26
+ return bytes;
27
+ }
28
+ // ---------------------------------------------------------------------------
29
+ // CRC16-Modbus
30
+ // ---------------------------------------------------------------------------
31
+ /**
32
+ * CRC16-Modbus checksum.
33
+ * Polynomial 0xA001, initial value 0xFFFF, right-shift algorithm.
34
+ */
35
+ function calculateCRC16(data) {
36
+ let crc = 0xffff;
37
+ for (let i = 0; i < data.length; i++) {
38
+ crc ^= data[i];
39
+ for (let j = 0; j < 8; j++) {
40
+ if ((crc & 0x0001) !== 0) {
41
+ crc = (crc >> 1) ^ 0xa001;
42
+ }
43
+ else {
44
+ crc = crc >> 1;
45
+ }
46
+ }
47
+ }
48
+ return crc & 0xffff;
49
+ }
50
+ /** Convert CRC16 to 2-byte little-endian array. */
51
+ function crc16ToBytes(crc16) {
52
+ return new Uint8Array([crc16 & 0xff, (crc16 >> 8) & 0xff]);
53
+ }
54
+ /**
55
+ * Extract CRC16 from the last 2 bytes of a data buffer (little-endian).
56
+ * @throws if buffer is shorter than 2 bytes.
57
+ */
58
+ function extractCRC16(data) {
59
+ if (data.length < 2) {
60
+ throw new Error('INVALID_CARD_DATA');
61
+ }
62
+ return data[data.length - 2] | (data[data.length - 1] << 8);
63
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * NFC Writer Module
3
+ *
4
+ * Public API:
5
+ * - initializeCard() – write mnemonic + set password on a blank card
6
+ * - updateCard() – update mnemonic & password (old password required)
7
+ * - updatePassword() – change password only
8
+ * - writeUserNickname() – write user nickname (password required)
9
+ * - resetCard() – wipe card and set password to "000000"
10
+ *
11
+ * Based on MIFARE Ultralight AES (MF0AES(H)20) datasheet.
12
+ */
13
+ import { ResultCode, type NfcResult } from './types';
14
+ import { isNfcOperationLocked, releaseNfcOperationLock } from './nfc-core';
15
+ export { ResultCode, type NfcResult, isNfcOperationLocked, releaseNfcOperationLock };
16
+ /**
17
+ * Initialize a blank card: write mnemonic + set password protection.
18
+ */
19
+ export declare function initializeCard(mnemonic: string, password: string, onCardIdentified?: () => void): Promise<NfcResult>;
20
+ /**
21
+ * Update card: authenticate with old password, then write new mnemonic + new password.
22
+ */
23
+ export declare function updateCard(oldPassword: string, newPassword: string, newMnemonic: string, onCardIdentified?: () => void): Promise<NfcResult>;
24
+ /** Change password only (old password required). */
25
+ export declare function updatePassword(oldPassword: string, newPassword: string, onCardIdentified?: () => void): Promise<NfcResult>;
26
+ /** Write a user nickname (password required for authentication). */
27
+ export declare function writeUserNickname(password: string, nickname: string): Promise<NfcResult>;
28
+ /**
29
+ * Reset card: wipe user data, set password to "000000".
30
+ * @param password – current card password (required if protection is enabled).
31
+ */
32
+ export declare function resetCard(password?: string, onCardIdentified?: () => void): Promise<NfcResult>;
package/dist/writer.js ADDED
@@ -0,0 +1,545 @@
1
+ "use strict";
2
+ /**
3
+ * NFC Writer Module
4
+ *
5
+ * Public API:
6
+ * - initializeCard() – write mnemonic + set password on a blank card
7
+ * - updateCard() – update mnemonic & password (old password required)
8
+ * - updatePassword() – change password only
9
+ * - writeUserNickname() – write user nickname (password required)
10
+ * - resetCard() – wipe card and set password to "000000"
11
+ *
12
+ * Based on MIFARE Ultralight AES (MF0AES(H)20) datasheet.
13
+ */
14
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ var desc = Object.getOwnPropertyDescriptor(m, k);
17
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
18
+ desc = { enumerable: true, get: function() { return m[k]; } };
19
+ }
20
+ Object.defineProperty(o, k2, desc);
21
+ }) : (function(o, m, k, k2) {
22
+ if (k2 === undefined) k2 = k;
23
+ o[k2] = m[k];
24
+ }));
25
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
26
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
27
+ }) : function(o, v) {
28
+ o["default"] = v;
29
+ });
30
+ var __importStar = (this && this.__importStar) || (function () {
31
+ var ownKeys = function(o) {
32
+ ownKeys = Object.getOwnPropertyNames || function (o) {
33
+ var ar = [];
34
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
35
+ return ar;
36
+ };
37
+ return ownKeys(o);
38
+ };
39
+ return function (mod) {
40
+ if (mod && mod.__esModule) return mod;
41
+ var result = {};
42
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
43
+ __setModuleDefault(result, mod);
44
+ return result;
45
+ };
46
+ })();
47
+ var __importDefault = (this && this.__importDefault) || function (mod) {
48
+ return (mod && mod.__esModule) ? mod : { "default": mod };
49
+ };
50
+ Object.defineProperty(exports, "__esModule", { value: true });
51
+ exports.releaseNfcOperationLock = exports.isNfcOperationLocked = exports.ResultCode = void 0;
52
+ exports.initializeCard = initializeCard;
53
+ exports.updateCard = updateCard;
54
+ exports.updatePassword = updatePassword;
55
+ exports.writeUserNickname = writeUserNickname;
56
+ exports.resetCard = resetCard;
57
+ const react_native_nfc_manager_1 = __importDefault(require("react-native-nfc-manager"));
58
+ const react_native_1 = require("react-native");
59
+ const bip39 = __importStar(require("bip39"));
60
+ const constants_1 = require("./constants");
61
+ const types_1 = require("./types");
62
+ Object.defineProperty(exports, "ResultCode", { enumerable: true, get: function () { return types_1.ResultCode; } });
63
+ const utils_1 = require("./utils");
64
+ const crypto_1 = require("./crypto");
65
+ const nfc_core_1 = require("./nfc-core");
66
+ Object.defineProperty(exports, "isNfcOperationLocked", { enumerable: true, get: function () { return nfc_core_1.isNfcOperationLocked; } });
67
+ Object.defineProperty(exports, "releaseNfcOperationLock", { enumerable: true, get: function () { return nfc_core_1.releaseNfcOperationLock; } });
68
+ // ===========================================================================
69
+ // Internal write helpers
70
+ // ===========================================================================
71
+ async function readPage(page) {
72
+ return await (0, nfc_core_1.transceive)([constants_1.CMD_READ, page]);
73
+ }
74
+ async function writePage(page, data) {
75
+ if (data.length !== 4)
76
+ throw new Error('INVALID_CARD_DATA');
77
+ await (0, nfc_core_1.transceive)([constants_1.CMD_WRITE, page, ...data]);
78
+ }
79
+ /**
80
+ * Write data to user memory (pages 0x08–0x27).
81
+ * iOS: inserts small delays and periodic keep-alive reads.
82
+ */
83
+ async function writeUserMemory(data) {
84
+ const buffer = new Uint8Array(constants_1.USER_MEMORY_SIZE);
85
+ buffer.set(data, 0);
86
+ const totalPages = constants_1.USER_PAGE_END - constants_1.USER_PAGE_START + 1;
87
+ for (let i = 0; i < totalPages; i++) {
88
+ const page = constants_1.USER_PAGE_START + i;
89
+ const offset = i * constants_1.PAGE_SIZE;
90
+ const pageData = Array.from(buffer.slice(offset, offset + constants_1.PAGE_SIZE));
91
+ await writePage(page, pageData);
92
+ if (react_native_1.Platform.OS === 'ios' && i < totalPages - 1) {
93
+ await new Promise(r => setTimeout(r, 20));
94
+ // Keep-alive every 4 pages to prevent iOS session timeout
95
+ if ((i + 1) % 4 === 0) {
96
+ try {
97
+ await react_native_nfc_manager_1.default.sendMifareCommandIOS([0x30, 0x00]);
98
+ }
99
+ catch { /* non-fatal */ }
100
+ }
101
+ }
102
+ }
103
+ }
104
+ /**
105
+ * Write a 16-byte AES key to AES_KEY0 (pages 0x30–0x33).
106
+ * Byte order is reversed per the datasheet.
107
+ */
108
+ async function writeAesKey(key) {
109
+ for (let i = 0; i < 4; i++) {
110
+ const page = constants_1.PAGE_AES_KEY0_START + i;
111
+ const off = (3 - i) * 4;
112
+ const pageData = [key[off + 3], key[off + 2], key[off + 1], key[off + 0]];
113
+ await writePage(page, pageData);
114
+ if (react_native_1.Platform.OS === 'ios' && i < 3)
115
+ await new Promise(r => setTimeout(r, 10));
116
+ }
117
+ }
118
+ /** Write a UTF-8 nickname into the last 3 pages of user memory. */
119
+ async function writeNicknameToCard(nickname) {
120
+ const safeNickname = nickname || '';
121
+ const encoder = new TextEncoder();
122
+ let nicknameBytes = encoder.encode(String(safeNickname));
123
+ if (nicknameBytes.length > constants_1.USER_NICKNAME_MAX_LENGTH) {
124
+ nicknameBytes = nicknameBytes.slice(0, constants_1.USER_NICKNAME_MAX_LENGTH);
125
+ }
126
+ const buffer = new Uint8Array(constants_1.USER_NICKNAME_MAX_LENGTH);
127
+ buffer.set(nicknameBytes, 0);
128
+ const totalPages = constants_1.USER_NICKNAME_PAGE_END - constants_1.USER_NICKNAME_PAGE_START + 1;
129
+ for (let i = 0; i < totalPages; i++) {
130
+ const page = constants_1.USER_NICKNAME_PAGE_START + i;
131
+ const offset = i * constants_1.PAGE_SIZE;
132
+ await writePage(page, Array.from(buffer.slice(offset, offset + constants_1.PAGE_SIZE)));
133
+ if (react_native_1.Platform.OS === 'ios' && i < totalPages - 1)
134
+ await new Promise(r => setTimeout(r, 100));
135
+ }
136
+ }
137
+ // ===========================================================================
138
+ // Auth configuration
139
+ // ===========================================================================
140
+ /** Enable read+write protection starting from USER_PAGE_START. */
141
+ async function configureAuth() {
142
+ const cfg0 = await readPage(constants_1.PAGE_CFG0);
143
+ cfg0[3] = constants_1.USER_PAGE_START;
144
+ await writePage(constants_1.PAGE_CFG0, cfg0.slice(0, 4));
145
+ if (react_native_1.Platform.OS === 'ios')
146
+ await new Promise(r => setTimeout(r, 80));
147
+ const cfg1 = await readPage(constants_1.PAGE_CFG1);
148
+ cfg1[0] = cfg1[0] | 0x80; // PROT bit = 1
149
+ await writePage(constants_1.PAGE_CFG1, cfg1.slice(0, 4));
150
+ }
151
+ /** Disable auth protection (AUTH0 = 0x3C, PROT = 0). */
152
+ async function disableAuth() {
153
+ if (react_native_1.Platform.OS === 'ios') {
154
+ try {
155
+ await (0, nfc_core_1.transceive)([constants_1.CMD_READ, 0x00]);
156
+ }
157
+ catch { /* keep-alive */ }
158
+ }
159
+ const cfg0 = await readPage(constants_1.PAGE_CFG0);
160
+ cfg0[3] = 0x3c;
161
+ await writePage(constants_1.PAGE_CFG0, cfg0.slice(0, 4));
162
+ if (react_native_1.Platform.OS === 'ios')
163
+ await new Promise(r => setTimeout(r, 80));
164
+ const cfg1 = await readPage(constants_1.PAGE_CFG1);
165
+ cfg1[0] = cfg1[0] & 0x7f; // Clear PROT bit
166
+ await writePage(constants_1.PAGE_CFG1, cfg1.slice(0, 4));
167
+ }
168
+ // ===========================================================================
169
+ // Mnemonic → entropy conversion
170
+ // ===========================================================================
171
+ /**
172
+ * Convert a BIP-39 mnemonic into the on-card storage format:
173
+ * [type 1B] [entropy 16-32B] [CRC16 2B]
174
+ */
175
+ function mnemonicToEntropyWithCRC(mnemonic) {
176
+ if (!bip39.validateMnemonic(mnemonic))
177
+ throw new Error('INVALID_MNEMONIC');
178
+ const entropyHex = bip39.mnemonicToEntropy(mnemonic);
179
+ const entropyBytes = (0, utils_1.hexToBytes)(entropyHex);
180
+ let typeId;
181
+ let typeStr;
182
+ switch (entropyBytes.length) {
183
+ case 16:
184
+ typeId = constants_1.MNEMONIC_TYPE_12;
185
+ typeStr = '12 words (128-bit)';
186
+ break;
187
+ case 20:
188
+ typeId = constants_1.MNEMONIC_TYPE_15;
189
+ typeStr = '15 words (160-bit)';
190
+ break;
191
+ case 24:
192
+ typeId = constants_1.MNEMONIC_TYPE_18;
193
+ typeStr = '18 words (192-bit)';
194
+ break;
195
+ case 28:
196
+ typeId = constants_1.MNEMONIC_TYPE_21;
197
+ typeStr = '21 words (224-bit)';
198
+ break;
199
+ case 32:
200
+ typeId = constants_1.MNEMONIC_TYPE_24;
201
+ typeStr = '24 words (256-bit)';
202
+ break;
203
+ default: throw new Error('UNSUPPORTED_MNEMONIC_LENGTH');
204
+ }
205
+ const dataBlock = new Uint8Array(1 + entropyBytes.length);
206
+ dataBlock[0] = typeId;
207
+ dataBlock.set(entropyBytes, 1);
208
+ const crc16 = (0, utils_1.calculateCRC16)(dataBlock);
209
+ const result = new Uint8Array(dataBlock.length + 2);
210
+ result.set(dataBlock, 0);
211
+ result.set((0, utils_1.crc16ToBytes)(crc16), dataBlock.length);
212
+ return { data: result, type: typeStr, entropyHex, rawBytes: (0, utils_1.bytesToHex)(result), crc16 };
213
+ }
214
+ // ===========================================================================
215
+ // Public API
216
+ // ===========================================================================
217
+ /**
218
+ * Initialize a blank card: write mnemonic + set password protection.
219
+ */
220
+ async function initializeCard(mnemonic, password, onCardIdentified) {
221
+ try {
222
+ await (0, nfc_core_1.acquireNfcLock)();
223
+ }
224
+ catch {
225
+ return { code: types_1.ResultCode.UNKNOWN_ERROR, success: false };
226
+ }
227
+ try {
228
+ try {
229
+ await (0, nfc_core_1.requestNfcTech)();
230
+ }
231
+ catch {
232
+ (0, nfc_core_1.releaseNfcLock)();
233
+ return { code: types_1.ResultCode.NFC_CONNECT_FAILED, success: false };
234
+ }
235
+ try {
236
+ const entropyResult = mnemonicToEntropyWithCRC(mnemonic);
237
+ const aesKey = (0, crypto_1.passwordToAesKey)(password);
238
+ const aesKeyHex = (0, utils_1.bytesToHex)(aesKey);
239
+ onCardIdentified?.();
240
+ await writeUserMemory(entropyResult.data);
241
+ await writeAesKey(aesKey);
242
+ await configureAuth();
243
+ try {
244
+ await (0, nfc_core_1.writeRetryCountInSession)(constants_1.DEFAULT_PIN_RETRY_COUNT);
245
+ }
246
+ catch { /* non-fatal */ }
247
+ await (0, nfc_core_1.releaseNfcTech)();
248
+ (0, nfc_core_1.releaseNfcLock)();
249
+ return {
250
+ code: types_1.ResultCode.INIT_SUCCESS,
251
+ success: true,
252
+ data: {
253
+ type: entropyResult.type,
254
+ entropyHex: entropyResult.entropyHex,
255
+ rawBytes: entropyResult.rawBytes,
256
+ crc16: entropyResult.crc16,
257
+ aesKeyHex,
258
+ retryCount: constants_1.DEFAULT_PIN_RETRY_COUNT,
259
+ },
260
+ };
261
+ }
262
+ catch (error) {
263
+ await (0, nfc_core_1.releaseNfcTech)();
264
+ (0, nfc_core_1.releaseNfcLock)();
265
+ const code = (0, types_1.errorToCode)(error);
266
+ return { code, success: false };
267
+ }
268
+ }
269
+ catch (error) {
270
+ (0, nfc_core_1.releaseNfcLock)();
271
+ throw error;
272
+ }
273
+ }
274
+ /**
275
+ * Update card: authenticate with old password, then write new mnemonic + new password.
276
+ */
277
+ async function updateCard(oldPassword, newPassword, newMnemonic, onCardIdentified) {
278
+ try {
279
+ await (0, nfc_core_1.acquireNfcLock)();
280
+ }
281
+ catch {
282
+ return { code: types_1.ResultCode.UNKNOWN_ERROR, success: false };
283
+ }
284
+ let retryCountAfterPreDecrement;
285
+ try {
286
+ try {
287
+ await (0, nfc_core_1.requestNfcTech)();
288
+ }
289
+ catch {
290
+ (0, nfc_core_1.releaseNfcLock)();
291
+ return { code: types_1.ResultCode.NFC_CONNECT_FAILED, success: false };
292
+ }
293
+ try {
294
+ const entropyResult = mnemonicToEntropyWithCRC(newMnemonic);
295
+ try {
296
+ const n = await (0, nfc_core_1.decrementRetryCountInSession)();
297
+ if (typeof n === 'number')
298
+ retryCountAfterPreDecrement = n;
299
+ }
300
+ catch { /* non-fatal */ }
301
+ const oldKey = (0, crypto_1.passwordToAesKey)(oldPassword);
302
+ await (0, nfc_core_1.authenticate)(oldKey);
303
+ try {
304
+ await (0, nfc_core_1.writeRetryCountInSession)(constants_1.DEFAULT_PIN_RETRY_COUNT);
305
+ }
306
+ catch { /* non-fatal */ }
307
+ onCardIdentified?.();
308
+ await disableAuth();
309
+ await writeUserMemory(entropyResult.data);
310
+ const newKey = (0, crypto_1.passwordToAesKey)(newPassword);
311
+ const aesKeyHex = (0, utils_1.bytesToHex)(newKey);
312
+ await writeAesKey(newKey);
313
+ await configureAuth();
314
+ await (0, nfc_core_1.releaseNfcTech)();
315
+ (0, nfc_core_1.releaseNfcLock)();
316
+ return {
317
+ code: types_1.ResultCode.WRITE_SUCCESS,
318
+ success: true,
319
+ data: {
320
+ type: entropyResult.type,
321
+ entropyHex: entropyResult.entropyHex,
322
+ rawBytes: entropyResult.rawBytes,
323
+ aesKeyHex,
324
+ retryCount: constants_1.DEFAULT_PIN_RETRY_COUNT,
325
+ },
326
+ };
327
+ }
328
+ catch (error) {
329
+ const isTransceiveErr = error?.message?.includes('transceive fail') ||
330
+ error?.message?.includes('TagLost');
331
+ await (0, nfc_core_1.releaseNfcTech)(isTransceiveErr);
332
+ (0, nfc_core_1.releaseNfcLock)();
333
+ const code = (0, types_1.errorToCode)(error);
334
+ return {
335
+ code,
336
+ success: false,
337
+ data: typeof retryCountAfterPreDecrement === 'number' ? { retryCount: retryCountAfterPreDecrement } : undefined,
338
+ };
339
+ }
340
+ }
341
+ catch (error) {
342
+ (0, nfc_core_1.releaseNfcLock)();
343
+ throw error;
344
+ }
345
+ }
346
+ /** Change password only (old password required). */
347
+ async function updatePassword(oldPassword, newPassword, onCardIdentified) {
348
+ try {
349
+ await (0, nfc_core_1.acquireNfcLock)();
350
+ }
351
+ catch {
352
+ return { code: types_1.ResultCode.UNKNOWN_ERROR, success: false };
353
+ }
354
+ let retryCountAfterPreDecrement;
355
+ try {
356
+ try {
357
+ await (0, nfc_core_1.requestNfcTech)();
358
+ }
359
+ catch {
360
+ (0, nfc_core_1.releaseNfcLock)();
361
+ return { code: types_1.ResultCode.NFC_CONNECT_FAILED, success: false };
362
+ }
363
+ try {
364
+ try {
365
+ const n = await (0, nfc_core_1.decrementRetryCountInSession)();
366
+ if (typeof n === 'number')
367
+ retryCountAfterPreDecrement = n;
368
+ }
369
+ catch { /* non-fatal */ }
370
+ const oldKey = (0, crypto_1.passwordToAesKey)(oldPassword);
371
+ await (0, nfc_core_1.authenticate)(oldKey);
372
+ try {
373
+ await (0, nfc_core_1.writeRetryCountInSession)(constants_1.DEFAULT_PIN_RETRY_COUNT);
374
+ }
375
+ catch { /* non-fatal */ }
376
+ onCardIdentified?.();
377
+ await disableAuth();
378
+ const newKey = (0, crypto_1.passwordToAesKey)(newPassword);
379
+ const aesKeyHex = (0, utils_1.bytesToHex)(newKey);
380
+ await writeAesKey(newKey);
381
+ await configureAuth();
382
+ await (0, nfc_core_1.releaseNfcTech)();
383
+ (0, nfc_core_1.releaseNfcLock)();
384
+ return {
385
+ code: types_1.ResultCode.UPDATE_PASSWORD_SUCCESS,
386
+ success: true,
387
+ data: { aesKeyHex, retryCount: constants_1.DEFAULT_PIN_RETRY_COUNT },
388
+ };
389
+ }
390
+ catch (error) {
391
+ await (0, nfc_core_1.releaseNfcTech)();
392
+ (0, nfc_core_1.releaseNfcLock)();
393
+ const code = (0, types_1.errorToCode)(error);
394
+ return {
395
+ code,
396
+ success: false,
397
+ data: typeof retryCountAfterPreDecrement === 'number' ? { retryCount: retryCountAfterPreDecrement } : undefined,
398
+ };
399
+ }
400
+ }
401
+ catch (error) {
402
+ (0, nfc_core_1.releaseNfcLock)();
403
+ throw error;
404
+ }
405
+ }
406
+ /** Write a user nickname (password required for authentication). */
407
+ async function writeUserNickname(password, nickname) {
408
+ try {
409
+ await (0, nfc_core_1.acquireNfcLock)();
410
+ }
411
+ catch {
412
+ return { code: types_1.ResultCode.UNKNOWN_ERROR, success: false };
413
+ }
414
+ try {
415
+ try {
416
+ await (0, nfc_core_1.requestNfcTech)();
417
+ }
418
+ catch {
419
+ (0, nfc_core_1.releaseNfcLock)();
420
+ return { code: types_1.ResultCode.NFC_CONNECT_FAILED, success: false };
421
+ }
422
+ try {
423
+ const aesKey = (0, crypto_1.passwordToAesKey)(password);
424
+ await (0, nfc_core_1.authenticate)(aesKey);
425
+ await disableAuth();
426
+ await writeNicknameToCard(nickname);
427
+ await configureAuth();
428
+ await (0, nfc_core_1.releaseNfcTech)();
429
+ (0, nfc_core_1.releaseNfcLock)();
430
+ return {
431
+ code: types_1.ResultCode.WRITE_NICKNAME_SUCCESS,
432
+ success: true,
433
+ };
434
+ }
435
+ catch (error) {
436
+ await (0, nfc_core_1.releaseNfcTech)();
437
+ (0, nfc_core_1.releaseNfcLock)();
438
+ const code = (0, types_1.errorToCode)(error);
439
+ return { code, success: false };
440
+ }
441
+ }
442
+ catch (error) {
443
+ (0, nfc_core_1.releaseNfcLock)();
444
+ throw error;
445
+ }
446
+ }
447
+ /**
448
+ * Reset card: wipe user data, set password to "000000".
449
+ * @param password – current card password (required if protection is enabled).
450
+ */
451
+ async function resetCard(password, onCardIdentified) {
452
+ try {
453
+ await (0, nfc_core_1.acquireNfcLock)();
454
+ }
455
+ catch {
456
+ return { code: types_1.ResultCode.UNKNOWN_ERROR, success: false };
457
+ }
458
+ let retryCountAfterPreDecrement;
459
+ try {
460
+ try {
461
+ await (0, nfc_core_1.requestNfcTech)();
462
+ }
463
+ catch {
464
+ (0, nfc_core_1.releaseNfcLock)();
465
+ return { code: types_1.ResultCode.NFC_CONNECT_FAILED, success: false };
466
+ }
467
+ try {
468
+ if (password) {
469
+ try {
470
+ const n = await (0, nfc_core_1.decrementRetryCountInSession)();
471
+ if (typeof n === 'number')
472
+ retryCountAfterPreDecrement = n;
473
+ }
474
+ catch { /* non-fatal */ }
475
+ const aesKey = (0, crypto_1.passwordToAesKey)(password);
476
+ try {
477
+ await (0, nfc_core_1.authenticate)(aesKey);
478
+ try {
479
+ await (0, nfc_core_1.writeRetryCountInSession)(constants_1.DEFAULT_PIN_RETRY_COUNT);
480
+ }
481
+ catch { /* non-fatal */ }
482
+ }
483
+ catch (authError) {
484
+ const msg = authError?.message || '';
485
+ if (msg.includes('AUTH_WRONG_PASSWORD') ||
486
+ (react_native_1.Platform.OS === 'ios' && (msg.includes('AUTH_INVALID_RESPONSE') || !msg.trim()))) {
487
+ throw new Error('AUTH_WRONG_PASSWORD');
488
+ }
489
+ throw authError;
490
+ }
491
+ onCardIdentified?.();
492
+ // iOS: verify connection after auth
493
+ if (react_native_1.Platform.OS === 'ios') {
494
+ try {
495
+ await readPage(constants_1.USER_PAGE_START);
496
+ }
497
+ catch { /* non-fatal */ }
498
+ }
499
+ await disableAuth();
500
+ }
501
+ else {
502
+ try {
503
+ await disableAuth();
504
+ }
505
+ catch {
506
+ throw new Error('AUTH_WRONG_PASSWORD');
507
+ }
508
+ onCardIdentified?.();
509
+ }
510
+ // Wipe user memory
511
+ await writeUserMemory(new Uint8Array(constants_1.USER_MEMORY_SIZE));
512
+ // Set default password "000000"
513
+ const defaultKey = (0, crypto_1.passwordToAesKey)('000000');
514
+ await writeAesKey(defaultKey);
515
+ // Ensure protection is disabled
516
+ try {
517
+ await disableAuth();
518
+ }
519
+ catch { /* may already be off */ }
520
+ await (0, nfc_core_1.releaseNfcTech)();
521
+ (0, nfc_core_1.releaseNfcLock)();
522
+ return {
523
+ code: types_1.ResultCode.RESET_SUCCESS,
524
+ success: true,
525
+ data: { retryCount: constants_1.DEFAULT_PIN_RETRY_COUNT },
526
+ };
527
+ }
528
+ catch (error) {
529
+ await (0, nfc_core_1.releaseNfcTech)();
530
+ (0, nfc_core_1.releaseNfcLock)();
531
+ const rawMsg = error.message || '';
532
+ const isWriteErr = rawMsg.includes('WRITE_FAILED') || rawMsg.includes('transceive');
533
+ const code = (0, types_1.errorToCode)(isWriteErr ? new Error('WRITE_FAILED') : error);
534
+ return {
535
+ code,
536
+ success: false,
537
+ data: typeof retryCountAfterPreDecrement === 'number' ? { retryCount: retryCountAfterPreDecrement } : undefined,
538
+ };
539
+ }
540
+ }
541
+ catch (error) {
542
+ (0, nfc_core_1.releaseNfcLock)();
543
+ throw error;
544
+ }
545
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@ukeyfe/react-native-nfc-litecard",
3
+ "version": "1.0.0",
4
+ "description": "NFC read/write for MIFARE Ultralight AES (LiteCard mnemonic storage)",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/bestyourwallet/react-native-nfc-litecard.git"
11
+ },
12
+ "dependencies": {
13
+ "bip39": "^3.1.0",
14
+ "crypto-js": "^4.2.0"
15
+ },
16
+ "devDependencies": {
17
+ "@types/crypto-js": "^4",
18
+ "typescript": "^5.0.0"
19
+ },
20
+ "peerDependencies": {
21
+ "react-native": ">=0.70.0",
22
+ "react-native-nfc-manager": ">=3.0.0"
23
+ },
24
+ "scripts": {
25
+ "build": "tsc",
26
+ "prepublishOnly": "npm run build"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "README.md"
31
+ ]
32
+ }