@ukeyfe/react-native-nfc-litecard 1.0.0 → 1.0.2

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.
@@ -1,6 +1,6 @@
1
1
  # @ukeyfe/react-native-nfc-litecard
2
2
 
3
- English | [中文](./README.md)
3
+ [English](./README.md) | 中文
4
4
 
5
5
  React Native NFC read/write library for **MIFARE Ultralight AES** (MF0AES(H)20), designed for LiteCard mnemonic storage.
6
6
 
@@ -19,7 +19,7 @@ React Native NFC read/write library for **MIFARE Ultralight AES** (MF0AES(H)20),
19
19
  | Update card | `updateCard()` | Update mnemonic & password (old password required) |
20
20
  | Change password | `updatePassword()` | Change password only (old password required) |
21
21
  | Write nickname | `writeUserNickname()` | Write user nickname to card |
22
- | Reset card | `resetCard()` | Wipe data, reset password to "000000" |
22
+ | Reset card | `resetCard()` | Wipe mnemonic data, set a new password |
23
23
 
24
24
  ## Installation
25
25
 
@@ -55,36 +55,51 @@ import {
55
55
 
56
56
  ## API Reference
57
57
 
58
- ### `checkCard(onCardIdentified?)`
58
+ ### `checkCard(password?, onCardIdentified?)`
59
59
 
60
60
  Detect card status (empty / has data).
61
61
 
62
+ **Without password (quick probe):**
62
63
  ```typescript
63
64
  const result = await checkCard();
64
65
  if (result.code === ResultCode.CHECK_EMPTY) {
65
66
  // Empty card – ready to initialize
66
67
  } else if (result.code === ResultCode.CHECK_HAS_DATA) {
67
- // Has data need password to read or update
68
+ // Has data (or read-protected, cannot determine)
69
+ }
70
+ ```
71
+
72
+ **With password (authenticated deep check, for read-protected cards):**
73
+ ```typescript
74
+ const result = await checkCard('password');
75
+ if (result.code === ResultCode.CHECK_EMPTY) {
76
+ // Empty card – ready to write
77
+ } else if (result.code === ResultCode.CHECK_HAS_DATA) {
78
+ // Valid mnemonic backup exists, type: result.data?.type
79
+ } else if (result.code === ResultCode.AUTH_WRONG_PASSWORD) {
80
+ // Wrong password
68
81
  }
69
82
  ```
70
83
 
71
84
  **Parameters:**
72
85
  | Parameter | Type | Required | Description |
73
86
  |-----------|------|----------|-------------|
74
- | `onCardIdentified` | `() => void` | No | Callback after card is identified |
87
+ | `password` | `string` | No | Card protection password. If omitted, the library reads directly (suited for unencrypted cards). If provided, AES authentication runs first, then full data is read and CRC16 is verified (suited for read-protected cards; more accurate). |
88
+ | `onCardIdentified` | `() => void` | No | Called after the NFC session is established; use for UI such as “card detected”. |
75
89
 
76
90
  **Result codes:**
77
91
  | Code | Meaning |
78
92
  |------|---------|
79
- | `ResultCode.CHECK_EMPTY` (10104) | Empty card |
80
- | `ResultCode.CHECK_HAS_DATA` (10105) | Card has data |
81
- | `ResultCode.NFC_CONNECT_FAILED` (40001) | NFC connection failed |
93
+ | `ResultCode.CHECK_EMPTY` (10104) | Empty card – no mnemonic data |
94
+ | `ResultCode.CHECK_HAS_DATA` (10105) | Card has data. When a password is supplied, `data.type` contains the mnemonic type (e.g. `"12 words (128-bit)"`). |
95
+ | `ResultCode.AUTH_WRONG_PASSWORD` (40002) | Wrong password (only possible when a password is provided) |
96
+ | `ResultCode.NFC_CONNECT_FAILED` (40001) | NFC connection failed – card not tapped or device unsupported |
82
97
 
83
98
  ---
84
99
 
85
100
  ### `readMnemonic(password, onCardIdentified?)`
86
101
 
87
- Read mnemonic from a password-protected card.
102
+ Read the mnemonic (password authentication required). The retry counter is decremented before authentication and reset to 10 after a successful authentication.
88
103
 
89
104
  ```typescript
90
105
  const result = await readMnemonic('your-password');
@@ -99,24 +114,24 @@ if (result.success) {
99
114
  **Parameters:**
100
115
  | Parameter | Type | Required | Description |
101
116
  |-----------|------|----------|-------------|
102
- | `password` | `string` | Yes | Card protection password |
103
- | `onCardIdentified` | `() => void` | No | Callback after successful authentication |
117
+ | `password` | `string` | Yes | Card protection password for AES-128 authentication |
118
+ | `onCardIdentified` | `() => void` | No | Called after successful authentication and before reading data; use for UI such as “reading”. |
104
119
 
105
120
  **Returned `data` fields:**
106
- | Field | Description |
107
- |-------|-------------|
108
- | `mnemonic` | BIP-39 mnemonic phrase |
109
- | `type` | Mnemonic type (e.g. "12 words (128-bit)") |
110
- | `entropyHex` | Entropy as hex string |
111
- | `rawBytes` | Raw data as hex string |
112
- | `nickname` | User nickname (if set) |
113
- | `retryCount` | Retry count after successful reset |
121
+ | Field | Type | Description |
122
+ |-------|------|-------------|
123
+ | `mnemonic` | `string` | BIP-39 mnemonic (e.g. `"abandon abandon ... about"`) |
124
+ | `type` | `string` | Mnemonic type (e.g. `"12 words (128-bit)"`, `"24 words (256-bit)"`) |
125
+ | `entropyHex` | `string` | Entropy as a hexadecimal string |
126
+ | `rawBytes` | `string` | Hex string of raw on-card data (type + entropy) |
127
+ | `nickname` | `string` | User nickname if set on the card |
128
+ | `retryCount` | `number` | Retry count after successful authentication (normally 10) |
114
129
 
115
130
  ---
116
131
 
117
132
  ### `initializeCard(mnemonic, password, onCardIdentified?)`
118
133
 
119
- Initialize a blank card: write mnemonic + set password protection.
134
+ Initialize a blank card: convert the mnemonic to BIP-39 entropy, write it to the card, enable AES password protection, and require read/write authentication.
120
135
 
121
136
  ```typescript
122
137
  const result = await initializeCard(
@@ -131,15 +146,15 @@ if (result.code === ResultCode.INIT_SUCCESS) {
131
146
  **Parameters:**
132
147
  | Parameter | Type | Required | Description |
133
148
  |-----------|------|----------|-------------|
134
- | `mnemonic` | `string` | Yes | BIP-39 mnemonic (12/15/18/21/24 words) |
135
- | `password` | `string` | Yes | Protection password to set |
136
- | `onCardIdentified` | `() => void` | No | Callback before writing begins |
149
+ | `mnemonic` | `string` | Yes | BIP-39 mnemonic; supports 12/15/18/21/24 words. The library converts to entropy + CRC16 and writes to the card. |
150
+ | `password` | `string` | Yes | Protection password to set; used to derive the AES-128 key written to the card. |
151
+ | `onCardIdentified` | `() => void` | No | Called after NFC connection is established and before writing begins; use for UI such as “writing”. |
137
152
 
138
153
  ---
139
154
 
140
- ### `updateCard(oldPassword, newPassword, newMnemonic, onCardIdentified?)`
155
+ ### `updateCard(oldPassword, newPassword, newMnemonic, onCardIdentified?, options?)`
141
156
 
142
- Update card: authenticate with old password, then write new mnemonic + new password.
157
+ Update the card: authenticate with the old password, then write the new mnemonic and new password. The retry counter is decremented automatically before authentication.
143
158
 
144
159
  ```typescript
145
160
  const result = await updateCard('old-password', 'new-password', 'new mnemonic words ...');
@@ -148,19 +163,34 @@ if (result.code === ResultCode.WRITE_SUCCESS) {
148
163
  }
149
164
  ```
150
165
 
166
+ **Pre-check for existing backup:**
167
+
168
+ ```typescript
169
+ const result = await updateCard('old-password', 'new-password', 'mnemonic ...', undefined, {
170
+ precheckExistingMnemonic: true,
171
+ });
172
+ if (result.code === ResultCode.PRECHECK_HAS_BACKUP) {
173
+ // Card already has a valid mnemonic backup; write was skipped
174
+ console.log('Backup exists, type:', result.data?.type);
175
+ } else if (result.code === ResultCode.WRITE_SUCCESS) {
176
+ console.log('Write successful');
177
+ }
178
+ ```
179
+
151
180
  **Parameters:**
152
181
  | Parameter | Type | Required | Description |
153
182
  |-----------|------|----------|-------------|
154
- | `oldPassword` | `string` | Yes | Current password |
155
- | `newPassword` | `string` | Yes | New password |
156
- | `newMnemonic` | `string` | Yes | New mnemonic |
157
- | `onCardIdentified` | `() => void` | No | Callback after successful authentication |
183
+ | `oldPassword` | `string` | Yes | Current card password for AES authentication |
184
+ | `newPassword` | `string` | Yes | New password; the card will be protected with this password after the write |
185
+ | `newMnemonic` | `string` | Yes | New BIP-39 mnemonic (12/15/18/21/24 words) |
186
+ | `onCardIdentified` | `() => void` | No | Called after successful authentication; use for UI such as “writing”. |
187
+ | `options.precheckExistingMnemonic` | `boolean` | No | When `true`, after authentication the library reads card data and verifies CRC16. If a valid mnemonic already exists, returns `PRECHECK_HAS_BACKUP` and does not write. |
158
188
 
159
189
  ---
160
190
 
161
191
  ### `updatePassword(oldPassword, newPassword, onCardIdentified?)`
162
192
 
163
- Change password only, without modifying mnemonic data.
193
+ Change the password only; mnemonic data on the card is unchanged. The retry counter is decremented automatically before authentication.
164
194
 
165
195
  ```typescript
166
196
  const result = await updatePassword('old-password', 'new-password');
@@ -169,11 +199,18 @@ if (result.code === ResultCode.UPDATE_PASSWORD_SUCCESS) {
169
199
  }
170
200
  ```
171
201
 
202
+ **Parameters:**
203
+ | Parameter | Type | Required | Description |
204
+ |-----------|------|----------|-------------|
205
+ | `oldPassword` | `string` | Yes | Current card password for AES authentication |
206
+ | `newPassword` | `string` | Yes | New password; the card will use this password after the change |
207
+ | `onCardIdentified` | `() => void` | No | Called after successful authentication |
208
+
172
209
  ---
173
210
 
174
- ### `writeUserNickname(password, nickname)`
211
+ ### `writeUserNickname(password, nickname, onCardIdentified?)`
175
212
 
176
- Write a user nickname to the card (max 12 bytes, UTF-8 encoded).
213
+ Write a user nickname to the card. The nickname is UTF-8 encoded, max 12 bytes (longer input is truncated).
177
214
 
178
215
  ```typescript
179
216
  const result = await writeUserNickname('your-password', 'MyCard');
@@ -182,11 +219,18 @@ if (result.code === ResultCode.WRITE_NICKNAME_SUCCESS) {
182
219
  }
183
220
  ```
184
221
 
222
+ **Parameters:**
223
+ | Parameter | Type | Required | Description |
224
+ |-----------|------|----------|-------------|
225
+ | `password` | `string` | Yes | Card protection password for AES authentication |
226
+ | `nickname` | `string` | Yes | User nickname; UTF-8 max 12 bytes (roughly 4 CJK characters or 12 Latin letters) |
227
+ | `onCardIdentified` | `() => void` | No | Called after successful authentication |
228
+
185
229
  ---
186
230
 
187
- ### `readUserNickname(password?)`
231
+ ### `readUserNickname(password?, onCardIdentified?)`
188
232
 
189
- Read user nickname from the card. Supply password if read-protection is enabled.
233
+ Read the user nickname from the card.
190
234
 
191
235
  ```typescript
192
236
  const result = await readUserNickname('your-password');
@@ -195,26 +239,39 @@ if (result.success) {
195
239
  }
196
240
  ```
197
241
 
242
+ **Parameters:**
243
+ | Parameter | Type | Required | Description |
244
+ |-----------|------|----------|-------------|
245
+ | `password` | `string` | No | Card password. Required if read protection is enabled (`PROT=1`); otherwise optional. |
246
+ | `onCardIdentified` | `() => void` | No | Called after successful authentication |
247
+
198
248
  ---
199
249
 
200
- ### `resetCard(password?, onCardIdentified?)`
250
+ ### `resetCard(password, newPassword, onCardIdentified?)`
201
251
 
202
- Reset card: wipe all user data, set password to `"000000"`.
252
+ Reset the card: wipe mnemonic data, set a new password, and disable read/write protection. Nickname is preserved.
203
253
 
204
254
  ```typescript
205
- const result = await resetCard('your-password');
255
+ const result = await resetCard('old-password', 'new-password');
206
256
  if (result.code === ResultCode.RESET_SUCCESS) {
207
257
  console.log('Reset successful');
208
258
  }
209
259
  ```
210
260
 
211
- > ⚠️ This operation is irreversible. Use with caution.
261
+ > ⚠️ This operation is irreversible; mnemonic data on the card is permanently erased.
262
+
263
+ **Parameters:**
264
+ | Parameter | Type | Required | Description |
265
+ |-----------|------|----------|-------------|
266
+ | `password` | `string \| undefined` | No | Current card password. Required if the card is protected; pass `undefined` otherwise. The retry counter is decremented automatically before authentication. |
267
+ | `newPassword` | `string` | Yes | Password to set after reset. |
268
+ | `onCardIdentified` | `() => void` | No | Called after successful authentication |
212
269
 
213
270
  ---
214
271
 
215
- ### `readMnemonicRetryCount()`
272
+ ### `readMnemonicRetryCount(onCardIdentified?)`
216
273
 
217
- Read the current PIN retry counter value.
274
+ Read the PIN retry counter on the card. No password authentication is required (the counter page is outside the protected area).
218
275
 
219
276
  ```typescript
220
277
  const result = await readMnemonicRetryCount();
@@ -223,16 +280,26 @@ if (result.success) {
223
280
  }
224
281
  ```
225
282
 
283
+ **Parameters:**
284
+ | Parameter | Type | Required | Description |
285
+ |-----------|------|----------|-------------|
286
+ | `onCardIdentified` | `() => void` | No | Called after the NFC session is established |
287
+
226
288
  ---
227
289
 
228
- ### `resetRetryCountTo10()`
290
+ ### `resetRetryCountTo10(onCardIdentified?)`
229
291
 
230
- Reset the PIN retry counter to its default value (10).
292
+ Reset the PIN retry counter to its default value (10). No password authentication is required.
231
293
 
232
294
  ```typescript
233
295
  const result = await resetRetryCountTo10();
234
296
  ```
235
297
 
298
+ **Parameters:**
299
+ | Parameter | Type | Required | Description |
300
+ |-----------|------|----------|-------------|
301
+ | `onCardIdentified` | `() => void` | No | Called after the NFC session is established |
302
+
236
303
  ---
237
304
 
238
305
  ### NFC Lock Management
@@ -299,6 +366,9 @@ switch (result.code) {
299
366
  case ResultCode.READ_TIMEOUT:
300
367
  alert('Read timeout – remove and re-tap the card');
301
368
  break;
369
+ case ResultCode.RETRY_COUNT_EXHAUSTED:
370
+ alert('Retry count exhausted – card is locked');
371
+ break;
302
372
  default:
303
373
  alert('Operation failed');
304
374
  }
@@ -320,6 +390,7 @@ switch (result.code) {
320
390
  | `UPDATE_PASSWORD_SUCCESS` | 10204 | Password change successful |
321
391
  | `WRITE_NICKNAME_SUCCESS` | 10205 | Nickname written |
322
392
  | `RESET_SUCCESS` | 10206 | Card reset successful |
393
+ | `PRECHECK_HAS_BACKUP` | 10207 | Card already has a valid backup; write was skipped |
323
394
 
324
395
  ### Error Codes
325
396
 
@@ -339,6 +410,7 @@ switch (result.code) {
339
410
  | `READ_TIMEOUT` | 40012 | Read timeout |
340
411
  | `NFC_LOCK_TIMEOUT` | 40013 | NFC lock timeout |
341
412
  | `CRC16_CHECK_FAILED` | 40014 | CRC16 check failed |
413
+ | `RETRY_COUNT_EXHAUSTED` | 40015 | PIN retry count exhausted, card locked |
342
414
 
343
415
  ## Storage Format
344
416
 
@@ -364,13 +436,6 @@ The card stores BIP-39 mnemonics using entropy compression:
364
436
  - **PIN retry counter**: auto-decrements on wrong password, resets to 10 on success
365
437
  - **Secure random**: authentication uses `crypto.getRandomValues()` (requires Hermes ≥ 0.72 or `react-native-get-random-values` polyfill)
366
438
 
367
- ## Platform Support
368
-
369
- | Platform | Technology | Requirements |
370
- |----------|-----------|--------------|
371
- | iOS | MifareIOS | iPhone 7 or later |
372
- | Android | NfcA | NFC-capable device |
373
-
374
439
  ## Project Structure
375
440
 
376
441
  ```
@@ -379,12 +444,19 @@ src/
379
444
  ├── constants.ts # Shared constants (page addresses, NFC commands, mnemonic types)
380
445
  ├── types.ts # Unified ResultCode, NfcResult interface, error mapping
381
446
  ├── crypto.ts # AES encrypt/decrypt, key derivation, secure random
382
- ├── utils.ts # CRC16, hex conversion
447
+ ├── utils.ts # CRC16, hex conversion, array utilities
383
448
  ├── nfc-core.ts # NFC lock, transceive, authentication, retry counter
384
449
  ├── reader.ts # Reader API
385
450
  └── writer.ts # Writer API
386
451
  ```
387
452
 
453
+ ## Platform Support
454
+
455
+ | Platform | Technology | Requirements |
456
+ |----------|-----------|--------------|
457
+ | iOS | MifareIOS | iPhone 7 or later |
458
+ | Android | NfcA | NFC-capable device |
459
+
388
460
  ## License
389
461
 
390
462
  MIT
@@ -43,6 +43,10 @@ export declare const MNEMONIC_TYPE_18 = 3;
43
43
  export declare const MNEMONIC_TYPE_21 = 4;
44
44
  /** 24-word mnemonic (256-bit entropy, 32 bytes) */
45
45
  export declare const MNEMONIC_TYPE_24 = 5;
46
+ /** Mnemonic data end page (stops before nickname area) */
47
+ export declare const MNEMONIC_PAGE_END: number;
48
+ /** Mnemonic data size: (0x24 - 0x08 + 1) * 4 = 116 bytes */
49
+ export declare const MNEMONIC_MEMORY_SIZE: number;
46
50
  /** Nickname start page */
47
51
  export declare const USER_NICKNAME_PAGE_START: number;
48
52
  /** Nickname end page */
package/dist/constants.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * Shared constants for MIFARE Ultralight AES (MF0AES(H)20) NFC operations.
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.DEFAULT_PIN_RETRY_COUNT = exports.RETRY_COUNTER_OFFSET = exports.RETRY_COUNTER_PAGE = exports.USER_NICKNAME_MAX_LENGTH = exports.USER_NICKNAME_PAGE_END = exports.USER_NICKNAME_PAGE_START = exports.MNEMONIC_TYPE_24 = exports.MNEMONIC_TYPE_21 = exports.MNEMONIC_TYPE_18 = exports.MNEMONIC_TYPE_15 = exports.MNEMONIC_TYPE_12 = exports.USER_CARD_INFO_SIZE = exports.USER_MEMORY_SIZE = exports.PAGE_AES_KEY0_START = exports.PAGE_CFG1 = exports.PAGE_CFG0 = exports.USER_CARD_INFO_PAGE_END = exports.USER_CARD_INFO_PAGE_START = exports.USER_PAGE_END = exports.USER_PAGE_START = exports.PAGE_SIZE = exports.KEY_NO_DATA_PROT = exports.CMD_AUTH_PART2 = exports.CMD_AUTH_PART1 = exports.CMD_FAST_READ = exports.CMD_WRITE = exports.CMD_READ = void 0;
6
+ exports.DEFAULT_PIN_RETRY_COUNT = exports.RETRY_COUNTER_OFFSET = exports.RETRY_COUNTER_PAGE = exports.USER_NICKNAME_MAX_LENGTH = exports.USER_NICKNAME_PAGE_END = exports.USER_NICKNAME_PAGE_START = exports.MNEMONIC_MEMORY_SIZE = exports.MNEMONIC_PAGE_END = exports.MNEMONIC_TYPE_24 = exports.MNEMONIC_TYPE_21 = exports.MNEMONIC_TYPE_18 = exports.MNEMONIC_TYPE_15 = exports.MNEMONIC_TYPE_12 = exports.USER_CARD_INFO_SIZE = exports.USER_MEMORY_SIZE = exports.PAGE_AES_KEY0_START = exports.PAGE_CFG1 = exports.PAGE_CFG0 = exports.USER_CARD_INFO_PAGE_END = exports.USER_CARD_INFO_PAGE_START = exports.USER_PAGE_END = exports.USER_PAGE_START = exports.PAGE_SIZE = exports.KEY_NO_DATA_PROT = exports.CMD_AUTH_PART2 = exports.CMD_AUTH_PART1 = exports.CMD_FAST_READ = exports.CMD_WRITE = exports.CMD_READ = void 0;
7
7
  // ---------------------------------------------------------------------------
8
8
  // NFC command codes
9
9
  // ---------------------------------------------------------------------------
@@ -59,6 +59,13 @@ exports.MNEMONIC_TYPE_21 = 0x04;
59
59
  /** 24-word mnemonic (256-bit entropy, 32 bytes) */
60
60
  exports.MNEMONIC_TYPE_24 = 0x05;
61
61
  // ---------------------------------------------------------------------------
62
+ // Mnemonic data area (excludes nickname pages)
63
+ // ---------------------------------------------------------------------------
64
+ /** Mnemonic data end page (stops before nickname area) */
65
+ exports.MNEMONIC_PAGE_END = exports.USER_PAGE_END - 3; // 0x24
66
+ /** Mnemonic data size: (0x24 - 0x08 + 1) * 4 = 116 bytes */
67
+ exports.MNEMONIC_MEMORY_SIZE = (exports.MNEMONIC_PAGE_END - exports.USER_PAGE_START + 1) * exports.PAGE_SIZE;
68
+ // ---------------------------------------------------------------------------
62
69
  // User nickname area (last 3 pages of user memory)
63
70
  // ---------------------------------------------------------------------------
64
71
  /** Nickname start page */
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * NFC read/write library for MIFARE Ultralight AES (LiteCard mnemonic storage).
5
5
  */
6
- export { ResultCode, type NfcResult } from './types';
6
+ export { ResultCode, type NfcResult, nfcResultRetryCountExhausted, } from './types';
7
7
  export { checkCard, readMnemonic, readUserNickname, readMnemonicRetryCount, resetRetryCountTo10, cardInfoToJson, } from './reader';
8
8
  export { initializeCard, updateCard, updatePassword, writeUserNickname, resetCard, } from './writer';
9
9
  export { isNfcOperationLocked, releaseNfcOperationLock, markNfcOperationCancelledByCleanup, consumeNfcOperationCancelledByCleanup, getNfcOperationCancelledByCleanupTimestamp, clearNfcOperationCancelledByCleanup, } from './nfc-core';
package/dist/index.js CHANGED
@@ -5,12 +5,13 @@
5
5
  * NFC read/write library for MIFARE Ultralight AES (LiteCard mnemonic storage).
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.DEFAULT_PIN_RETRY_COUNT = exports.clearNfcOperationCancelledByCleanup = exports.getNfcOperationCancelledByCleanupTimestamp = exports.consumeNfcOperationCancelledByCleanup = exports.markNfcOperationCancelledByCleanup = exports.releaseNfcOperationLock = exports.isNfcOperationLocked = exports.resetCard = exports.writeUserNickname = exports.updatePassword = exports.updateCard = exports.initializeCard = exports.cardInfoToJson = exports.resetRetryCountTo10 = exports.readMnemonicRetryCount = exports.readUserNickname = exports.readMnemonic = exports.checkCard = exports.ResultCode = void 0;
8
+ exports.DEFAULT_PIN_RETRY_COUNT = exports.clearNfcOperationCancelledByCleanup = exports.getNfcOperationCancelledByCleanupTimestamp = exports.consumeNfcOperationCancelledByCleanup = exports.markNfcOperationCancelledByCleanup = exports.releaseNfcOperationLock = exports.isNfcOperationLocked = exports.resetCard = exports.writeUserNickname = exports.updatePassword = exports.updateCard = exports.initializeCard = exports.cardInfoToJson = exports.resetRetryCountTo10 = exports.readMnemonicRetryCount = exports.readUserNickname = exports.readMnemonic = exports.checkCard = exports.nfcResultRetryCountExhausted = exports.ResultCode = void 0;
9
9
  // ---------------------------------------------------------------------------
10
10
  // Unified types & constants (consumers can use a single ResultCode)
11
11
  // ---------------------------------------------------------------------------
12
12
  var types_1 = require("./types");
13
13
  Object.defineProperty(exports, "ResultCode", { enumerable: true, get: function () { return types_1.ResultCode; } });
14
+ Object.defineProperty(exports, "nfcResultRetryCountExhausted", { enumerable: true, get: function () { return types_1.nfcResultRetryCountExhausted; } });
14
15
  // ---------------------------------------------------------------------------
15
16
  // Reader API
16
17
  // ---------------------------------------------------------------------------
package/dist/nfc-core.js CHANGED
@@ -257,7 +257,9 @@ async function decrementRetryCountInSession() {
257
257
  const page = pageBlock.slice(0, constants_1.PAGE_SIZE);
258
258
  const raw = page[constants_1.RETRY_COUNTER_OFFSET];
259
259
  const current = typeof raw === 'number' ? raw & 0xff : 0;
260
- const next = current > 0 ? current - 1 : 0;
260
+ if (current <= 0)
261
+ throw new Error('RETRY_COUNT_EXHAUSTED');
262
+ const next = current - 1;
261
263
  page[constants_1.RETRY_COUNTER_OFFSET] = next & 0xff;
262
264
  await transceive([constants_1.CMD_WRITE, constants_1.RETRY_COUNTER_PAGE, ...page]);
263
265
  return next;
package/dist/reader.d.ts CHANGED
@@ -58,12 +58,17 @@ export declare function cardInfoToJson(cardInfo: ReturnType<typeof parseCardInfo
58
58
  /**
59
59
  * Detect whether the card is empty or already contains data.
60
60
  *
61
- * Logic:
61
+ * Without password: tries to read without auth.
62
62
  * - Read succeeds & first byte is a valid mnemonic type → HAS_DATA
63
63
  * - Read succeeds & first byte is other → EMPTY
64
- * - Read fails (auth required) → HAS_DATA (read-protection is on)
64
+ * - Read fails (auth required) → HAS_DATA (read-protection is on, cannot determine)
65
+ *
66
+ * With password: authenticates first, then reads and validates with CRC16.
67
+ * - Valid mnemonic payload → HAS_DATA (with type info)
68
+ * - Empty or invalid data → EMPTY
69
+ * - Auth failure → AUTH_WRONG_PASSWORD
65
70
  */
66
- export declare function checkCard(onCardIdentified?: () => void): Promise<NfcResult>;
71
+ export declare function checkCard(password?: string, onCardIdentified?: () => void): Promise<NfcResult>;
67
72
  /**
68
73
  * Read the mnemonic from a password-protected card.
69
74
  *
@@ -75,8 +80,8 @@ export declare function readMnemonic(password: string, onCardIdentified?: () =>
75
80
  * Read the user nickname from the card.
76
81
  * @param password – supply if the card has read-protection enabled.
77
82
  */
78
- export declare function readUserNickname(password?: string): Promise<NfcResult>;
83
+ export declare function readUserNickname(password?: string, onCardIdentified?: () => void): Promise<NfcResult>;
79
84
  /** Read the current PIN retry counter from the card. */
80
- export declare function readMnemonicRetryCount(): Promise<NfcResult>;
85
+ export declare function readMnemonicRetryCount(onCardIdentified?: () => void): Promise<NfcResult>;
81
86
  /** Reset the PIN retry counter to the default value (10). */
82
- export declare function resetRetryCountTo10(): Promise<NfcResult>;
87
+ export declare function resetRetryCountTo10(onCardIdentified?: () => void): Promise<NfcResult>;
package/dist/reader.js CHANGED
@@ -228,12 +228,17 @@ function cardInfoToJson(cardInfo, pretty = true) {
228
228
  /**
229
229
  * Detect whether the card is empty or already contains data.
230
230
  *
231
- * Logic:
231
+ * Without password: tries to read without auth.
232
232
  * - Read succeeds & first byte is a valid mnemonic type → HAS_DATA
233
233
  * - Read succeeds & first byte is other → EMPTY
234
- * - Read fails (auth required) → HAS_DATA (read-protection is on)
234
+ * - Read fails (auth required) → HAS_DATA (read-protection is on, cannot determine)
235
+ *
236
+ * With password: authenticates first, then reads and validates with CRC16.
237
+ * - Valid mnemonic payload → HAS_DATA (with type info)
238
+ * - Empty or invalid data → EMPTY
239
+ * - Auth failure → AUTH_WRONG_PASSWORD
235
240
  */
236
- async function checkCard(onCardIdentified) {
241
+ async function checkCard(password, onCardIdentified) {
237
242
  try {
238
243
  await (0, nfc_core_1.acquireNfcLock)();
239
244
  }
@@ -248,25 +253,54 @@ async function checkCard(onCardIdentified) {
248
253
  (0, nfc_core_1.releaseNfcLock)();
249
254
  return { code: types_1.ResultCode.NFC_CONNECT_FAILED, success: false };
250
255
  }
251
- onCardIdentified?.();
252
- let response = null;
253
256
  try {
254
- response = await (0, nfc_core_1.transceive)([constants_1.CMD_READ, constants_1.USER_PAGE_START]);
257
+ if (password) {
258
+ // Authenticated check: authenticate → read full memory → validate CRC16
259
+ const aesKey = (0, crypto_1.passwordToAesKey)(password);
260
+ await (0, nfc_core_1.authenticate)(aesKey);
261
+ onCardIdentified?.();
262
+ const data = await readUserMemory();
263
+ try {
264
+ const decoded = (0, utils_1.validateMnemonicPayload)(data);
265
+ await (0, nfc_core_1.releaseNfcTech)();
266
+ (0, nfc_core_1.releaseNfcLock)();
267
+ return { code: types_1.ResultCode.CHECK_HAS_DATA, success: true, data: { type: decoded.type } };
268
+ }
269
+ catch {
270
+ await (0, nfc_core_1.releaseNfcTech)();
271
+ (0, nfc_core_1.releaseNfcLock)();
272
+ return { code: types_1.ResultCode.CHECK_EMPTY, success: true };
273
+ }
274
+ }
275
+ else {
276
+ // Unauthenticated check: try to read first page directly
277
+ onCardIdentified?.();
278
+ let response = null;
279
+ try {
280
+ response = await (0, nfc_core_1.transceive)([constants_1.CMD_READ, constants_1.USER_PAGE_START]);
281
+ }
282
+ catch { /* read-protected */ }
283
+ await (0, nfc_core_1.releaseNfcTech)();
284
+ (0, nfc_core_1.releaseNfcLock)();
285
+ if (response && response.length >= 4) {
286
+ const first = response[0];
287
+ const hasData = first === constants_1.MNEMONIC_TYPE_12 ||
288
+ first === constants_1.MNEMONIC_TYPE_15 ||
289
+ first === constants_1.MNEMONIC_TYPE_18 ||
290
+ first === constants_1.MNEMONIC_TYPE_21 ||
291
+ first === constants_1.MNEMONIC_TYPE_24;
292
+ return { code: hasData ? types_1.ResultCode.CHECK_HAS_DATA : types_1.ResultCode.CHECK_EMPTY, success: true };
293
+ }
294
+ // Read failed = protection is on, assume has data
295
+ return { code: types_1.ResultCode.CHECK_HAS_DATA, success: true };
296
+ }
255
297
  }
256
- catch { /* read-protected */ }
257
- await (0, nfc_core_1.releaseNfcTech)();
258
- (0, nfc_core_1.releaseNfcLock)();
259
- if (response && response.length >= 4) {
260
- const first = response[0];
261
- const hasData = first === constants_1.MNEMONIC_TYPE_12 ||
262
- first === constants_1.MNEMONIC_TYPE_15 ||
263
- first === constants_1.MNEMONIC_TYPE_18 ||
264
- first === constants_1.MNEMONIC_TYPE_21 ||
265
- first === constants_1.MNEMONIC_TYPE_24;
266
- const code = hasData ? types_1.ResultCode.CHECK_HAS_DATA : types_1.ResultCode.CHECK_EMPTY;
267
- return { code, success: true };
298
+ catch (error) {
299
+ await (0, nfc_core_1.releaseNfcTech)();
300
+ (0, nfc_core_1.releaseNfcLock)();
301
+ const code = (0, types_1.errorToCode)(error);
302
+ return { code, success: false };
268
303
  }
269
- return { code: types_1.ResultCode.CHECK_HAS_DATA, success: true };
270
304
  }
271
305
  catch (error) {
272
306
  (0, nfc_core_1.releaseNfcLock)();
@@ -302,7 +336,14 @@ async function readMnemonic(password, onCardIdentified) {
302
336
  if (typeof next === 'number')
303
337
  retryCountAfterPreDecrement = next;
304
338
  }
305
- catch { /* non-fatal */ }
339
+ catch (error) {
340
+ const code = (0, types_1.errorToCode)(error);
341
+ if (code === types_1.ResultCode.RETRY_COUNT_EXHAUSTED) {
342
+ await (0, nfc_core_1.releaseNfcTech)();
343
+ (0, nfc_core_1.releaseNfcLock)();
344
+ return (0, types_1.nfcResultRetryCountExhausted)();
345
+ }
346
+ }
306
347
  const aesKey = (0, crypto_1.passwordToAesKey)(password);
307
348
  await (0, nfc_core_1.authenticate)(aesKey);
308
349
  onCardIdentified?.();
@@ -359,7 +400,7 @@ async function readMnemonic(password, onCardIdentified) {
359
400
  * Read the user nickname from the card.
360
401
  * @param password – supply if the card has read-protection enabled.
361
402
  */
362
- async function readUserNickname(password) {
403
+ async function readUserNickname(password, onCardIdentified) {
363
404
  try {
364
405
  await (0, nfc_core_1.acquireNfcLock)();
365
406
  }
@@ -379,6 +420,7 @@ async function readUserNickname(password) {
379
420
  const aesKey = (0, crypto_1.passwordToAesKey)(password);
380
421
  await (0, nfc_core_1.authenticate)(aesKey);
381
422
  }
423
+ onCardIdentified?.();
382
424
  const nickname = await readUserNicknameInternal();
383
425
  await (0, nfc_core_1.releaseNfcTech)();
384
426
  (0, nfc_core_1.releaseNfcLock)();
@@ -401,7 +443,7 @@ async function readUserNickname(password) {
401
443
  }
402
444
  }
403
445
  /** Read the current PIN retry counter from the card. */
404
- async function readMnemonicRetryCount() {
446
+ async function readMnemonicRetryCount(onCardIdentified) {
405
447
  try {
406
448
  await (0, nfc_core_1.acquireNfcLock)();
407
449
  }
@@ -417,6 +459,7 @@ async function readMnemonicRetryCount() {
417
459
  return { code: types_1.ResultCode.NFC_CONNECT_FAILED, success: false };
418
460
  }
419
461
  try {
462
+ onCardIdentified?.();
420
463
  const pageBlock = await (0, nfc_core_1.transceive)([constants_1.CMD_READ, constants_1.RETRY_COUNTER_PAGE]);
421
464
  if (!pageBlock || pageBlock.length < constants_1.PAGE_SIZE) {
422
465
  await (0, nfc_core_1.releaseNfcTech)();
@@ -449,7 +492,7 @@ async function readMnemonicRetryCount() {
449
492
  }
450
493
  }
451
494
  /** Reset the PIN retry counter to the default value (10). */
452
- async function resetRetryCountTo10() {
495
+ async function resetRetryCountTo10(onCardIdentified) {
453
496
  try {
454
497
  await (0, nfc_core_1.acquireNfcLock)();
455
498
  }
@@ -465,6 +508,7 @@ async function resetRetryCountTo10() {
465
508
  return { code: types_1.ResultCode.NFC_CONNECT_FAILED, success: false };
466
509
  }
467
510
  try {
511
+ onCardIdentified?.();
468
512
  await (0, nfc_core_1.writeRetryCountInSession)(constants_1.DEFAULT_PIN_RETRY_COUNT);
469
513
  await (0, nfc_core_1.releaseNfcTech)();
470
514
  (0, nfc_core_1.releaseNfcLock)();
package/dist/types.d.ts CHANGED
@@ -20,6 +20,8 @@ export declare const ResultCode: {
20
20
  readonly UPDATE_PASSWORD_SUCCESS: 10204;
21
21
  readonly WRITE_NICKNAME_SUCCESS: 10205;
22
22
  readonly RESET_SUCCESS: 10206;
23
+ /** Card already has a valid mnemonic backup; write was skipped */
24
+ readonly PRECHECK_HAS_BACKUP: 10207;
23
25
  readonly NFC_CONNECT_FAILED: 40001;
24
26
  readonly AUTH_WRONG_PASSWORD: 40002;
25
27
  readonly AUTH_INVALID_RESPONSE: 40003;
@@ -34,6 +36,8 @@ export declare const ResultCode: {
34
36
  readonly READ_TIMEOUT: 40012;
35
37
  readonly NFC_LOCK_TIMEOUT: 40013;
36
38
  readonly CRC16_CHECK_FAILED: 40014;
39
+ /** PIN retry counter is 0; no authentication attempts left */
40
+ readonly RETRY_COUNT_EXHAUSTED: 40015;
37
41
  };
38
42
  export interface NfcResult {
39
43
  /** Numeric result code – compare against ResultCode constants */
@@ -52,6 +56,11 @@ export interface NfcResult {
52
56
  crc16?: number;
53
57
  };
54
58
  }
59
+ /**
60
+ * Unified failure result when the PIN retry counter has reached 0.
61
+ * Used by readMnemonic, updateCard, updatePassword, and resetCard.
62
+ */
63
+ export declare function nfcResultRetryCountExhausted(): NfcResult;
55
64
  /**
56
65
  * Derive a ResultCode from an error thrown during NFC operations.
57
66
  * Handles iOS-specific cancel detection, known error strings, and fallback.