@ukeyfe/react-native-nfc-litecard 1.0.1 → 1.0.3

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/writer.d.ts CHANGED
@@ -10,13 +10,18 @@
10
10
  *
11
11
  * Based on MIFARE Ultralight AES (MF0AES(H)20) datasheet.
12
12
  */
13
- import { ResultCode, type NfcResult } from './types';
13
+ import { NfcStatusCode, type NfcResult } from './types';
14
14
  import { isNfcOperationLocked, releaseNfcOperationLock } from './nfc-core';
15
- export { ResultCode, type NfcResult, isNfcOperationLocked, releaseNfcOperationLock };
15
+ export { NfcStatusCode, type NfcResult, isNfcOperationLocked, releaseNfcOperationLock };
16
16
  /**
17
- * Initialize a blank card: write mnemonic + set password protection.
17
+ * Initialize a card: authenticate with the default password, then write
18
+ * mnemonic and set a new password.
19
+ *
20
+ * @param mnemonic BIP-39 mnemonic to write.
21
+ * @param newPassword Password to protect the card with after initialization.
22
+ * @param defaultPassword Current (factory-default) password on the card.
18
23
  */
19
- export declare function initializeCard(mnemonic: string, password: string, onCardIdentified?: () => void): Promise<NfcResult>;
24
+ export declare function initializeCard(mnemonic: string, newPassword: string, defaultPassword: string, onCardIdentified?: () => void): Promise<NfcResult>;
20
25
  /**
21
26
  * Update card: authenticate with old password, then write new mnemonic + new password.
22
27
  *
@@ -33,7 +38,9 @@ export declare function updatePassword(oldPassword: string, newPassword: string,
33
38
  /** Write a user nickname (password required for authentication). */
34
39
  export declare function writeUserNickname(password: string, nickname: string, onCardIdentified?: () => void): Promise<NfcResult>;
35
40
  /**
36
- * Reset card: wipe user data, set password to "000000".
41
+ * Reset card: wipe mnemonic data and set a new password.
37
42
  * @param password – current card password (required if protection is enabled).
43
+ * @param newPassword – password to set after reset.
44
+ * @param onCardIdentified – callback when card is identified.
38
45
  */
39
- export declare function resetCard(password?: string, onCardIdentified?: () => void): Promise<NfcResult>;
46
+ export declare function resetCard(password: string | undefined, newPassword: string, onCardIdentified?: () => void): Promise<NfcResult>;
package/dist/writer.js CHANGED
@@ -48,7 +48,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
48
48
  return (mod && mod.__esModule) ? mod : { "default": mod };
49
49
  };
50
50
  Object.defineProperty(exports, "__esModule", { value: true });
51
- exports.releaseNfcOperationLock = exports.isNfcOperationLocked = exports.ResultCode = void 0;
51
+ exports.releaseNfcOperationLock = exports.isNfcOperationLocked = exports.NfcStatusCode = void 0;
52
52
  exports.initializeCard = initializeCard;
53
53
  exports.updateCard = updateCard;
54
54
  exports.updatePassword = updatePassword;
@@ -59,7 +59,7 @@ const react_native_1 = require("react-native");
59
59
  const bip39 = __importStar(require("bip39"));
60
60
  const constants_1 = require("./constants");
61
61
  const types_1 = require("./types");
62
- Object.defineProperty(exports, "ResultCode", { enumerable: true, get: function () { return types_1.ResultCode; } });
62
+ Object.defineProperty(exports, "NfcStatusCode", { enumerable: true, get: function () { return types_1.NfcStatusCode; } });
63
63
  const utils_1 = require("./utils");
64
64
  const crypto_1 = require("./crypto");
65
65
  const nfc_core_1 = require("./nfc-core");
@@ -77,22 +77,23 @@ async function writePage(page, data) {
77
77
  await (0, nfc_core_1.transceive)([constants_1.CMD_WRITE, page, ...data]);
78
78
  }
79
79
  /**
80
- * Write data to user memory (pages 0x08–0x27).
80
+ * Write data to mnemonic area (pages 0x08–0x24).
81
+ * Does NOT touch the nickname area (pages 0x25–0x27).
81
82
  * iOS: inserts small delays and periodic keep-alive reads.
82
83
  */
83
84
  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;
85
+ const buffer = new Uint8Array(constants_1.MNEMONIC_MEMORY_SIZE);
86
+ buffer.set(data.slice(0, constants_1.MNEMONIC_MEMORY_SIZE), 0);
87
+ const totalPages = constants_1.MNEMONIC_PAGE_END - constants_1.USER_PAGE_START + 1;
87
88
  for (let i = 0; i < totalPages; i++) {
88
89
  const page = constants_1.USER_PAGE_START + i;
89
90
  const offset = i * constants_1.PAGE_SIZE;
90
91
  const pageData = Array.from(buffer.slice(offset, offset + constants_1.PAGE_SIZE));
91
92
  await writePage(page, pageData);
92
93
  if (react_native_1.Platform.OS === 'ios' && i < totalPages - 1) {
93
- await new Promise(r => setTimeout(r, 20));
94
+ await new Promise(r => setTimeout(r, constants_1.DELAY.IOS_PAGE_WRITE));
94
95
  // Keep-alive every 4 pages to prevent iOS session timeout
95
- if ((i + 1) % 4 === 0) {
96
+ if ((i + 1) % constants_1.DELAY.KEEPALIVE_FREQ === 0) {
96
97
  try {
97
98
  await react_native_nfc_manager_1.default.sendMifareCommandIOS([0x30, 0x00]);
98
99
  }
@@ -101,13 +102,6 @@ async function writeUserMemory(data) {
101
102
  }
102
103
  }
103
104
  }
104
- /** FAST_READ pages 0x08–0x27 (user memory). */
105
- async function readUserMemory() {
106
- const response = await (0, nfc_core_1.transceive)([constants_1.CMD_FAST_READ, constants_1.USER_PAGE_START, constants_1.USER_PAGE_END]);
107
- if (!response || response.length < constants_1.USER_MEMORY_SIZE)
108
- throw new Error('READ_FAILED');
109
- return new Uint8Array(response.slice(0, constants_1.USER_MEMORY_SIZE));
110
- }
111
105
  /**
112
106
  * Write a 16-byte AES key to AES_KEY0 (pages 0x30–0x33).
113
107
  * Byte order is reversed per the datasheet.
@@ -119,7 +113,7 @@ async function writeAesKey(key) {
119
113
  const pageData = [key[off + 3], key[off + 2], key[off + 1], key[off + 0]];
120
114
  await writePage(page, pageData);
121
115
  if (react_native_1.Platform.OS === 'ios' && i < 3)
122
- await new Promise(r => setTimeout(r, 10));
116
+ await new Promise(r => setTimeout(r, constants_1.DELAY.IOS_KEY_WRITE));
123
117
  }
124
118
  }
125
119
  /** Write a UTF-8 nickname into the last 3 pages of user memory. */
@@ -138,19 +132,20 @@ async function writeNicknameToCard(nickname) {
138
132
  const offset = i * constants_1.PAGE_SIZE;
139
133
  await writePage(page, Array.from(buffer.slice(offset, offset + constants_1.PAGE_SIZE)));
140
134
  if (react_native_1.Platform.OS === 'ios' && i < totalPages - 1)
141
- await new Promise(r => setTimeout(r, 100));
135
+ await new Promise(r => setTimeout(r, constants_1.DELAY.IOS_NICKNAME_WRITE));
142
136
  }
143
137
  }
144
138
  // ===========================================================================
145
139
  // Auth configuration
146
140
  // ===========================================================================
147
- /** Enable read+write protection starting from USER_PAGE_START. */
141
+ /** Enable read+write protection and secure messaging starting from USER_PAGE_START. */
148
142
  async function configureAuth() {
149
143
  const cfg0 = await readPage(constants_1.PAGE_CFG0);
144
+ cfg0[0] = cfg0[0] | constants_1.SEC_MSG_ACT_MASK; // Enable CMAC secure messaging
150
145
  cfg0[3] = constants_1.USER_PAGE_START;
151
146
  await writePage(constants_1.PAGE_CFG0, cfg0.slice(0, 4));
152
147
  if (react_native_1.Platform.OS === 'ios')
153
- await new Promise(r => setTimeout(r, 80));
148
+ await new Promise(r => setTimeout(r, constants_1.DELAY.IOS_CONFIG));
154
149
  const cfg1 = await readPage(constants_1.PAGE_CFG1);
155
150
  cfg1[0] = cfg1[0] | 0x80; // PROT bit = 1
156
151
  await writePage(constants_1.PAGE_CFG1, cfg1.slice(0, 4));
@@ -167,7 +162,7 @@ async function disableAuth() {
167
162
  cfg0[3] = 0x3c;
168
163
  await writePage(constants_1.PAGE_CFG0, cfg0.slice(0, 4));
169
164
  if (react_native_1.Platform.OS === 'ios')
170
- await new Promise(r => setTimeout(r, 80));
165
+ await new Promise(r => setTimeout(r, constants_1.DELAY.IOS_CONFIG));
171
166
  const cfg1 = await readPage(constants_1.PAGE_CFG1);
172
167
  cfg1[0] = cfg1[0] & 0x7f; // Clear PROT bit
173
168
  await writePage(constants_1.PAGE_CFG1, cfg1.slice(0, 4));
@@ -184,31 +179,7 @@ function mnemonicToEntropyWithCRC(mnemonic) {
184
179
  throw new Error('INVALID_MNEMONIC');
185
180
  const entropyHex = bip39.mnemonicToEntropy(mnemonic);
186
181
  const entropyBytes = (0, utils_1.hexToBytes)(entropyHex);
187
- let typeId;
188
- let typeStr;
189
- switch (entropyBytes.length) {
190
- case 16:
191
- typeId = constants_1.MNEMONIC_TYPE_12;
192
- typeStr = '12 words (128-bit)';
193
- break;
194
- case 20:
195
- typeId = constants_1.MNEMONIC_TYPE_15;
196
- typeStr = '15 words (160-bit)';
197
- break;
198
- case 24:
199
- typeId = constants_1.MNEMONIC_TYPE_18;
200
- typeStr = '18 words (192-bit)';
201
- break;
202
- case 28:
203
- typeId = constants_1.MNEMONIC_TYPE_21;
204
- typeStr = '21 words (224-bit)';
205
- break;
206
- case 32:
207
- typeId = constants_1.MNEMONIC_TYPE_24;
208
- typeStr = '24 words (256-bit)';
209
- break;
210
- default: throw new Error('UNSUPPORTED_MNEMONIC_LENGTH');
211
- }
182
+ const { typeId, description: typeStr } = (0, utils_1.getMnemonicTypeByEntropyLength)(entropyBytes.length);
212
183
  const dataBlock = new Uint8Array(1 + entropyBytes.length);
213
184
  dataBlock[0] = typeId;
214
185
  dataBlock.set(entropyBytes, 1);
@@ -222,46 +193,72 @@ function mnemonicToEntropyWithCRC(mnemonic) {
222
193
  // Public API
223
194
  // ===========================================================================
224
195
  /**
225
- * Initialize a blank card: write mnemonic + set password protection.
196
+ * Initialize a card: authenticate with the default password, then write
197
+ * mnemonic and set a new password.
198
+ *
199
+ * @param mnemonic BIP-39 mnemonic to write.
200
+ * @param newPassword Password to protect the card with after initialization.
201
+ * @param defaultPassword Current (factory-default) password on the card.
226
202
  */
227
- async function initializeCard(mnemonic, password, onCardIdentified) {
203
+ async function initializeCard(mnemonic, newPassword, defaultPassword, onCardIdentified) {
228
204
  try {
229
205
  await (0, nfc_core_1.acquireNfcLock)();
230
206
  }
231
207
  catch {
232
- return { code: types_1.ResultCode.UNKNOWN_ERROR, success: false };
208
+ return { code: types_1.NfcStatusCode.UNKNOWN_ERROR, success: false };
233
209
  }
210
+ let retryCountAfterPreDecrement;
234
211
  try {
235
212
  try {
236
213
  await (0, nfc_core_1.requestNfcTech)();
237
214
  }
238
215
  catch {
239
216
  (0, nfc_core_1.releaseNfcLock)();
240
- return { code: types_1.ResultCode.NFC_CONNECT_FAILED, success: false };
217
+ return { code: types_1.NfcStatusCode.NFC_CONNECT_FAILED, success: false };
241
218
  }
242
219
  try {
243
220
  const entropyResult = mnemonicToEntropyWithCRC(mnemonic);
244
- const aesKey = (0, crypto_1.passwordToAesKey)(password);
245
- const aesKeyHex = (0, utils_1.bytesToHex)(aesKey);
246
- onCardIdentified?.();
247
- await writeUserMemory(entropyResult.data);
248
- await writeAesKey(aesKey);
249
- await configureAuth();
221
+ // Decrement retry counter before auth
222
+ try {
223
+ const n = await (0, nfc_core_1.decrementRetryCountInSession)();
224
+ if (typeof n === 'number')
225
+ retryCountAfterPreDecrement = n;
226
+ }
227
+ catch (error) {
228
+ const code = (0, types_1.errorToCode)(error);
229
+ if (code === types_1.NfcStatusCode.RETRY_COUNT_EXHAUSTED) {
230
+ await (0, nfc_core_1.releaseNfcTech)();
231
+ (0, nfc_core_1.releaseNfcLock)();
232
+ return (0, types_1.nfcResultRetryCountExhausted)();
233
+ }
234
+ }
235
+ // Authenticate with factory-default password
236
+ const oldKey = (0, crypto_1.passwordToAesKey)(defaultPassword);
237
+ await (0, nfc_core_1.authenticate)(oldKey);
238
+ // Auth succeeded – reset retry counter
250
239
  try {
251
240
  await (0, nfc_core_1.writeRetryCountInSession)(constants_1.DEFAULT_PIN_RETRY_COUNT);
241
+ retryCountAfterPreDecrement = constants_1.DEFAULT_PIN_RETRY_COUNT;
252
242
  }
253
243
  catch { /* non-fatal */ }
244
+ onCardIdentified?.();
245
+ // Write mnemonic (while still authenticated)
246
+ await writeUserMemory(entropyResult.data);
247
+ // Switch to new password
248
+ const newKey = (0, crypto_1.passwordToAesKey)(newPassword);
249
+ await disableAuth();
250
+ await writeAesKey(newKey);
251
+ await configureAuth();
254
252
  await (0, nfc_core_1.releaseNfcTech)();
255
253
  (0, nfc_core_1.releaseNfcLock)();
256
254
  return {
257
- code: types_1.ResultCode.INIT_SUCCESS,
255
+ code: types_1.NfcStatusCode.INIT_SUCCESS,
258
256
  success: true,
259
257
  data: {
260
258
  type: entropyResult.type,
261
259
  entropyHex: entropyResult.entropyHex,
262
260
  rawBytes: entropyResult.rawBytes,
263
261
  crc16: entropyResult.crc16,
264
- aesKeyHex,
265
262
  retryCount: constants_1.DEFAULT_PIN_RETRY_COUNT,
266
263
  },
267
264
  };
@@ -270,7 +267,11 @@ async function initializeCard(mnemonic, password, onCardIdentified) {
270
267
  await (0, nfc_core_1.releaseNfcTech)();
271
268
  (0, nfc_core_1.releaseNfcLock)();
272
269
  const code = (0, types_1.errorToCode)(error);
273
- return { code, success: false };
270
+ return {
271
+ code,
272
+ success: false,
273
+ data: typeof retryCountAfterPreDecrement === 'number' ? { retryCount: retryCountAfterPreDecrement } : undefined,
274
+ };
274
275
  }
275
276
  }
276
277
  catch (error) {
@@ -291,7 +292,7 @@ async function updateCard(oldPassword, newPassword, newMnemonic, onCardIdentifie
291
292
  await (0, nfc_core_1.acquireNfcLock)();
292
293
  }
293
294
  catch {
294
- return { code: types_1.ResultCode.UNKNOWN_ERROR, success: false };
295
+ return { code: types_1.NfcStatusCode.UNKNOWN_ERROR, success: false };
295
296
  }
296
297
  let retryCountAfterPreDecrement;
297
298
  try {
@@ -300,10 +301,9 @@ async function updateCard(oldPassword, newPassword, newMnemonic, onCardIdentifie
300
301
  }
301
302
  catch {
302
303
  (0, nfc_core_1.releaseNfcLock)();
303
- return { code: types_1.ResultCode.NFC_CONNECT_FAILED, success: false };
304
+ return { code: types_1.NfcStatusCode.NFC_CONNECT_FAILED, success: false };
304
305
  }
305
306
  try {
306
- const entropyResult = mnemonicToEntropyWithCRC(newMnemonic);
307
307
  try {
308
308
  const n = await (0, nfc_core_1.decrementRetryCountInSession)();
309
309
  if (typeof n === 'number')
@@ -311,36 +311,38 @@ async function updateCard(oldPassword, newPassword, newMnemonic, onCardIdentifie
311
311
  }
312
312
  catch (error) {
313
313
  const code = (0, types_1.errorToCode)(error);
314
- if (code === types_1.ResultCode.RETRY_COUNT_EXHAUSTED) {
314
+ if (code === types_1.NfcStatusCode.RETRY_COUNT_EXHAUSTED) {
315
315
  await (0, nfc_core_1.releaseNfcTech)();
316
316
  (0, nfc_core_1.releaseNfcLock)();
317
317
  return (0, types_1.nfcResultRetryCountExhausted)();
318
318
  }
319
+ // Communication failure — counter was not decremented, continue to auth
319
320
  }
320
321
  const oldKey = (0, crypto_1.passwordToAesKey)(oldPassword);
321
322
  await (0, nfc_core_1.authenticate)(oldKey);
322
323
  try {
323
324
  await (0, nfc_core_1.writeRetryCountInSession)(constants_1.DEFAULT_PIN_RETRY_COUNT);
325
+ retryCountAfterPreDecrement = constants_1.DEFAULT_PIN_RETRY_COUNT;
324
326
  }
325
327
  catch { /* non-fatal */ }
326
328
  onCardIdentified?.();
327
329
  if (options?.precheckExistingMnemonic) {
328
330
  try {
329
- const data = await readUserMemory();
331
+ const data = await (0, nfc_core_1.readUserMemory)();
330
332
  const decoded = (0, utils_1.validateMnemonicPayload)(data);
331
333
  await (0, nfc_core_1.releaseNfcTech)();
332
334
  (0, nfc_core_1.releaseNfcLock)();
333
335
  return {
334
- code: types_1.ResultCode.PRECHECK_HAS_BACKUP,
336
+ code: types_1.NfcStatusCode.PRECHECK_HAS_BACKUP,
335
337
  success: true,
336
338
  data: { type: decoded.type, retryCount: constants_1.DEFAULT_PIN_RETRY_COUNT },
337
339
  };
338
340
  }
339
341
  catch (e) {
340
342
  const c = (0, types_1.errorToCode)(e);
341
- if (c === types_1.ResultCode.CHECK_EMPTY ||
342
- c === types_1.ResultCode.CRC16_CHECK_FAILED ||
343
- c === types_1.ResultCode.INVALID_CARD_DATA) {
343
+ if (c === types_1.NfcStatusCode.CHECK_EMPTY ||
344
+ c === types_1.NfcStatusCode.CRC16_CHECK_FAILED ||
345
+ c === types_1.NfcStatusCode.INVALID_CARD_DATA) {
344
346
  // No valid mnemonic on card — fall through to write
345
347
  }
346
348
  else {
@@ -350,8 +352,9 @@ async function updateCard(oldPassword, newPassword, newMnemonic, onCardIdentifie
350
352
  }
351
353
  }
352
354
  }
353
- await disableAuth();
355
+ const entropyResult = mnemonicToEntropyWithCRC(newMnemonic);
354
356
  await writeUserMemory(entropyResult.data);
357
+ await disableAuth();
355
358
  const newKey = (0, crypto_1.passwordToAesKey)(newPassword);
356
359
  const aesKeyHex = (0, utils_1.bytesToHex)(newKey);
357
360
  await writeAesKey(newKey);
@@ -359,7 +362,7 @@ async function updateCard(oldPassword, newPassword, newMnemonic, onCardIdentifie
359
362
  await (0, nfc_core_1.releaseNfcTech)();
360
363
  (0, nfc_core_1.releaseNfcLock)();
361
364
  return {
362
- code: types_1.ResultCode.WRITE_SUCCESS,
365
+ code: types_1.NfcStatusCode.WRITE_SUCCESS,
363
366
  success: true,
364
367
  data: {
365
368
  type: entropyResult.type,
@@ -394,7 +397,7 @@ async function updatePassword(oldPassword, newPassword, onCardIdentified) {
394
397
  await (0, nfc_core_1.acquireNfcLock)();
395
398
  }
396
399
  catch {
397
- return { code: types_1.ResultCode.UNKNOWN_ERROR, success: false };
400
+ return { code: types_1.NfcStatusCode.UNKNOWN_ERROR, success: false };
398
401
  }
399
402
  let retryCountAfterPreDecrement;
400
403
  try {
@@ -403,7 +406,7 @@ async function updatePassword(oldPassword, newPassword, onCardIdentified) {
403
406
  }
404
407
  catch {
405
408
  (0, nfc_core_1.releaseNfcLock)();
406
- return { code: types_1.ResultCode.NFC_CONNECT_FAILED, success: false };
409
+ return { code: types_1.NfcStatusCode.NFC_CONNECT_FAILED, success: false };
407
410
  }
408
411
  try {
409
412
  try {
@@ -413,16 +416,18 @@ async function updatePassword(oldPassword, newPassword, onCardIdentified) {
413
416
  }
414
417
  catch (error) {
415
418
  const code = (0, types_1.errorToCode)(error);
416
- if (code === types_1.ResultCode.RETRY_COUNT_EXHAUSTED) {
419
+ if (code === types_1.NfcStatusCode.RETRY_COUNT_EXHAUSTED) {
417
420
  await (0, nfc_core_1.releaseNfcTech)();
418
421
  (0, nfc_core_1.releaseNfcLock)();
419
422
  return (0, types_1.nfcResultRetryCountExhausted)();
420
423
  }
424
+ // Communication failure — counter was not decremented, continue to auth
421
425
  }
422
426
  const oldKey = (0, crypto_1.passwordToAesKey)(oldPassword);
423
427
  await (0, nfc_core_1.authenticate)(oldKey);
424
428
  try {
425
429
  await (0, nfc_core_1.writeRetryCountInSession)(constants_1.DEFAULT_PIN_RETRY_COUNT);
430
+ retryCountAfterPreDecrement = constants_1.DEFAULT_PIN_RETRY_COUNT;
426
431
  }
427
432
  catch { /* non-fatal */ }
428
433
  onCardIdentified?.();
@@ -434,7 +439,7 @@ async function updatePassword(oldPassword, newPassword, onCardIdentified) {
434
439
  await (0, nfc_core_1.releaseNfcTech)();
435
440
  (0, nfc_core_1.releaseNfcLock)();
436
441
  return {
437
- code: types_1.ResultCode.UPDATE_PASSWORD_SUCCESS,
442
+ code: types_1.NfcStatusCode.UPDATE_PASSWORD_SUCCESS,
438
443
  success: true,
439
444
  data: { aesKeyHex, retryCount: constants_1.DEFAULT_PIN_RETRY_COUNT },
440
445
  };
@@ -461,7 +466,7 @@ async function writeUserNickname(password, nickname, onCardIdentified) {
461
466
  await (0, nfc_core_1.acquireNfcLock)();
462
467
  }
463
468
  catch {
464
- return { code: types_1.ResultCode.UNKNOWN_ERROR, success: false };
469
+ return { code: types_1.NfcStatusCode.UNKNOWN_ERROR, success: false };
465
470
  }
466
471
  try {
467
472
  try {
@@ -469,11 +474,28 @@ async function writeUserNickname(password, nickname, onCardIdentified) {
469
474
  }
470
475
  catch {
471
476
  (0, nfc_core_1.releaseNfcLock)();
472
- return { code: types_1.ResultCode.NFC_CONNECT_FAILED, success: false };
477
+ return { code: types_1.NfcStatusCode.NFC_CONNECT_FAILED, success: false };
473
478
  }
474
479
  try {
480
+ // Pre-decrement retry counter before auth
481
+ try {
482
+ await (0, nfc_core_1.decrementRetryCountInSession)();
483
+ }
484
+ catch (error) {
485
+ const code = (0, types_1.errorToCode)(error);
486
+ if (code === types_1.NfcStatusCode.RETRY_COUNT_EXHAUSTED) {
487
+ await (0, nfc_core_1.releaseNfcTech)();
488
+ (0, nfc_core_1.releaseNfcLock)();
489
+ return (0, types_1.nfcResultRetryCountExhausted)();
490
+ }
491
+ }
475
492
  const aesKey = (0, crypto_1.passwordToAesKey)(password);
476
493
  await (0, nfc_core_1.authenticate)(aesKey);
494
+ // Auth succeeded – reset retry counter
495
+ try {
496
+ await (0, nfc_core_1.writeRetryCountInSession)(constants_1.DEFAULT_PIN_RETRY_COUNT);
497
+ }
498
+ catch { /* non-fatal */ }
477
499
  onCardIdentified?.();
478
500
  await disableAuth();
479
501
  await writeNicknameToCard(nickname);
@@ -481,7 +503,7 @@ async function writeUserNickname(password, nickname, onCardIdentified) {
481
503
  await (0, nfc_core_1.releaseNfcTech)();
482
504
  (0, nfc_core_1.releaseNfcLock)();
483
505
  return {
484
- code: types_1.ResultCode.WRITE_NICKNAME_SUCCESS,
506
+ code: types_1.NfcStatusCode.WRITE_NICKNAME_SUCCESS,
485
507
  success: true,
486
508
  };
487
509
  }
@@ -498,15 +520,17 @@ async function writeUserNickname(password, nickname, onCardIdentified) {
498
520
  }
499
521
  }
500
522
  /**
501
- * Reset card: wipe user data, set password to "000000".
523
+ * Reset card: wipe mnemonic data and set a new password.
502
524
  * @param password – current card password (required if protection is enabled).
525
+ * @param newPassword – password to set after reset.
526
+ * @param onCardIdentified – callback when card is identified.
503
527
  */
504
- async function resetCard(password, onCardIdentified) {
528
+ async function resetCard(password, newPassword, onCardIdentified) {
505
529
  try {
506
530
  await (0, nfc_core_1.acquireNfcLock)();
507
531
  }
508
532
  catch {
509
- return { code: types_1.ResultCode.UNKNOWN_ERROR, success: false };
533
+ return { code: types_1.NfcStatusCode.UNKNOWN_ERROR, success: false };
510
534
  }
511
535
  let retryCountAfterPreDecrement;
512
536
  try {
@@ -515,7 +539,7 @@ async function resetCard(password, onCardIdentified) {
515
539
  }
516
540
  catch {
517
541
  (0, nfc_core_1.releaseNfcLock)();
518
- return { code: types_1.ResultCode.NFC_CONNECT_FAILED, success: false };
542
+ return { code: types_1.NfcStatusCode.NFC_CONNECT_FAILED, success: false };
519
543
  }
520
544
  try {
521
545
  if (password) {
@@ -526,17 +550,19 @@ async function resetCard(password, onCardIdentified) {
526
550
  }
527
551
  catch (error) {
528
552
  const code = (0, types_1.errorToCode)(error);
529
- if (code === types_1.ResultCode.RETRY_COUNT_EXHAUSTED) {
553
+ if (code === types_1.NfcStatusCode.RETRY_COUNT_EXHAUSTED) {
530
554
  await (0, nfc_core_1.releaseNfcTech)();
531
555
  (0, nfc_core_1.releaseNfcLock)();
532
556
  return (0, types_1.nfcResultRetryCountExhausted)();
533
557
  }
558
+ // Communication failure — counter was not decremented, continue to auth
534
559
  }
535
560
  const aesKey = (0, crypto_1.passwordToAesKey)(password);
536
561
  try {
537
562
  await (0, nfc_core_1.authenticate)(aesKey);
538
563
  try {
539
564
  await (0, nfc_core_1.writeRetryCountInSession)(constants_1.DEFAULT_PIN_RETRY_COUNT);
565
+ retryCountAfterPreDecrement = constants_1.DEFAULT_PIN_RETRY_COUNT;
540
566
  }
541
567
  catch { /* non-fatal */ }
542
568
  }
@@ -556,31 +582,27 @@ async function resetCard(password, onCardIdentified) {
556
582
  }
557
583
  catch { /* non-fatal */ }
558
584
  }
559
- await disableAuth();
560
585
  }
561
586
  else {
562
587
  try {
563
- await disableAuth();
588
+ await (0, nfc_core_1.authenticate)((0, crypto_1.passwordToAesKey)(newPassword));
564
589
  }
565
590
  catch {
566
591
  throw new Error('AUTH_WRONG_PASSWORD');
567
592
  }
568
593
  onCardIdentified?.();
569
594
  }
570
- // Wipe user memory
571
- await writeUserMemory(new Uint8Array(constants_1.USER_MEMORY_SIZE));
572
- // Set default password "000000"
573
- const defaultKey = (0, crypto_1.passwordToAesKey)('000000');
574
- await writeAesKey(defaultKey);
575
- // Ensure protection is disabled
576
- try {
577
- await disableAuth();
578
- }
579
- catch { /* may already be off */ }
595
+ // Wipe mnemonic data (nickname is preserved)
596
+ await writeUserMemory(new Uint8Array(constants_1.MNEMONIC_MEMORY_SIZE));
597
+ // Set new password and keep protection enabled
598
+ const resetKey = (0, crypto_1.passwordToAesKey)(newPassword);
599
+ await disableAuth();
600
+ await writeAesKey(resetKey);
601
+ await configureAuth();
580
602
  await (0, nfc_core_1.releaseNfcTech)();
581
603
  (0, nfc_core_1.releaseNfcLock)();
582
604
  return {
583
- code: types_1.ResultCode.RESET_SUCCESS,
605
+ code: types_1.NfcStatusCode.RESET_SUCCESS,
584
606
  success: true,
585
607
  data: { retryCount: constants_1.DEFAULT_PIN_RETRY_COUNT },
586
608
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ukeyfe/react-native-nfc-litecard",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "NFC read/write for MIFARE Ultralight AES (LiteCard mnemonic storage)",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -28,6 +28,6 @@
28
28
  "files": [
29
29
  "dist",
30
30
  "README.md",
31
- "README.en.md"
31
+ "README.zh.md"
32
32
  ]
33
33
  }