@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/README.en.md +390 -0
- package/README.md +390 -0
- package/dist/constants.d.ts +57 -0
- package/dist/constants.js +78 -0
- package/dist/crypto.d.ts +33 -0
- package/dist/crypto.js +121 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +47 -0
- package/dist/nfc-core.d.ts +65 -0
- package/dist/nfc-core.js +277 -0
- package/dist/reader.d.ts +82 -0
- package/dist/reader.js +488 -0
- package/dist/types.d.ts +59 -0
- package/dist/types.js +94 -0
- package/dist/utils.d.ts +17 -0
- package/dist/utils.js +63 -0
- package/dist/writer.d.ts +32 -0
- package/dist/writer.js +545 -0
- package/package.json +32 -0
package/dist/reader.js
ADDED
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* NFC Reader Module
|
|
4
|
+
*
|
|
5
|
+
* Public API:
|
|
6
|
+
* - checkCard() – detect whether a card is empty or contains data
|
|
7
|
+
* - readMnemonic() – read mnemonic (password required)
|
|
8
|
+
* - readUserNickname() – read user nickname (password optional)
|
|
9
|
+
* - readMnemonicRetryCount() – read the PIN retry counter
|
|
10
|
+
* - resetRetryCountTo10() – reset the retry counter to default
|
|
11
|
+
* - cardInfoToJson() – serialise card-info to JSON
|
|
12
|
+
*
|
|
13
|
+
* Based on MIFARE Ultralight AES (MF0AES(H)20) datasheet.
|
|
14
|
+
*/
|
|
15
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
18
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
19
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
20
|
+
}
|
|
21
|
+
Object.defineProperty(o, k2, desc);
|
|
22
|
+
}) : (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
o[k2] = m[k];
|
|
25
|
+
}));
|
|
26
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
27
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
28
|
+
}) : function(o, v) {
|
|
29
|
+
o["default"] = v;
|
|
30
|
+
});
|
|
31
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
32
|
+
var ownKeys = function(o) {
|
|
33
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
34
|
+
var ar = [];
|
|
35
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
36
|
+
return ar;
|
|
37
|
+
};
|
|
38
|
+
return ownKeys(o);
|
|
39
|
+
};
|
|
40
|
+
return function (mod) {
|
|
41
|
+
if (mod && mod.__esModule) return mod;
|
|
42
|
+
var result = {};
|
|
43
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
44
|
+
__setModuleDefault(result, mod);
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
})();
|
|
48
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
|
+
exports.DEFAULT_PIN_RETRY_COUNT = exports.clearNfcOperationCancelledByCleanup = exports.getNfcOperationCancelledByCleanupTimestamp = exports.consumeNfcOperationCancelledByCleanup = exports.markNfcOperationCancelledByCleanup = exports.releaseNfcOperationLock = exports.isNfcOperationLocked = exports.ResultCode = void 0;
|
|
50
|
+
exports.cardInfoToJson = cardInfoToJson;
|
|
51
|
+
exports.checkCard = checkCard;
|
|
52
|
+
exports.readMnemonic = readMnemonic;
|
|
53
|
+
exports.readUserNickname = readUserNickname;
|
|
54
|
+
exports.readMnemonicRetryCount = readMnemonicRetryCount;
|
|
55
|
+
exports.resetRetryCountTo10 = resetRetryCountTo10;
|
|
56
|
+
const bip39 = __importStar(require("bip39"));
|
|
57
|
+
const constants_1 = require("./constants");
|
|
58
|
+
Object.defineProperty(exports, "DEFAULT_PIN_RETRY_COUNT", { enumerable: true, get: function () { return constants_1.DEFAULT_PIN_RETRY_COUNT; } });
|
|
59
|
+
const types_1 = require("./types");
|
|
60
|
+
Object.defineProperty(exports, "ResultCode", { enumerable: true, get: function () { return types_1.ResultCode; } });
|
|
61
|
+
const utils_1 = require("./utils");
|
|
62
|
+
const crypto_1 = require("./crypto");
|
|
63
|
+
const nfc_core_1 = require("./nfc-core");
|
|
64
|
+
Object.defineProperty(exports, "isNfcOperationLocked", { enumerable: true, get: function () { return
|
|
65
|
+
// re-export lock helpers so consumers can manage lock from outside
|
|
66
|
+
nfc_core_1.isNfcOperationLocked; } });
|
|
67
|
+
Object.defineProperty(exports, "releaseNfcOperationLock", { enumerable: true, get: function () { return nfc_core_1.releaseNfcOperationLock; } });
|
|
68
|
+
Object.defineProperty(exports, "markNfcOperationCancelledByCleanup", { enumerable: true, get: function () { return nfc_core_1.markNfcOperationCancelledByCleanup; } });
|
|
69
|
+
Object.defineProperty(exports, "consumeNfcOperationCancelledByCleanup", { enumerable: true, get: function () { return nfc_core_1.consumeNfcOperationCancelledByCleanup; } });
|
|
70
|
+
Object.defineProperty(exports, "getNfcOperationCancelledByCleanupTimestamp", { enumerable: true, get: function () { return nfc_core_1.getNfcOperationCancelledByCleanupTimestamp; } });
|
|
71
|
+
Object.defineProperty(exports, "clearNfcOperationCancelledByCleanup", { enumerable: true, get: function () { return nfc_core_1.clearNfcOperationCancelledByCleanup; } });
|
|
72
|
+
// ===========================================================================
|
|
73
|
+
// Internal read helpers
|
|
74
|
+
// ===========================================================================
|
|
75
|
+
/** FAST_READ pages 0x08–0x27 (user memory). */
|
|
76
|
+
async function readUserMemory() {
|
|
77
|
+
const response = await (0, nfc_core_1.transceive)([constants_1.CMD_FAST_READ, constants_1.USER_PAGE_START, constants_1.USER_PAGE_END]);
|
|
78
|
+
if (!response || response.length < constants_1.USER_MEMORY_SIZE)
|
|
79
|
+
throw new Error('READ_FAILED');
|
|
80
|
+
return new Uint8Array(response.slice(0, constants_1.USER_MEMORY_SIZE));
|
|
81
|
+
}
|
|
82
|
+
/** FAST_READ pages 0x04–0x07 (card info area). */
|
|
83
|
+
async function readUserCardInfo() {
|
|
84
|
+
const response = await (0, nfc_core_1.transceive)([constants_1.CMD_FAST_READ, constants_1.USER_CARD_INFO_PAGE_START, constants_1.USER_CARD_INFO_PAGE_END]);
|
|
85
|
+
if (!response || response.length < constants_1.USER_CARD_INFO_SIZE)
|
|
86
|
+
throw new Error('READ_FAILED');
|
|
87
|
+
return new Uint8Array(response.slice(0, constants_1.USER_CARD_INFO_SIZE));
|
|
88
|
+
}
|
|
89
|
+
/** FAST_READ nickname pages (last 3 pages of user memory). */
|
|
90
|
+
async function readUserNicknameInternal() {
|
|
91
|
+
const response = await (0, nfc_core_1.transceive)([constants_1.CMD_FAST_READ, constants_1.USER_NICKNAME_PAGE_START, constants_1.USER_NICKNAME_PAGE_END]);
|
|
92
|
+
if (!response || response.length < constants_1.USER_NICKNAME_MAX_LENGTH)
|
|
93
|
+
throw new Error('READ_FAILED');
|
|
94
|
+
const responseArray = Array.isArray(response) ? response : Array.from(response);
|
|
95
|
+
const nicknameBytes = new Uint8Array(responseArray.slice(0, constants_1.USER_NICKNAME_MAX_LENGTH));
|
|
96
|
+
// Find actual length (ignore trailing 0x00 padding)
|
|
97
|
+
let actualLength = 0;
|
|
98
|
+
for (let i = 0; i < constants_1.USER_NICKNAME_MAX_LENGTH; i++) {
|
|
99
|
+
if (nicknameBytes[i] !== 0x00)
|
|
100
|
+
actualLength = i + 1;
|
|
101
|
+
}
|
|
102
|
+
if (actualLength === 0)
|
|
103
|
+
return '';
|
|
104
|
+
try {
|
|
105
|
+
return new TextDecoder('utf-8').decode(nicknameBytes.slice(0, actualLength));
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// Fallback: ASCII-compatible manual conversion
|
|
109
|
+
let result = '';
|
|
110
|
+
for (let i = 0; i < actualLength; i++)
|
|
111
|
+
result += String.fromCharCode(nicknameBytes[i]);
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// ===========================================================================
|
|
116
|
+
// Entropy → mnemonic conversion
|
|
117
|
+
// ===========================================================================
|
|
118
|
+
/**
|
|
119
|
+
* Decode on-card data into a BIP-39 mnemonic.
|
|
120
|
+
*
|
|
121
|
+
* Data layout: [type 1B] [entropy 16-32B] [CRC16 2B]
|
|
122
|
+
*/
|
|
123
|
+
function entropyToMnemonic(data) {
|
|
124
|
+
if (data.length < 19)
|
|
125
|
+
throw new Error('INVALID_CARD_DATA');
|
|
126
|
+
if (data.every(b => b === 0))
|
|
127
|
+
throw new Error('EMPTY_CARD');
|
|
128
|
+
const typeId = data[0];
|
|
129
|
+
let entropyLength;
|
|
130
|
+
let typeStr;
|
|
131
|
+
switch (typeId) {
|
|
132
|
+
case constants_1.MNEMONIC_TYPE_12:
|
|
133
|
+
entropyLength = 16;
|
|
134
|
+
typeStr = '12 words (128-bit)';
|
|
135
|
+
break;
|
|
136
|
+
case constants_1.MNEMONIC_TYPE_15:
|
|
137
|
+
entropyLength = 20;
|
|
138
|
+
typeStr = '15 words (160-bit)';
|
|
139
|
+
break;
|
|
140
|
+
case constants_1.MNEMONIC_TYPE_18:
|
|
141
|
+
entropyLength = 24;
|
|
142
|
+
typeStr = '18 words (192-bit)';
|
|
143
|
+
break;
|
|
144
|
+
case constants_1.MNEMONIC_TYPE_21:
|
|
145
|
+
entropyLength = 28;
|
|
146
|
+
typeStr = '21 words (224-bit)';
|
|
147
|
+
break;
|
|
148
|
+
case constants_1.MNEMONIC_TYPE_24:
|
|
149
|
+
entropyLength = 32;
|
|
150
|
+
typeStr = '24 words (256-bit)';
|
|
151
|
+
break;
|
|
152
|
+
default: throw new Error('INVALID_CARD_DATA');
|
|
153
|
+
}
|
|
154
|
+
const expectedTotal = 1 + entropyLength + 2;
|
|
155
|
+
if (data.length < expectedTotal)
|
|
156
|
+
throw new Error('INVALID_CARD_DATA');
|
|
157
|
+
const dataBlock = data.slice(0, 1 + entropyLength);
|
|
158
|
+
const storedCRC = (0, utils_1.extractCRC16)(data.slice(1 + entropyLength, expectedTotal));
|
|
159
|
+
const calcCRC = (0, utils_1.calculateCRC16)(dataBlock);
|
|
160
|
+
if (storedCRC !== calcCRC)
|
|
161
|
+
throw new Error('CRC16_CHECK_FAILED');
|
|
162
|
+
const entropyBytes = data.slice(1, 1 + entropyLength);
|
|
163
|
+
const entropyHex = (0, utils_1.bytesToHex)(Array.from(entropyBytes));
|
|
164
|
+
const rawBytes = (0, utils_1.bytesToHex)(Array.from(dataBlock));
|
|
165
|
+
const mnemonic = bip39.entropyToMnemonic(entropyHex);
|
|
166
|
+
return { mnemonic, entropyHex, type: typeStr, rawBytes, crc16: calcCRC };
|
|
167
|
+
}
|
|
168
|
+
// ===========================================================================
|
|
169
|
+
// Card info parsing
|
|
170
|
+
// ===========================================================================
|
|
171
|
+
const CARD_TYPE_MAP = {
|
|
172
|
+
0x00: 'Unknown',
|
|
173
|
+
0x01: 'Standard',
|
|
174
|
+
0x02: 'Encrypted',
|
|
175
|
+
0x10: 'Test',
|
|
176
|
+
0x14: 'UKey',
|
|
177
|
+
0x20: 'Development',
|
|
178
|
+
};
|
|
179
|
+
function parseCardInfo(data) {
|
|
180
|
+
if (!data || data.length < constants_1.USER_CARD_INFO_SIZE)
|
|
181
|
+
throw new Error('INVALID_CARD_DATA');
|
|
182
|
+
const version = data[0];
|
|
183
|
+
const cardType = data[1];
|
|
184
|
+
const infoBytes = data.slice(2, constants_1.USER_CARD_INFO_SIZE);
|
|
185
|
+
const rawBytes = (0, utils_1.bytesToHex)(Array.from(data.slice(0, constants_1.USER_CARD_INFO_SIZE)));
|
|
186
|
+
const pageBreakdown = [];
|
|
187
|
+
for (let i = 0; i < Math.ceil(constants_1.USER_CARD_INFO_SIZE / constants_1.PAGE_SIZE); i++) {
|
|
188
|
+
const off = i * constants_1.PAGE_SIZE;
|
|
189
|
+
const pageBytes = Array.from(data.slice(off, off + constants_1.PAGE_SIZE));
|
|
190
|
+
pageBreakdown.push({
|
|
191
|
+
page: constants_1.USER_CARD_INFO_PAGE_START + i,
|
|
192
|
+
bytes: pageBytes,
|
|
193
|
+
hex: (0, utils_1.bytesToHex)(pageBytes),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
version,
|
|
198
|
+
cardType,
|
|
199
|
+
rawBytes,
|
|
200
|
+
infoBytes,
|
|
201
|
+
json: {
|
|
202
|
+
pageInfo: {
|
|
203
|
+
startPage: constants_1.USER_CARD_INFO_PAGE_START,
|
|
204
|
+
endPage: constants_1.USER_CARD_INFO_PAGE_END,
|
|
205
|
+
totalPages: Math.ceil(constants_1.USER_CARD_INFO_SIZE / constants_1.PAGE_SIZE),
|
|
206
|
+
totalBytes: constants_1.USER_CARD_INFO_SIZE,
|
|
207
|
+
},
|
|
208
|
+
version: { value: version, hex: `0x${version.toString(16).padStart(2, '0')}` },
|
|
209
|
+
cardType: {
|
|
210
|
+
value: cardType,
|
|
211
|
+
hex: `0x${cardType.toString(16).padStart(2, '0')}`,
|
|
212
|
+
description: CARD_TYPE_MAP[cardType] || `Unknown (0x${cardType.toString(16).padStart(2, '0')})`,
|
|
213
|
+
known: cardType in CARD_TYPE_MAP,
|
|
214
|
+
},
|
|
215
|
+
additionalInfo: { hex: (0, utils_1.bytesToHex)(Array.from(infoBytes)), bytes: Array.from(infoBytes) },
|
|
216
|
+
rawData: { hex: rawBytes, bytes: Array.from(data.slice(0, constants_1.USER_CARD_INFO_SIZE)), length: constants_1.USER_CARD_INFO_SIZE },
|
|
217
|
+
pageBreakdown,
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
/** Serialise parseCardInfo result to JSON. */
|
|
222
|
+
function cardInfoToJson(cardInfo, pretty = true) {
|
|
223
|
+
return pretty ? JSON.stringify(cardInfo.json, null, 2) : JSON.stringify(cardInfo.json);
|
|
224
|
+
}
|
|
225
|
+
// ===========================================================================
|
|
226
|
+
// Public API
|
|
227
|
+
// ===========================================================================
|
|
228
|
+
/**
|
|
229
|
+
* Detect whether the card is empty or already contains data.
|
|
230
|
+
*
|
|
231
|
+
* Logic:
|
|
232
|
+
* - Read succeeds & first byte is a valid mnemonic type → HAS_DATA
|
|
233
|
+
* - Read succeeds & first byte is other → EMPTY
|
|
234
|
+
* - Read fails (auth required) → HAS_DATA (read-protection is on)
|
|
235
|
+
*/
|
|
236
|
+
async function checkCard(onCardIdentified) {
|
|
237
|
+
try {
|
|
238
|
+
await (0, nfc_core_1.acquireNfcLock)();
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
return { code: types_1.ResultCode.UNKNOWN_ERROR, success: false };
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
try {
|
|
245
|
+
await (0, nfc_core_1.requestNfcTech)();
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
249
|
+
return { code: types_1.ResultCode.NFC_CONNECT_FAILED, success: false };
|
|
250
|
+
}
|
|
251
|
+
onCardIdentified?.();
|
|
252
|
+
let response = null;
|
|
253
|
+
try {
|
|
254
|
+
response = await (0, nfc_core_1.transceive)([constants_1.CMD_READ, constants_1.USER_PAGE_START]);
|
|
255
|
+
}
|
|
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 };
|
|
268
|
+
}
|
|
269
|
+
return { code: types_1.ResultCode.CHECK_HAS_DATA, success: true };
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
273
|
+
throw error;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Read the mnemonic from a password-protected card.
|
|
278
|
+
*
|
|
279
|
+
* Flow: connect → pre-decrement retry counter → authenticate → read →
|
|
280
|
+
* decode entropy → read nickname → reset retry counter → release
|
|
281
|
+
*/
|
|
282
|
+
async function readMnemonic(password, onCardIdentified) {
|
|
283
|
+
try {
|
|
284
|
+
await (0, nfc_core_1.acquireNfcLock)();
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
return { code: types_1.ResultCode.UNKNOWN_ERROR, success: false };
|
|
288
|
+
}
|
|
289
|
+
let retryCountAfterPreDecrement;
|
|
290
|
+
try {
|
|
291
|
+
try {
|
|
292
|
+
await (0, nfc_core_1.requestNfcTech)();
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
296
|
+
return { code: types_1.ResultCode.NFC_CONNECT_FAILED, success: false };
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
// Pre-decrement retry counter before auth attempt
|
|
300
|
+
try {
|
|
301
|
+
const next = await (0, nfc_core_1.decrementRetryCountInSession)();
|
|
302
|
+
if (typeof next === 'number')
|
|
303
|
+
retryCountAfterPreDecrement = next;
|
|
304
|
+
}
|
|
305
|
+
catch { /* non-fatal */ }
|
|
306
|
+
const aesKey = (0, crypto_1.passwordToAesKey)(password);
|
|
307
|
+
await (0, nfc_core_1.authenticate)(aesKey);
|
|
308
|
+
onCardIdentified?.();
|
|
309
|
+
const data = await readUserMemory();
|
|
310
|
+
await readUserCardInfo(); // read but not used directly yet
|
|
311
|
+
const result = entropyToMnemonic(data);
|
|
312
|
+
let nickname;
|
|
313
|
+
try {
|
|
314
|
+
nickname = await readUserNicknameInternal();
|
|
315
|
+
}
|
|
316
|
+
catch { /* non-fatal */ }
|
|
317
|
+
// Auth succeeded – reset counter to default
|
|
318
|
+
let finalRetryCount;
|
|
319
|
+
try {
|
|
320
|
+
await (0, nfc_core_1.writeRetryCountInSession)(constants_1.DEFAULT_PIN_RETRY_COUNT);
|
|
321
|
+
finalRetryCount = constants_1.DEFAULT_PIN_RETRY_COUNT;
|
|
322
|
+
}
|
|
323
|
+
catch { /* non-fatal */ }
|
|
324
|
+
await (0, nfc_core_1.releaseNfcTech)();
|
|
325
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
326
|
+
return {
|
|
327
|
+
code: types_1.ResultCode.READ_SUCCESS,
|
|
328
|
+
success: true,
|
|
329
|
+
data: {
|
|
330
|
+
mnemonic: result.mnemonic,
|
|
331
|
+
type: result.type,
|
|
332
|
+
entropyHex: result.entropyHex,
|
|
333
|
+
rawBytes: result.rawBytes,
|
|
334
|
+
nickname,
|
|
335
|
+
retryCount: finalRetryCount,
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
const isTimeout = error?.message?.includes('TRANSCEIVE_TIMEOUT') ||
|
|
341
|
+
error?.message?.includes('timeout') ||
|
|
342
|
+
error?.message?.includes('TagLost');
|
|
343
|
+
await (0, nfc_core_1.releaseNfcTech)(isTimeout);
|
|
344
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
345
|
+
const code = (0, types_1.errorToCode)(error);
|
|
346
|
+
return {
|
|
347
|
+
code,
|
|
348
|
+
success: false,
|
|
349
|
+
data: typeof retryCountAfterPreDecrement === 'number' ? { retryCount: retryCountAfterPreDecrement } : undefined,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
355
|
+
throw error;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Read the user nickname from the card.
|
|
360
|
+
* @param password – supply if the card has read-protection enabled.
|
|
361
|
+
*/
|
|
362
|
+
async function readUserNickname(password) {
|
|
363
|
+
try {
|
|
364
|
+
await (0, nfc_core_1.acquireNfcLock)();
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
return { code: types_1.ResultCode.UNKNOWN_ERROR, success: false };
|
|
368
|
+
}
|
|
369
|
+
try {
|
|
370
|
+
try {
|
|
371
|
+
await (0, nfc_core_1.requestNfcTech)();
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
375
|
+
return { code: types_1.ResultCode.NFC_CONNECT_FAILED, success: false };
|
|
376
|
+
}
|
|
377
|
+
try {
|
|
378
|
+
if (password) {
|
|
379
|
+
const aesKey = (0, crypto_1.passwordToAesKey)(password);
|
|
380
|
+
await (0, nfc_core_1.authenticate)(aesKey);
|
|
381
|
+
}
|
|
382
|
+
const nickname = await readUserNicknameInternal();
|
|
383
|
+
await (0, nfc_core_1.releaseNfcTech)();
|
|
384
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
385
|
+
return {
|
|
386
|
+
code: types_1.ResultCode.READ_NICKNAME_SUCCESS,
|
|
387
|
+
success: true,
|
|
388
|
+
data: { nickname },
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
await (0, nfc_core_1.releaseNfcTech)();
|
|
393
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
394
|
+
const code = (0, types_1.errorToCode)(error);
|
|
395
|
+
return { code, success: false };
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
catch (error) {
|
|
399
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
400
|
+
throw error;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
/** Read the current PIN retry counter from the card. */
|
|
404
|
+
async function readMnemonicRetryCount() {
|
|
405
|
+
try {
|
|
406
|
+
await (0, nfc_core_1.acquireNfcLock)();
|
|
407
|
+
}
|
|
408
|
+
catch {
|
|
409
|
+
return { code: types_1.ResultCode.UNKNOWN_ERROR, success: false };
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
try {
|
|
413
|
+
await (0, nfc_core_1.requestNfcTech)();
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
417
|
+
return { code: types_1.ResultCode.NFC_CONNECT_FAILED, success: false };
|
|
418
|
+
}
|
|
419
|
+
try {
|
|
420
|
+
const pageBlock = await (0, nfc_core_1.transceive)([constants_1.CMD_READ, constants_1.RETRY_COUNTER_PAGE]);
|
|
421
|
+
if (!pageBlock || pageBlock.length < constants_1.PAGE_SIZE) {
|
|
422
|
+
await (0, nfc_core_1.releaseNfcTech)();
|
|
423
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
424
|
+
return {
|
|
425
|
+
code: types_1.ResultCode.READ_RETRY_COUNT_SUCCESS,
|
|
426
|
+
success: true,
|
|
427
|
+
data: { retryCount: 0 },
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
const retryCount = typeof pageBlock[constants_1.RETRY_COUNTER_OFFSET] === 'number' ? pageBlock[constants_1.RETRY_COUNTER_OFFSET] & 0xff : 0;
|
|
431
|
+
await (0, nfc_core_1.releaseNfcTech)();
|
|
432
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
433
|
+
return {
|
|
434
|
+
code: types_1.ResultCode.READ_RETRY_COUNT_SUCCESS,
|
|
435
|
+
success: true,
|
|
436
|
+
data: { retryCount },
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
await (0, nfc_core_1.releaseNfcTech)();
|
|
441
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
442
|
+
const code = (0, types_1.errorToCode)(error);
|
|
443
|
+
return { code, success: false };
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
catch (error) {
|
|
447
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
448
|
+
throw error;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
/** Reset the PIN retry counter to the default value (10). */
|
|
452
|
+
async function resetRetryCountTo10() {
|
|
453
|
+
try {
|
|
454
|
+
await (0, nfc_core_1.acquireNfcLock)();
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
return { code: types_1.ResultCode.UNKNOWN_ERROR, success: false };
|
|
458
|
+
}
|
|
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
|
+
await (0, nfc_core_1.writeRetryCountInSession)(constants_1.DEFAULT_PIN_RETRY_COUNT);
|
|
469
|
+
await (0, nfc_core_1.releaseNfcTech)();
|
|
470
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
471
|
+
return {
|
|
472
|
+
code: types_1.ResultCode.READ_RETRY_COUNT_SUCCESS,
|
|
473
|
+
success: true,
|
|
474
|
+
data: { retryCount: constants_1.DEFAULT_PIN_RETRY_COUNT },
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
catch (error) {
|
|
478
|
+
await (0, nfc_core_1.releaseNfcTech)();
|
|
479
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
480
|
+
const code = (0, types_1.errorToCode)(error);
|
|
481
|
+
return { code, success: false };
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
catch (error) {
|
|
485
|
+
(0, nfc_core_1.releaseNfcLock)();
|
|
486
|
+
throw error;
|
|
487
|
+
}
|
|
488
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified result codes, NfcResult interface, and error mapping helpers.
|
|
3
|
+
*
|
|
4
|
+
* All codes are unique across reader and writer to prevent ambiguity.
|
|
5
|
+
* - Reader success : 101xx
|
|
6
|
+
* - Writer success : 102xx
|
|
7
|
+
* - Failure : 4xxxx (shared)
|
|
8
|
+
*
|
|
9
|
+
* This library does NOT provide user-facing messages.
|
|
10
|
+
* The caller should map ResultCode to their own localised strings.
|
|
11
|
+
*/
|
|
12
|
+
export declare const ResultCode: {
|
|
13
|
+
readonly READ_SUCCESS: 10102;
|
|
14
|
+
readonly READ_NICKNAME_SUCCESS: 10103;
|
|
15
|
+
readonly CHECK_EMPTY: 10104;
|
|
16
|
+
readonly CHECK_HAS_DATA: 10105;
|
|
17
|
+
readonly READ_RETRY_COUNT_SUCCESS: 10106;
|
|
18
|
+
readonly INIT_SUCCESS: 10201;
|
|
19
|
+
readonly WRITE_SUCCESS: 10203;
|
|
20
|
+
readonly UPDATE_PASSWORD_SUCCESS: 10204;
|
|
21
|
+
readonly WRITE_NICKNAME_SUCCESS: 10205;
|
|
22
|
+
readonly RESET_SUCCESS: 10206;
|
|
23
|
+
readonly NFC_CONNECT_FAILED: 40001;
|
|
24
|
+
readonly AUTH_WRONG_PASSWORD: 40002;
|
|
25
|
+
readonly AUTH_INVALID_RESPONSE: 40003;
|
|
26
|
+
readonly AUTH_VERIFY_FAILED: 40004;
|
|
27
|
+
readonly READ_FAILED: 40005;
|
|
28
|
+
readonly WRITE_FAILED: 40006;
|
|
29
|
+
readonly INVALID_MNEMONIC: 40007;
|
|
30
|
+
readonly UNSUPPORTED_MNEMONIC_LENGTH: 40008;
|
|
31
|
+
readonly INVALID_CARD_DATA: 40009;
|
|
32
|
+
readonly UNKNOWN_ERROR: 40010;
|
|
33
|
+
readonly NFC_USER_CANCELED: 40011;
|
|
34
|
+
readonly READ_TIMEOUT: 40012;
|
|
35
|
+
readonly NFC_LOCK_TIMEOUT: 40013;
|
|
36
|
+
readonly CRC16_CHECK_FAILED: 40014;
|
|
37
|
+
};
|
|
38
|
+
export interface NfcResult {
|
|
39
|
+
/** Numeric result code – compare against ResultCode constants */
|
|
40
|
+
code: number;
|
|
41
|
+
/** Whether the operation succeeded */
|
|
42
|
+
success: boolean;
|
|
43
|
+
/** Returned data (optional, only present for some operations) */
|
|
44
|
+
data?: {
|
|
45
|
+
mnemonic?: string;
|
|
46
|
+
type?: string;
|
|
47
|
+
entropyHex?: string;
|
|
48
|
+
rawBytes?: string;
|
|
49
|
+
nickname?: string;
|
|
50
|
+
retryCount?: number;
|
|
51
|
+
aesKeyHex?: string;
|
|
52
|
+
crc16?: number;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Derive a ResultCode from an error thrown during NFC operations.
|
|
57
|
+
* Handles iOS-specific cancel detection, known error strings, and fallback.
|
|
58
|
+
*/
|
|
59
|
+
export declare function errorToCode(error: any): number;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Unified result codes, NfcResult interface, and error mapping helpers.
|
|
4
|
+
*
|
|
5
|
+
* All codes are unique across reader and writer to prevent ambiguity.
|
|
6
|
+
* - Reader success : 101xx
|
|
7
|
+
* - Writer success : 102xx
|
|
8
|
+
* - Failure : 4xxxx (shared)
|
|
9
|
+
*
|
|
10
|
+
* This library does NOT provide user-facing messages.
|
|
11
|
+
* The caller should map ResultCode to their own localised strings.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.ResultCode = void 0;
|
|
15
|
+
exports.errorToCode = errorToCode;
|
|
16
|
+
const react_native_1 = require("react-native");
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Unified result codes
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
exports.ResultCode = {
|
|
21
|
+
// Reader success (101xx)
|
|
22
|
+
READ_SUCCESS: 10102,
|
|
23
|
+
READ_NICKNAME_SUCCESS: 10103,
|
|
24
|
+
CHECK_EMPTY: 10104,
|
|
25
|
+
CHECK_HAS_DATA: 10105,
|
|
26
|
+
READ_RETRY_COUNT_SUCCESS: 10106,
|
|
27
|
+
// Writer success (102xx)
|
|
28
|
+
INIT_SUCCESS: 10201,
|
|
29
|
+
WRITE_SUCCESS: 10203,
|
|
30
|
+
UPDATE_PASSWORD_SUCCESS: 10204,
|
|
31
|
+
WRITE_NICKNAME_SUCCESS: 10205,
|
|
32
|
+
RESET_SUCCESS: 10206,
|
|
33
|
+
// Failure (4xxxx – shared by reader & writer)
|
|
34
|
+
NFC_CONNECT_FAILED: 40001,
|
|
35
|
+
AUTH_WRONG_PASSWORD: 40002,
|
|
36
|
+
AUTH_INVALID_RESPONSE: 40003,
|
|
37
|
+
AUTH_VERIFY_FAILED: 40004,
|
|
38
|
+
READ_FAILED: 40005,
|
|
39
|
+
WRITE_FAILED: 40006,
|
|
40
|
+
INVALID_MNEMONIC: 40007,
|
|
41
|
+
UNSUPPORTED_MNEMONIC_LENGTH: 40008,
|
|
42
|
+
INVALID_CARD_DATA: 40009,
|
|
43
|
+
UNKNOWN_ERROR: 40010,
|
|
44
|
+
NFC_USER_CANCELED: 40011,
|
|
45
|
+
READ_TIMEOUT: 40012,
|
|
46
|
+
NFC_LOCK_TIMEOUT: 40013,
|
|
47
|
+
CRC16_CHECK_FAILED: 40014,
|
|
48
|
+
};
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Error → code mapping (internal use)
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
/**
|
|
53
|
+
* Derive a ResultCode from an error thrown during NFC operations.
|
|
54
|
+
* Handles iOS-specific cancel detection, known error strings, and fallback.
|
|
55
|
+
*/
|
|
56
|
+
function errorToCode(error) {
|
|
57
|
+
const msg = (typeof error === 'string'
|
|
58
|
+
? error
|
|
59
|
+
: (error?.message || '')).trim();
|
|
60
|
+
// iOS user-cancel detection
|
|
61
|
+
if (react_native_1.Platform.OS === 'ios') {
|
|
62
|
+
if (error &&
|
|
63
|
+
typeof error === 'object' &&
|
|
64
|
+
(error.name === 'UserCancel' || error.constructor?.name === 'UserCancel')) {
|
|
65
|
+
return exports.ResultCode.NFC_USER_CANCELED;
|
|
66
|
+
}
|
|
67
|
+
if (!msg || msg.includes('User Canceled') || msg.includes('Unknown empty error')) {
|
|
68
|
+
return exports.ResultCode.NFC_USER_CANCELED;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (msg.includes('NFC_USER_CANCELED_SIGNAL'))
|
|
72
|
+
return exports.ResultCode.NFC_USER_CANCELED;
|
|
73
|
+
const keywords = [
|
|
74
|
+
['AUTH_WRONG_PASSWORD', exports.ResultCode.AUTH_WRONG_PASSWORD],
|
|
75
|
+
['AUTH_INVALID_RESPONSE', exports.ResultCode.AUTH_INVALID_RESPONSE],
|
|
76
|
+
['AUTH_VERIFY_FAILED', exports.ResultCode.AUTH_VERIFY_FAILED],
|
|
77
|
+
['READ_FAILED', exports.ResultCode.READ_FAILED],
|
|
78
|
+
['WRITE_FAILED', exports.ResultCode.WRITE_FAILED],
|
|
79
|
+
['CRC16_CHECK_FAILED', exports.ResultCode.CRC16_CHECK_FAILED],
|
|
80
|
+
['EMPTY_CARD', exports.ResultCode.CHECK_EMPTY],
|
|
81
|
+
['INVALID_CARD_DATA', exports.ResultCode.INVALID_CARD_DATA],
|
|
82
|
+
['INVALID_MNEMONIC', exports.ResultCode.INVALID_MNEMONIC],
|
|
83
|
+
['UNSUPPORTED_MNEMONIC_LENGTH', exports.ResultCode.UNSUPPORTED_MNEMONIC_LENGTH],
|
|
84
|
+
['NFC_LOCK_TIMEOUT', exports.ResultCode.NFC_LOCK_TIMEOUT],
|
|
85
|
+
['NFC_CONNECT_FAILED', exports.ResultCode.NFC_CONNECT_FAILED],
|
|
86
|
+
['connection failed', exports.ResultCode.NFC_CONNECT_FAILED],
|
|
87
|
+
['transceive fail', exports.ResultCode.NFC_CONNECT_FAILED],
|
|
88
|
+
];
|
|
89
|
+
for (const [keyword, code] of keywords) {
|
|
90
|
+
if (msg.includes(keyword))
|
|
91
|
+
return code;
|
|
92
|
+
}
|
|
93
|
+
return exports.ResultCode.UNKNOWN_ERROR;
|
|
94
|
+
}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure utility functions – no NFC or platform dependencies.
|
|
3
|
+
*/
|
|
4
|
+
export declare function bytesToHex(bytes: Uint8Array | number[]): string;
|
|
5
|
+
export declare function hexToBytes(hex: string): Uint8Array;
|
|
6
|
+
/**
|
|
7
|
+
* CRC16-Modbus checksum.
|
|
8
|
+
* Polynomial 0xA001, initial value 0xFFFF, right-shift algorithm.
|
|
9
|
+
*/
|
|
10
|
+
export declare function calculateCRC16(data: Uint8Array): number;
|
|
11
|
+
/** Convert CRC16 to 2-byte little-endian array. */
|
|
12
|
+
export declare function crc16ToBytes(crc16: number): Uint8Array;
|
|
13
|
+
/**
|
|
14
|
+
* Extract CRC16 from the last 2 bytes of a data buffer (little-endian).
|
|
15
|
+
* @throws if buffer is shorter than 2 bytes.
|
|
16
|
+
*/
|
|
17
|
+
export declare function extractCRC16(data: Uint8Array): number;
|