diff --git a/.eslintrc.cjs b/.eslintrc.cjs index ff35ca1..3ca577c 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -13,8 +13,10 @@ module.exports = { }, rules: { '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/non-nullable-type-assertion-style': 'off', '@typescript-eslint/return-await': 'off', '@typescript-eslint/strict-boolean-expressions': 'off', + '@typescript-eslint/unbound-method': 'off', 'multiline-ternary': 'off', 'no-extra-boolean-cast': 'off', 'no-return-await': 'off', @@ -39,8 +41,5 @@ module.exports = { tuples: 'always-multiline', functions: 'only-multiline', }], - '@typescript-eslint/unbound-method': ['error', { - ignoreStatic: true, - }] }, } diff --git a/.npmignore b/.npmignore index 9a8c8c3..3e9d8be 100644 --- a/.npmignore +++ b/.npmignore @@ -157,4 +157,5 @@ out /tsconfig.*.json /tsconfig.json /tsdoc.json +/typedoc /typedoc.json diff --git a/README.md b/README.md index 9b8ca21..bd41cb9 100644 --- a/README.md +++ b/README.md @@ -178,3 +178,8 @@ await run(ultraUsb) - [GitHub RfidResearchGroup/ChameleonUltra](https://github.com/RfidResearchGroup/ChameleonUltra) - [Chameleon Ultra Guide](https://chameleonultra.com/docs) - [Chameleon Ultra GUI Documentation](https://docs.chameleonultragui.dev/) + +## Dependents (projects / website using chameleon-ultra.js) + +- Sil's Website: `https://drosi.nl/cu` (link is broken!) +- [Tech Security Tools's Website](https://chameleon-ultra.com/) diff --git a/package.json b/package.json index b11acda..424b3b7 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "module": "./dist/index.mjs", "name": "chameleon-ultra.js", "type": "commonjs", - "version": "0.3.7", + "version": "0.3.8", "bugs": { "url": "https://github.com/taichunmin/chameleon-ultra.js/issues" }, @@ -20,7 +20,7 @@ } ], "dependencies": { - "@taichunmin/buffer": "^0.13.4", + "@taichunmin/buffer": "^0.13.6", "debug": "^4.3.4", "lodash": "^4.17.21", "serialport": "^12.0.0", @@ -67,6 +67,7 @@ "serve-static": "^1.15.0", "supports-color": "^9.4.0", "ts-jest": "^29.1.4", + "ts-node": "^10.9.2", "tsup": "^8.0.2", "tsx": "^4.11.0", "typedoc": "^0.25.13", @@ -159,7 +160,7 @@ "dev:https": "tsx ./https.ts", "dev:js": "yarn build:js --watch", "dev:pug": "nodemon --watch pug --ext pug --exec \"yarn build:pug\"", - "dev": "DEBUG_COLORS=1 concurrently --names \"DOC,HTTPS,PUBLIC,PUG,TS\" -c \"bgGray,bgGreen,bgGray,bgBlue,bgYellow\" \"yarn dev:docs\" \"yarn dev:https\" \"yarn dev:public\" \"yarn dev:pug\" \"yarn dev:js\"", + "dev": "DEBUG_COLORS=1 concurrently --names \"DOC,HTTPS,PUG,TS\" -c \"bgGray,bgGreen,bgGray,bgBlue,bgYellow\" \"yarn dev:docs\" \"yarn dev:https\" \"yarn dev:pug\" \"yarn dev:js\"", "lint:ci": "eslint --ext .mjs,.js,.js,.ts,.pug .", "lint": "yarn lint:ci --fix", "mkcert": "mkdir ./mkcert && mkcert -key-file ./mkcert/key.pem -cert-file ./mkcert/cert.pem -ecdsa localhost", diff --git a/pug/include/bootstrapV4.pug b/pug/include/bootstrapV4.pug index dd4afb8..010eab1 100644 --- a/pug/include/bootstrapV4.pug +++ b/pug/include/bootstrapV4.pug @@ -57,6 +57,7 @@ html(lang="zh-Hant") //- pn532 require lodash script(crossorigin="anonymous", src=`${baseurl}index.global.js`) script(crossorigin="anonymous", src=`${baseurl}Crypto1.global.js`) + script(crossorigin="anonymous", src=`${baseurl}plugin/Debug.global.js`) script(crossorigin="anonymous", src=`${baseurl}plugin/WebbleAdapter.global.js`) script(crossorigin="anonymous", src=`${baseurl}plugin/WebserialAdapter.global.js`) block script diff --git a/pug/src/device-settings.pug b/pug/src/device-settings.pug index 32b0ed7..a0ad8bc 100644 --- a/pug/src/device-settings.pug +++ b/pug/src/device-settings.pug @@ -106,10 +106,12 @@ block content block script script(crossorigin="anonymous", src="https://cdn.jsdelivr.net/npm/joi@17/dist/joi-browser.min.js") script. - const { AnimationMode, ButtonAction, ButtonType, ChameleonUltra, DeviceMode, WebbleAdapter, WebserialAdapter } = ChameleonUltraJS // eslint-disable-line + const { AnimationMode, ButtonAction, ButtonType, ChameleonDebug, ChameleonUltra, DeviceMode, WebbleAdapter, WebserialAdapter } = ChameleonUltraJS // eslint-disable-line const ultraUsb = new ChameleonUltra(true) + ultraUsb.use(new ChameleonDebug()) ultraUsb.use(new WebserialAdapter()) const ultraBle = new ChameleonUltra(true) + ultraBle.use(new ChameleonDebug()) ultraBle.use(new WebbleAdapter()) const { joi: Joi } = window diff --git a/pug/src/mfkey32.pug b/pug/src/mfkey32.pug index 1117a3c..ab7d620 100644 --- a/pug/src/mfkey32.pug +++ b/pug/src/mfkey32.pug @@ -108,10 +108,12 @@ block content block script script. - const { Buffer, ChameleonUltra, DeviceMode, FreqType, Mf1KeyType, TagType, WebbleAdapter, WebserialAdapter } = ChameleonUltraJS // eslint-disable-line + const { Buffer, ChameleonDebug, ChameleonUltra, DeviceMode, FreqType, Mf1KeyType, TagType, WebbleAdapter, WebserialAdapter } = ChameleonUltraJS // eslint-disable-line const ultraUsb = new ChameleonUltra(true) + ultraUsb.use(new ChameleonDebug()) ultraUsb.use(new WebserialAdapter()) const ultraBle = new ChameleonUltra(true) + ultraBle.use(new ChameleonDebug()) ultraBle.use(new WebbleAdapter()) window.vm = new Vue({ diff --git a/pug/src/mifare-value.pug b/pug/src/mifare-value.pug index 606190d..d808c3d 100644 --- a/pug/src/mifare-value.pug +++ b/pug/src/mifare-value.pug @@ -93,10 +93,12 @@ block content block script script(crossorigin="anonymous", src="https://cdn.jsdelivr.net/npm/joi@17/dist/joi-browser.min.js") script. - const { Buffer, ChameleonUltra, DeviceMode, Mf1KeyType, Mf1VblockOperator, WebbleAdapter, WebserialAdapter } = ChameleonUltraJS // eslint-disable-line + const { Buffer, ChameleonDebug, ChameleonUltra, DeviceMode, Mf1KeyType, Mf1VblockOperator, WebbleAdapter, WebserialAdapter } = ChameleonUltraJS // eslint-disable-line const ultraUsb = new ChameleonUltra(true) + ultraUsb.use(new ChameleonDebug()) ultraUsb.use(new WebserialAdapter()) const ultraBle = new ChameleonUltra(true) + ultraBle.use(new ChameleonDebug()) ultraBle.use(new WebbleAdapter()) window.vm = new Vue({ diff --git a/pug/src/mifare1k.pug b/pug/src/mifare1k.pug index 61f2d54..0c41437 100644 --- a/pug/src/mifare1k.pug +++ b/pug/src/mifare1k.pug @@ -161,10 +161,12 @@ block content block script script(crossorigin="anonymous", src="https://cdn.jsdelivr.net/npm/joi@17/dist/joi-browser.min.js") script. - const { AnimationMode, Buffer, ButtonAction, ChameleonUltra, DeviceMode, FreqType, TagType, WebbleAdapter, WebserialAdapter } = ChameleonUltraJS // eslint-disable-line + const { AnimationMode, Buffer, ButtonAction, ChameleonDebug, ChameleonUltra, DeviceMode, FreqType, TagType, WebbleAdapter, WebserialAdapter } = ChameleonUltraJS // eslint-disable-line const ultraUsb = new ChameleonUltra(true) + ultraUsb.use(new ChameleonDebug()) ultraUsb.use(new WebserialAdapter()) const ultraBle = new ChameleonUltra(true) + ultraBle.use(new ChameleonDebug()) ultraBle.use(new WebbleAdapter()) const WELL_KNOWN_KEYS = ['ffffffffffff', 'a0a1a2a3a4a5', 'd3f7d3f7d3f7'] @@ -275,14 +277,14 @@ block script await ultra.cmdMf1SetGen2Mode(this.ss.gen2) await ultra.cmdMf1SetWriteMode(this.ss.write) await ultra.cmdHf14aSetAntiCollData({ - atqa: Buffer.fromHexString(this.ss.atqa).reverse(), - ats: Buffer.fromHexString(this.ss.ats), - sak: Buffer.fromHexString(this.ss.sak), - uid: Buffer.fromHexString(this.ss.uid), + atqa: Buffer.from(this.ss.atqa, 'hex').reverse(), + ats: Buffer.from(this.ss.ats, 'hex'), + sak: Buffer.from(this.ss.sak, 'hex'), + uid: Buffer.from(this.ss.uid, 'hex'), }) for (let i = 0; i < 16; i++) { if (!this.ss.toggle[i]) continue - const sectorData = Buffer.fromHexString(this.ss.body[i]) + const sectorData = Buffer.from(this.ss.body[i], 'hex') await ultra.cmdMf1EmuWriteBlock(i << 2, sectorData) } await Swal.fire({ icon: 'success', title: 'Emulate success' }) @@ -339,7 +341,7 @@ block script const ultra = this.ultra for (let i = 0; i < 16; i++) { if (!this.ss.toggle[i]) continue - const sectorData = Buffer.fromHexString(this.ss.body[i]) + const sectorData = Buffer.from(this.ss.body[i], 'hex') await ultra.mf1Gen1aWriteBlocks(i << 2, sectorData) this.showLoading(genSwalCfg(i + 1)) } @@ -399,7 +401,7 @@ block script for (let i = 0; i < 16; i++) { try { if (!this.ss.toggle[i]) continue - const sectorData = Buffer.fromHexString(this.ss.body[i]) + const sectorData = Buffer.from(this.ss.body[i], 'hex') const { success } = await ultra.mf1WriteSectorByKeys(i, keys, sectorData) for (let j = 0; j < 4; j++) if (!success[j]) failed.push(i * 4 + j) } catch (err) { @@ -424,7 +426,7 @@ block script let keys = [] for (let i = 0; i < 16; i++) { if (!this.ss.toggle[i]) continue - const sectorData = Buffer.fromHexString(this.ss.body[i]) + const sectorData = Buffer.from(this.ss.body[i], 'hex') if (sectorData.length !== 64) continue keys.push(..._.map([48, 58], offset => sectorData.subarray(offset, offset + 6).toString('hex'))) } @@ -490,7 +492,7 @@ block script const json = JSON5.parse(buf.toString('utf8')) if (json.FileType !== 'mfcard') throw new Error(`Invalid file type: ${json.FileType}`) if (!_.isNil(json?.Card?.UID)) this.$set(this.ss, 'uid', _.toLower(json.Card.UID)) - if (!_.isNil(json?.Card?.ATQA)) this.$set(this.ss, 'atqa', Buffer.fromHexString(json.Card.ATQA).reverse().toString('hex')) + if (!_.isNil(json?.Card?.ATQA)) this.$set(this.ss, 'atqa', Buffer.from(json.Card.ATQA, 'hex').reverse().toString('hex')) if (!_.isNil(json?.Card?.SAK)) this.$set(this.ss, 'sak', json.Card.SAK) if (!_.isNil(json?.blocks)) { for (let i = 0; i < 16; i++) { @@ -498,7 +500,7 @@ block script for (let j = 0; j < 4; j++) { const blockhex = json?.blocks?.[i * 4 + j] ?? '' if (blockhex.length !== 32) continue - const blockbuf = Buffer.fromHexString(blockhex.replaceAll('-', '0')) + const blockbuf = Buffer.from(blockhex.replaceAll('-', '0'), 'hex') if (blockbuf.length !== 16) continue blockbuf.copy(sectorData, j * 16) } @@ -507,7 +509,7 @@ block script } }, async btnCardImportEml (file, buf) { - buf = Buffer.fromHexString(buf.toString('utf8').replaceAll('-', '0')) + buf = Buffer.from(buf.toString('utf8').replaceAll('-', '0'), 'hex') if (buf.length !== 1024) throw new Error(`Invalid eml size: ${buf.length} bytes`) for (let i = 0; i < 16; i++) { const sectorData = buf.subarray(i * 64, (i + 1) * 64) @@ -523,7 +525,7 @@ block script blockNo = _.parseInt(row.slice(9)) * 4 } else if (/^[0-9a-fA-F-]{32}$/.test(row)) { // hex if (blockNo >= 64) throw new Error(`Invalid block number: ${blockNo}`) - const blockbuf = Buffer.fromHexString(row.replaceAll('-', '0')) + const blockbuf = Buffer.from(row.replaceAll('-', '0'), 'hex') if (blockbuf.length !== 16) throw new Error(`Invalid block size: ${blockbuf.length} bytes`) blockbuf.copy(buf, blockNo * 16) blockNo++ @@ -534,13 +536,13 @@ block script async btnCardExport () { const card = new Buffer(1024) for (let i = 0; i < 16; i++) { - const sectorData = Buffer.fromHexString(this.ss.body[i]) + const sectorData = Buffer.from(this.ss.body[i], 'hex') if (sectorData.length !== 64) continue // skip invalid sector sectorData.copy(card, i * 64) } - const uid = Buffer.fromHexString(this.ss.uid) - const atqa = Buffer.fromHexString(this.ss.atqa).reverse() - const sak = Buffer.fromHexString(this.ss.sak) + const uid = Buffer.from(this.ss.uid, 'hex') + const atqa = Buffer.from(this.ss.atqa, 'hex').reverse() + const sak = Buffer.from(this.ss.sak, 'hex') // helper const toHex = buf => _.toUpper(buf.toString('hex')) diff --git a/pug/src/test.pug b/pug/src/test.pug index 23733bf..7f7f603 100644 --- a/pug/src/test.pug +++ b/pug/src/test.pug @@ -24,11 +24,12 @@ block script script(crossorigin="anonymous", src="https://cdn.jsdelivr.net/npm/vconsole@3/dist/vconsole.min.js") script. window.vConsole = new window.VConsole() - const { ChameleonUltra, WebbleAdapter, WebserialAdapter } = ChameleonUltraJS // eslint-disable-line - window.Buffer = ChameleonUltraJS.Buffer + const { Buffer, ChameleonDebug, ChameleonUltra, Debug, WebbleAdapter, WebserialAdapter } = ChameleonUltraJS // eslint-disable-line const ultraUsb = new ChameleonUltra(true) + ultraUsb.use(new ChameleonDebug()) ultraUsb.use(new WebserialAdapter()) const ultraBle = new ChameleonUltra(true) + ultraBle.use(new ChameleonDebug()) ultraBle.use(new WebbleAdapter()) window.vm = new Vue({ diff --git a/src/ChameleonUltra.test.ts b/src/ChameleonUltra.test.ts index 8a619f6..c468c80 100644 --- a/src/ChameleonUltra.test.ts +++ b/src/ChameleonUltra.test.ts @@ -25,87 +25,88 @@ describe('ChameleonUltra with BufferMockAdapter', () => { beforeEach(async () => { ultra = new ChameleonUltra() adapter = new BufferMockAdapter() + ultra.readDefaultTimeout = 100 // 100ms await ultra.use(adapter) }) test('#cmdBleDeleteAllBonds()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0408 0068 0000 8c 00')) + adapter.send.push(Buffer.from('11ef 0408 0068 0000 8c 00', 'hex')) // act await ultra.cmdBleDeleteAllBonds() // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0408 0000 0000 f4 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0408 0000 0000 f4 00', 'hex')]) }) test('#cmdBleGetAddress()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03f4 0068 0006 9b d9cbc1d2b25f b8')) + adapter.send.push(Buffer.from('11ef 03f4 0068 0006 9b d9cbc1d2b25f b8', 'hex')) // act const actual = await ultra.cmdBleGetAddress() // assert expect(actual).toEqual('D9:CB:C1:D2:B2:5F') - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03f4 0000 0000 09 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03f4 0000 0000 09 00', 'hex')]) }) test('#cmdBleGetPairingKey()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0407 0068 0006 87 313233343536 cb')) + adapter.send.push(Buffer.from('11ef 0407 0068 0006 87 313233343536 cb', 'hex')) // act const actual = await ultra.cmdBleGetPairingKey() // assert expect(actual).toEqual('123456') - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0407 0000 0000 f5 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0407 0000 0000 f5 00', 'hex')]) }) test('#cmdBleGetPairingMode()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 040c 0068 0001 87 00 00')) + adapter.send.push(Buffer.from('11ef 040c 0068 0001 87 00 00', 'hex')) // act const actual = await ultra.cmdBleGetPairingMode() // assert expect(actual).toEqual(false) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 040c 0000 0000 f0 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 040c 0000 0000 f0 00', 'hex')]) }) test('#cmdBleSetPairingKey()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0406 0068 0000 8e 00')) + adapter.send.push(Buffer.from('11ef 0406 0068 0000 8e 00', 'hex')) // act await ultra.cmdBleSetPairingKey('123456') // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0406 0000 0006 f0 313233343536 cb')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0406 0000 0006 f0 313233343536 cb', 'hex')]) }) test('#cmdBleSetPairingMode()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 040d 0068 0000 87 00')) + adapter.send.push(Buffer.from('11ef 040d 0068 0000 87 00', 'hex')) // act await ultra.cmdBleSetPairingMode(false) // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 040d 0000 0001 ee 00 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 040d 0000 0001 ee 00 00', 'hex')]) }) test('#cmdChangeDeviceMode()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // act await ultra.cmdChangeDeviceMode(DeviceMode.TAG) // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03e9 0000 0001 13 00 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03e9 0000 0001 13 00 00', 'hex')]) }) test('#cmdEnterBootloader()', async () => { @@ -113,120 +114,120 @@ describe('ChameleonUltra with BufferMockAdapter', () => { await ultra.cmdEnterBootloader() // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03f2 0000 0000 0b 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03f2 0000 0000 0b 00', 'hex')]) }) test('#cmdGetAnimationMode()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03f8 0068 0001 9c 00 00')) + adapter.send.push(Buffer.from('11ef 03f8 0068 0001 9c 00 00', 'hex')) // act const actual = await ultra.cmdGetAnimationMode() // assert expect(actual).toEqual(AnimationMode.FULL) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03f8 0000 0000 05 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03f8 0000 0000 05 00', 'hex')]) }) test('#cmdGetAppVersion()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e8 0068 0002 ab 0200 fe')) + adapter.send.push(Buffer.from('11ef 03e8 0068 0002 ab 0200 fe', 'hex')) // act const actual = await ultra.cmdGetAppVersion() // assert expect(actual).toEqual('2.0') - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03e8 0000 0000 15 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03e8 0000 0000 15 00', 'hex')]) }) test('#isSupportedAppVersion()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e8 0068 0002 ab 0200 fe')) + adapter.send.push(Buffer.from('11ef 03e8 0068 0002 ab 0200 fe', 'hex')) // act const actual = await ultra.isSupportedAppVersion() // assert expect(actual).toEqual(true) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03e8 0000 0000 15 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03e8 0000 0000 15 00', 'hex')]) }) test('#cmdGetBatteryInfo()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0401 0068 0003 90 105461 3b')) + adapter.send.push(Buffer.from('11ef 0401 0068 0003 90 105461 3b', 'hex')) // act const actual = await ultra.cmdGetBatteryInfo() // assert expect(actual).toEqual({ level: 97, voltage: 4180 }) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0401 0000 0000 fb 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0401 0000 0000 fb 00', 'hex')]) }) test('#cmdGetButtonLongPressAction()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0404 0068 0001 8f 03 fd')) + adapter.send.push(Buffer.from('11ef 0404 0068 0001 8f 03 fd', 'hex')) // act const actual = await ultra.cmdGetButtonLongPressAction(ButtonType.BUTTON_A) // assert expect(actual).toEqual(ButtonAction.CLONE_IC_UID) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0404 0000 0001 f7 41 bf')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0404 0000 0001 f7 41 bf', 'hex')]) }) test('#cmdGetButtonPressAction()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0402 0068 0001 91 01 ff')) + adapter.send.push(Buffer.from('11ef 0402 0068 0001 91 01 ff', 'hex')) // act const actual = await ultra.cmdGetButtonPressAction(ButtonType.BUTTON_A) // assert expect(actual).toEqual(ButtonAction.CYCLE_SLOT_INC) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0402 0000 0001 f9 41 bf')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0402 0000 0001 f9 41 bf', 'hex')]) }) test('#cmdGetDeviceChipId()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03f3 0068 0008 9a bbef76355a6a2068 5f')) + adapter.send.push(Buffer.from('11ef 03f3 0068 0008 9a bbef76355a6a2068 5f', 'hex')) // act const actual = await ultra.cmdGetDeviceChipId() // assert expect(actual).toEqual('bbef76355a6a2068') - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03f3 0000 0000 0a 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03f3 0000 0000 0a 00', 'hex')]) }) test('#cmdGetDeviceMode()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03ea 0068 0001 aa 00 00')) + adapter.send.push(Buffer.from('11ef 03ea 0068 0001 aa 00 00', 'hex')) // act const actual = await ultra.cmdGetDeviceMode() // assert expect(actual).toEqual(DeviceMode.TAG) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03ea 0000 0000 13 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03ea 0000 0000 13 00', 'hex')]) }) test('#cmdGetDeviceModel()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0409 0068 0001 8a 00 00')) + adapter.send.push(Buffer.from('11ef 0409 0068 0001 8a 00 00', 'hex')) // act const actual = await ultra.cmdGetDeviceModel() // assert expect(actual).toEqual(DeviceModel.ULTRA) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0409 0000 0000 f3 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0409 0000 0000 f3 00', 'hex')]) }) test('#cmdGetDeviceSettings()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 040a 0068 000d 7d 05000102030400313233343536 bc')) + adapter.send.push(Buffer.from('11ef 040a 0068 000d 7d 05000102030400313233343536 bc', 'hex')) // act const actual = await ultra.cmdGetDeviceSettings() @@ -240,172 +241,172 @@ describe('ChameleonUltra with BufferMockAdapter', () => { buttonPressAction: [ButtonAction.CYCLE_SLOT_INC, ButtonAction.CYCLE_SLOT_DEC], version: 5, }) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 040a 0000 0000 f2 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 040a 0000 0000 f2 00', 'hex')]) }) test('#cmdGetGitVersion()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03f9 0068 0013 89 76322e302e302d3132322d6737666462333538 43')) + adapter.send.push(Buffer.from('11ef 03f9 0068 0013 89 76322e302e302d3132322d6737666462333538 43', 'hex')) // act const actual = await ultra.cmdGetGitVersion() // assert expect(actual).toEqual('v2.0.0-122-g7fdb358') - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03f9 0000 0000 04 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03f9 0000 0000 04 00', 'hex')]) }) test('#cmdGetSupportedCmds()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 040b 0068 008c fd 03e803e903ea03eb03ec03ed03ee03ef03f003f103f203f303f403f503f603f703f803f903fa03fb03fc03fd03ff0400040104020403040404050407040604080409040a040b040c040d07d007d107d207d307d407d507d607d707d807d907da07db0bb80bb90fa00fa10fa40fa50fa60fa70fa80fa90faa0fab0fac0fad0fae0faf0fb00fb10fb213881389 f9')) + adapter.send.push(Buffer.from('11ef 040b 0068 008c fd 03e803e903ea03eb03ec03ed03ee03ef03f003f103f203f303f403f503f603f703f803f903fa03fb03fc03fd03ff0400040104020403040404050407040604080409040a040b040c040d07d007d107d207d307d407d507d607d707d807d907da07db0bb80bb90fa00fa10fa40fa50fa60fa70fa80fa90faa0fab0fac0fad0fae0faf0fb00fb10fb213881389 f9', 'hex')) // act const actual = await ultra.cmdGetSupportedCmds() // assert expect(actual).toEqual(new Set([1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1023, 1024, 1025, 1026, 1027, 1028, 1029, 1031, 1030, 1032, 1033, 1034, 1035, 1036, 1037, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 3000, 3001, 4000, 4001, 4004, 4005, 4006, 4007, 4008, 4009, 4010, 4011, 4012, 4013, 4014, 4015, 4016, 4017, 4018, 5000, 5001])) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 040b 0000 0000 f1 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 040b 0000 0000 f1 00', 'hex')]) }) test('#cmdResetSettings()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03f6 0068 0000 9f 00')) + adapter.send.push(Buffer.from('11ef 03f6 0068 0000 9f 00', 'hex')) // act await ultra.cmdResetSettings() // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03f6 0000 0000 07 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03f6 0000 0000 07 00', 'hex')]) }) test('#cmdSaveSettings()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03f5 0068 0000 a0 00')) + adapter.send.push(Buffer.from('11ef 03f5 0068 0000 a0 00', 'hex')) // act await ultra.cmdSaveSettings() // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03f5 0000 0000 08 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03f5 0000 0000 08 00', 'hex')]) }) test('#cmdSetAnimationMode()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03f7 0068 0000 9e 00')) + adapter.send.push(Buffer.from('11ef 03f7 0068 0000 9e 00', 'hex')) // act await ultra.cmdSetAnimationMode(AnimationMode.FULL) // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03f7 0000 0001 05 00 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03f7 0000 0001 05 00 00', 'hex')]) }) test('#cmdSetButtonLongPressAction()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0405 0068 0000 8f 00')) + adapter.send.push(Buffer.from('11ef 0405 0068 0000 8f 00', 'hex')) // act await ultra.cmdSetButtonLongPressAction(ButtonType.BUTTON_A, ButtonAction.CLONE_IC_UID) // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0405 0000 0002 f5 4103 bc')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0405 0000 0002 f5 4103 bc', 'hex')]) }) test('#cmdSetButtonPressAction()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0403 0068 0000 91 00')) + adapter.send.push(Buffer.from('11ef 0403 0068 0000 91 00', 'hex')) // act await ultra.cmdSetButtonPressAction(ButtonType.BUTTON_A, ButtonAction.CYCLE_SLOT_INC) // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0403 0000 0002 f7 4101 be')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0403 0000 0002 f7 4101 be', 'hex')]) }) test('#cmdWipeFds()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03fc 0068 0000 99 00')) + adapter.send.push(Buffer.from('11ef 03fc 0068 0000 99 00', 'hex')) // act await ultra.cmdWipeFds() // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03fc 0000 0000 01 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03fc 0000 0000 01 00', 'hex')]) }) test('#cmdSlotChangeTagType()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03ec 0068 0000 a9 00')) + adapter.send.push(Buffer.from('11ef 03ec 0068 0000 a9 00', 'hex')) // act await ultra.cmdSlotChangeTagType(Slot.SLOT_1, TagType.MIFARE_1024) // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03ec 0000 0003 0e 0003e9 14')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03ec 0000 0003 0e 0003e9 14', 'hex')]) }) test('#cmdSlotDeleteFreqName()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03fd 0068 0000 98 00')) + adapter.send.push(Buffer.from('11ef 03fd 0068 0000 98 00', 'hex')) // act const actual = await ultra.cmdSlotDeleteFreqName(Slot.SLOT_1, FreqType.HF) // assert expect(actual).toEqual(true) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03fd 0000 0002 fe 0002 fe')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03fd 0000 0002 fe 0002 fe', 'hex')]) }) test('#cmdSlotDeleteFreqName()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03fd 0070 0000 90 00')) + adapter.send.push(Buffer.from('11ef 03fd 0070 0000 90 00', 'hex')) // act const actual = await ultra.cmdSlotDeleteFreqName(Slot.SLOT_1, FreqType.HF) // assert expect(actual).toEqual(false) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03fd 0000 0002 fe 0002 fe')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03fd 0000 0002 fe 0002 fe', 'hex')]) }) test('#cmdSlotDeleteFreqType()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0400 0068 0000 94 00')) + adapter.send.push(Buffer.from('11ef 0400 0068 0000 94 00', 'hex')) // act await ultra.cmdSlotDeleteFreqType(Slot.SLOT_1, FreqType.HF) // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0400 0000 0002 fa 0002 fe')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0400 0000 0002 fa 0002 fe', 'hex')]) }) test('#cmdSlotGetActive()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03fa 0068 0001 9a 00 00')) + adapter.send.push(Buffer.from('11ef 03fa 0068 0001 9a 00 00', 'hex')) // act const actual = await ultra.cmdSlotGetActive() // assert expect(actual).toEqual(Slot.SLOT_1) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03fa 0000 0000 03 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03fa 0000 0000 03 00', 'hex')]) }) test('#cmdSlotGetFreqName()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03f0 0068 0005 a0 4d79546167 1e')) + adapter.send.push(Buffer.from('11ef 03f0 0068 0005 a0 4d79546167 1e', 'hex')) // act const actual = await ultra.cmdSlotGetFreqName(Slot.SLOT_1, FreqType.HF) // assert expect(actual).toEqual('MyTag') - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03f0 0000 0002 0b 0002 fe')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03f0 0000 0002 0b 0002 fe', 'hex')]) }) test('#cmdSlotGetInfo()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03fb 0068 0020 7a 03e9006403e90000000000640000000000000000000000000000000000000000 60')) + adapter.send.push(Buffer.from('11ef 03fb 0068 0020 7a 03e9006403e90000000000640000000000000000000000000000000000000000 60', 'hex')) // act const actual = await ultra.cmdSlotGetInfo() @@ -421,12 +422,12 @@ describe('ChameleonUltra with BufferMockAdapter', () => { { hfTagType: TagType.UNDEFINED, lfTagType: TagType.UNDEFINED }, { hfTagType: TagType.UNDEFINED, lfTagType: TagType.UNDEFINED }, ]) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03fb 0000 0000 02 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03fb 0000 0000 02 00', 'hex')]) }) test('#cmdSlotGetIsEnable()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03ff 0068 0010 86 01010100000100000000000000000000 fc')) + adapter.send.push(Buffer.from('11ef 03ff 0068 0010 86 01010100000100000000000000000000 fc', 'hex')) // act const actual = await ultra.cmdSlotGetIsEnable() @@ -442,77 +443,77 @@ describe('ChameleonUltra with BufferMockAdapter', () => { { hf: false, lf: false }, { hf: false, lf: false }, ]) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03ff 0000 0000 fe 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03ff 0000 0000 fe 00', 'hex')]) }) test('#cmdSlotResetTagType()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03ed 0068 0000 a8 00')) + adapter.send.push(Buffer.from('11ef 03ed 0068 0000 a8 00', 'hex')) // act await ultra.cmdSlotResetTagType(Slot.SLOT_1, TagType.MIFARE_1024) // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03ed 0000 0003 0d 0003e9 14')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03ed 0000 0003 0d 0003e9 14', 'hex')]) }) test('#cmdSlotSaveSettings()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03f1 0068 0000 a4 00')) + adapter.send.push(Buffer.from('11ef 03f1 0068 0000 a4 00', 'hex')) // act await ultra.cmdSlotSaveSettings() // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03f1 0000 0000 0c 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03f1 0000 0000 0c 00', 'hex')]) }) test('#cmdSlotSetActive()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03eb 0068 0000 aa 00')) + adapter.send.push(Buffer.from('11ef 03eb 0068 0000 aa 00', 'hex')) // act await ultra.cmdSlotSetActive(Slot.SLOT_1) // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03eb 0000 0001 11 00 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03eb 0000 0001 11 00 00', 'hex')]) }) test('#cmdSlotSetEnable()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03ee 0068 0000 a7 00')) + adapter.send.push(Buffer.from('11ef 03ee 0068 0000 a7 00', 'hex')) // act await ultra.cmdSlotSetEnable(Slot.SLOT_1, FreqType.HF, true) // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03ee 0000 0003 0c 000201 fd')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03ee 0000 0003 0c 000201 fd', 'hex')]) }) test('#cmdSlotSetFreqName()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03ef 0068 0000 a6 00')) + adapter.send.push(Buffer.from('11ef 03ef 0068 0000 a6 00', 'hex')) // act await ultra.cmdSlotSetFreqName(Slot.SLOT_1, FreqType.HF, 'My Tag') // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 03ef 0000 0008 06 00024d7920546167 fc')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 03ef 0000 0008 06 00024d7920546167 fc', 'hex')]) }) test('#cmdEm410xScan()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 0bb8 0040 0005 f8 0000002076 6a')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 0bb8 0040 0005 f8 0000002076 6a', 'hex')) // act const actual = await ultra.cmdEm410xScan() // assert - expect(actual).toEqual(Buffer.fromHexString('0000002076')) + expect(actual).toEqual(Buffer.from('0000002076', 'hex')) expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 0bb8 0000 0000 3d 00'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 0bb8 0000 0000 3d 00', 'hex'), ]) }) @@ -520,17 +521,17 @@ describe('ChameleonUltra with BufferMockAdapter', () => { expect.hasAssertions() try { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 0bb8 0041 0000 fc 00')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 0bb8 0041 0000 fc 00', 'hex')) // act const actual = await ultra.cmdEm410xScan() // assert - expect(actual).toEqual(Buffer.fromHexString('0000002076')) + expect(actual).toEqual(Buffer.from('0000002076')) expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 0bb8 0000 0000 3d 00'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 0bb8 0000 0000 3d 00', 'hex'), ]) } catch (err) { expect(err.message).toMatch(/tag not found/) @@ -539,99 +540,99 @@ describe('ChameleonUltra with BufferMockAdapter', () => { test('#cmdEm410xWriteToT55xx()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 0bb9 0040 0000 fc 00')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 0bb9 0040 0000 fc 00', 'hex')) // act - await ultra.cmdEm410xWriteToT55xx(Buffer.fromHexString('deadbeef88')) + await ultra.cmdEm410xWriteToT55xx(Buffer.from('deadbeef88', 'hex')) // assert expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 0bb9 0000 0011 2b deadbeef88202066665124364819920427 6b'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 0bb9 0000 0011 2b deadbeef88202066665124364819920427 6b', 'hex'), ]) }) test('#cmdHf14aRaw()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 07da 0000 0000 1f 00')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 07da 0000 0000 1f 00', 'hex')) // act const actual = await ultra.cmdHf14aRaw({ appendCrc: true, data: Buffer.pack('!H', 0x5000), waitResponse: false }) // assert - expect(actual).toEqual(Buffer.fromHexString('')) + expect(actual).toEqual(new Buffer(0)) expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 07da 0000 0007 18 2003e800105000 95'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 07da 0000 0007 18 2003e800105000 95', 'hex'), ]) }) test('#cmdHf14aScan()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 07d0 0000 0009 20 0494194a3d04000800 bc')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 07d0 0000 0009 20 0494194a3d04000800 bc', 'hex')) // act const actual = await ultra.cmdHf14aScan() // assert expect(actual).toMatchObject([{ - atqa: Buffer.fromHexString('0400'), - ats: Buffer.fromHexString(''), - sak: Buffer.fromHexString('08'), - uid: Buffer.fromHexString('94194a3d'), + atqa: Buffer.from('0400', 'hex'), + ats: Buffer.from('', 'hex'), + sak: Buffer.from('08', 'hex'), + uid: Buffer.from('94194a3d', 'hex'), }]) expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 07d0 0000 0000 29 00'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 07d0 0000 0000 29 00', 'hex'), ]) }) test('#cmdEm410xGetEmuId()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 1389 0068 0005 f7 deadbeef88 40')) + adapter.send.push(Buffer.from('11ef 1389 0068 0005 f7 deadbeef88 40', 'hex')) // act const actual = await ultra.cmdEm410xGetEmuId() // assert - expect(actual).toMatchObject(Buffer.fromHexString('deadbeef88')) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 1389 0000 0000 64 00')]) + expect(actual).toMatchObject(Buffer.from('deadbeef88', 'hex')) + expect(adapter.recv).toEqual([Buffer.from('11ef 1389 0000 0000 64 00', 'hex')]) }) test('#cmdEm410xSetEmuId()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 1388 0068 0000 fd 00')) + adapter.send.push(Buffer.from('11ef 1388 0068 0000 fd 00', 'hex')) // act - await ultra.cmdEm410xSetEmuId(Buffer.fromHexString('deadbeef88')) + await ultra.cmdEm410xSetEmuId(Buffer.from('deadbeef88', 'hex')) // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 1388 0000 0005 60 deadbeef88 40')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 1388 0000 0005 60 deadbeef88 40', 'hex')]) }) test('#cmdHf14aGetAntiCollData()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0fb2 0068 0009 ce 04deadbeef04000800 b8')) + adapter.send.push(Buffer.from('11ef 0fb2 0068 0009 ce 04deadbeef04000800 b8', 'hex')) // act const actual = await ultra.cmdHf14aGetAntiCollData() // assert expect(actual).toMatchObject({ - atqa: Buffer.fromHexString('0400'), - ats: Buffer.fromHexString(''), - sak: Buffer.fromHexString('08'), - uid: Buffer.fromHexString('deadbeef'), + atqa: Buffer.from('0400', 'hex'), + ats: Buffer.from('', 'hex'), + sak: Buffer.from('08', 'hex'), + uid: Buffer.from('deadbeef', 'hex'), }) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0fb2 0000 0000 3f 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0fb2 0000 0000 3f 00', 'hex')]) }) test('#cmdHf14aSetAntiCollData()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0fa1 0068 0000 e8 00')) + adapter.send.push(Buffer.from('11ef 0fa1 0068 0000 e8 00', 'hex')) // act await ultra.cmdHf14aSetAntiCollData({ @@ -641,162 +642,162 @@ describe('ChameleonUltra with BufferMockAdapter', () => { }) // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0fa1 0000 0009 47 040102030404000800 e6')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0fa1 0000 0009 47 040102030404000800 e6', 'hex')]) }) test('#cmdMf1AcquireDarkside()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 07d4 0000 0021 04 00d3efed0c5499e1c00000000000000000070b0d0a060e0f090000000000000000 62')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 07d4 0000 0021 04 00d3efed0c5499e1c00000000000000000070b0d0a060e0f090000000000000000 62', 'hex')) // act const actual = await ultra.cmdMf1AcquireDarkside(0, Mf1KeyType.KEY_A, true) // assert expect(actual).toMatchObject({ - ar: Buffer.fromHexString('00000000'), - ks: Buffer.fromHexString('070b0d0a060e0f09'), - nr: Buffer.fromHexString('00000000'), - nt: Buffer.fromHexString('5499e1c0'), - par: Buffer.fromHexString('0000000000000000'), + ar: Buffer.from('00000000', 'hex'), + ks: Buffer.from('070b0d0a060e0f09', 'hex'), + nr: Buffer.from('00000000', 'hex'), + nt: Buffer.from('5499e1c0', 'hex'), + par: Buffer.from('0000000000000000', 'hex'), status: DarksideStatus.OK, - uid: Buffer.fromHexString('d3efed0c'), + uid: Buffer.from('d3efed0c', 'hex'), }) expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 07d4 0000 0004 21 6000011e 81'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 07d4 0000 0004 21 6000011e 81', 'hex'), ]) }) test('#cmdMf1AcquireNested()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 07d6 0000 0012 11 502e7c41d1b90dab0561d34ecbcd6cef0207 00')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 07d6 0000 0012 11 502e7c41d1b90dab0561d34ecbcd6cef0207 00', 'hex')) // act const actual = await ultra.cmdMf1AcquireNested( - { block: 0, keyType: Mf1KeyType.KEY_A, key: Buffer.fromHexString('FFFFFFFFFFFF') }, + { block: 0, keyType: Mf1KeyType.KEY_A, key: Buffer.from('FFFFFFFFFFFF', 'hex') }, { block: 4, keyType: Mf1KeyType.KEY_A }, ) // assert expect(actual).toMatchObject([ - { nt1: Buffer.fromHexString('502e7c41'), nt2: Buffer.fromHexString('d1b90dab'), par: 5 }, - { nt1: Buffer.fromHexString('61d34ecb'), nt2: Buffer.fromHexString('cd6cef02'), par: 7 }, + { nt1: Buffer.from('502e7c41', 'hex'), nt2: Buffer.from('d1b90dab', 'hex'), par: 5 }, + { nt1: Buffer.from('61d34ecb', 'hex'), nt2: Buffer.from('cd6cef02', 'hex'), par: 7 }, ]) expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 07d6 0000 000a 19 6000ffffffffffff6004 42'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 07d6 0000 000a 19 6000ffffffffffff6004 42', 'hex'), ]) }) test('#cmdMf1AcquireStaticNested()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 07d3 0000 0014 12 b908a16d012001458190197501200145cdd400f3 30')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 07d3 0000 0014 12 b908a16d012001458190197501200145cdd400f3 30', 'hex')) // act const actual = await ultra.cmdMf1AcquireStaticNested( - { block: 0, keyType: Mf1KeyType.KEY_A, key: Buffer.fromHexString('FFFFFFFFFFFF') }, + { block: 0, keyType: Mf1KeyType.KEY_A, key: Buffer.from('FFFFFFFFFFFF', 'hex') }, { block: 4, keyType: Mf1KeyType.KEY_A }, ) // assert expect(actual).toMatchObject({ - uid: Buffer.fromHexString('b908a16d'), + uid: Buffer.from('b908a16d', 'hex'), atks: [ - { nt1: Buffer.fromHexString('01200145'), nt2: Buffer.fromHexString('81901975') }, - { nt1: Buffer.fromHexString('01200145'), nt2: Buffer.fromHexString('cdd400f3') }, + { nt1: Buffer.from('01200145', 'hex'), nt2: Buffer.from('81901975', 'hex') }, + { nt1: Buffer.from('01200145', 'hex'), nt2: Buffer.from('cdd400f3', 'hex') }, ], }) expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 07d3 0000 000a 1c 6000ffffffffffff6004 42'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 07d3 0000 000a 1c 6000ffffffffffff6004 42', 'hex'), ]) }) test('#cmdMf1CheckBlockKey()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 07d7 0000 0000 22 00')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 07d7 0000 0000 22 00', 'hex')) // act const actual = await ultra.cmdMf1CheckBlockKey({ block: 0, - key: Buffer.fromHexString('FFFFFFFFFFFF'), + key: Buffer.from('FFFFFFFFFFFF', 'hex'), keyType: Mf1KeyType.KEY_A, }) // assert expect(actual).toEqual(true) expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 07d7 0000 0008 1a 6000ffffffffffff a6'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 07d7 0000 0008 1a 6000ffffffffffff a6', 'hex'), ]) }) test('#cmdMf1EmuReadBlock()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0fa8 0068 0010 d1 deadbeef220804000177a2cc35afa51d 0e')) + adapter.send.push(Buffer.from('11ef 0fa8 0068 0010 d1 deadbeef220804000177a2cc35afa51d 0e', 'hex')) // act const actual = await ultra.cmdMf1EmuReadBlock(0) // assert - expect(actual).toEqual(Buffer.fromHexString('deadbeef220804000177a2cc35afa51d')) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0fa8 0000 0002 47 0001 ff')]) + expect(actual).toEqual(Buffer.from('deadbeef220804000177a2cc35afa51d', 'hex')) + expect(adapter.recv).toEqual([Buffer.from('11ef 0fa8 0000 0002 47 0001 ff', 'hex')]) }) test('#cmdMf1EmuWriteBlock()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0fa0 0068 0000 e9 00')) + adapter.send.push(Buffer.from('11ef 0fa0 0068 0000 e9 00', 'hex')) // act - await ultra.cmdMf1EmuWriteBlock(1, Buffer.fromHexString('000102030405060708090a0b0c0d0e0f')) + await ultra.cmdMf1EmuWriteBlock(1, Buffer.from('000102030405060708090a0b0c0d0e0f', 'hex')) // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0fa0 0000 0011 40 01000102030405060708090a0b0c0d0e0f 87')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0fa0 0000 0011 40 01000102030405060708090a0b0c0d0e0f 87', 'hex')]) }) test('#cmdMf1GetAntiCollMode()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0fae 0068 0001 da 00 00')) + adapter.send.push(Buffer.from('11ef 0fae 0068 0001 da 00 00', 'hex')) // act const actual = await ultra.cmdMf1GetAntiCollMode() // assert expect(actual).toEqual(false) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0fae 0000 0000 43 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0fae 0000 0000 43 00', 'hex')]) }) test('#cmdMf1GetDetectionCount()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0fa5 0068 0004 e0 00000003 fd')) + adapter.send.push(Buffer.from('11ef 0fa5 0068 0004 e0 00000003 fd', 'hex')) // act const actual = await ultra.cmdMf1GetDetectionCount() // assert expect(actual).toEqual(3) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0fa5 0000 0000 4c 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0fa5 0000 0000 4c 00', 'hex')]) }) test('#cmdMf1GetDetectionEnable()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0fa7 0068 0001 e1 01 ff')) + adapter.send.push(Buffer.from('11ef 0fa7 0068 0001 e1 01 ff', 'hex')) // act const actual = await ultra.cmdMf1GetDetectionEnable() // assert expect(actual).toEqual(true) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0fa7 0000 0000 4a 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0fa7 0000 0000 4a 00', 'hex')]) }) test('#cmdMf1GetDetectionLogs()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0fa6 0068 0036 ad 03fcb908a16d6bc182304954057b639985b903fcb908a16d47129bc3a78799187711be4e03fcb908a16d5ffb408aac7fd168fd7eb758 25')) + adapter.send.push(Buffer.from('11ef 0fa6 0068 0036 ad 03fcb908a16d6bc182304954057b639985b903fcb908a16d47129bc3a78799187711be4e03fcb908a16d5ffb408aac7fd168fd7eb758 25', 'hex')) // act const actual = await ultra.cmdMf1GetDetectionLogs(0) @@ -807,36 +808,36 @@ describe('ChameleonUltra with BufferMockAdapter', () => { block: 3, isKeyB: false, isNested: false, - ar: Buffer.fromHexString('639985b9'), - nr: Buffer.fromHexString('4954057b'), - nt: Buffer.fromHexString('6bc18230'), - uid: Buffer.fromHexString('b908a16d'), + ar: Buffer.from('639985b9', 'hex'), + nr: Buffer.from('4954057b', 'hex'), + nt: Buffer.from('6bc18230', 'hex'), + uid: Buffer.from('b908a16d', 'hex'), }, { block: 3, isKeyB: false, isNested: false, - ar: Buffer.fromHexString('7711be4e'), - nr: Buffer.fromHexString('a7879918'), - nt: Buffer.fromHexString('47129bc3'), - uid: Buffer.fromHexString('b908a16d'), + ar: Buffer.from('7711be4e', 'hex'), + nr: Buffer.from('a7879918', 'hex'), + nt: Buffer.from('47129bc3', 'hex'), + uid: Buffer.from('b908a16d', 'hex'), }, { block: 3, isKeyB: false, isNested: false, - ar: Buffer.fromHexString('fd7eb758'), - nr: Buffer.fromHexString('ac7fd168'), - nt: Buffer.fromHexString('5ffb408a'), - uid: Buffer.fromHexString('b908a16d'), + ar: Buffer.from('fd7eb758', 'hex'), + nr: Buffer.from('ac7fd168', 'hex'), + nt: Buffer.from('5ffb408a', 'hex'), + uid: Buffer.from('b908a16d', 'hex'), }, ]) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0fa6 0000 0004 47 00000000 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0fa6 0000 0004 47 00000000 00', 'hex')]) }) test('#cmdMf1GetEmuSettings()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0fa9 0068 0005 db 0100000000 ff')) + adapter.send.push(Buffer.from('11ef 0fa9 0068 0005 db 0100000000 ff', 'hex')) // act const actual = await ultra.cmdMf1GetEmuSettings() @@ -849,49 +850,49 @@ describe('ChameleonUltra with BufferMockAdapter', () => { gen2: false, write: 0, }) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0fa9 0000 0000 48 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0fa9 0000 0000 48 00', 'hex')]) }) test('#cmdMf1GetGen1aMode()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0faa 0068 0001 de 00 00')) + adapter.send.push(Buffer.from('11ef 0faa 0068 0001 de 00 00', 'hex')) // act const actual = await ultra.cmdMf1GetGen1aMode() // assert expect(actual).toEqual(false) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0faa 0000 0000 47 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0faa 0000 0000 47 00', 'hex')]) }) test('#cmdMf1GetGen2Mode()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0fac 0068 0001 dc 00 00')) + adapter.send.push(Buffer.from('11ef 0fac 0068 0001 dc 00 00', 'hex')) // act const actual = await ultra.cmdMf1GetGen2Mode() // assert expect(actual).toEqual(false) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0fac 0000 0000 45 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0fac 0000 0000 45 00', 'hex')]) }) test('#cmdMf1GetWriteMode()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0fb0 0068 0001 d8 00 00')) + adapter.send.push(Buffer.from('11ef 0fb0 0068 0001 d8 00 00', 'hex')) // act const actual = await ultra.cmdMf1GetWriteMode() // assert expect(actual).toEqual(0) - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0fb0 0000 0000 41 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0fb0 0000 0000 41 00', 'hex')]) }) test('#cmdMf1IsSupport()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 07d1 0000 0000 28 00')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 07d1 0000 0000 28 00', 'hex')) // act const actual = await ultra.cmdMf1IsSupport() @@ -899,113 +900,113 @@ describe('ChameleonUltra with BufferMockAdapter', () => { // assert expect(actual).toEqual(true) expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 07d1 0000 0000 28 00'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 07d1 0000 0000 28 00', 'hex'), ]) }) test('#cmdMf1ReadBlock()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 07d8 0000 0010 11 877209e11d0804000392abdef258ec90 10')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 07d8 0000 0010 11 877209e11d0804000392abdef258ec90 10', 'hex')) // act const actual = await ultra.cmdMf1ReadBlock({ block: 0, - key: Buffer.fromHexString('FFFFFFFFFFFF'), + key: Buffer.from('FFFFFFFFFFFF', 'hex'), keyType: Mf1KeyType.KEY_A, }) // assert - expect(actual).toEqual(Buffer.fromHexString('877209e11d0804000392abdef258ec90')) + expect(actual).toEqual(Buffer.from('877209e11d0804000392abdef258ec90', 'hex')) expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 07d8 0000 0008 19 6000ffffffffffff a6'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 07d8 0000 0008 19 6000ffffffffffff a6', 'hex'), ]) }) test('#cmdMf1SetAntiCollMode()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0faf 0068 0000 da 00')) + adapter.send.push(Buffer.from('11ef 0faf 0068 0000 da 00', 'hex')) // act await ultra.cmdMf1SetAntiCollMode(false) // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0faf 0000 0001 41 00 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0faf 0000 0001 41 00 00', 'hex')]) }) test('#cmdMf1SetDetectionEnable()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0fa4 0068 0000 e5 00')) + adapter.send.push(Buffer.from('11ef 0fa4 0068 0000 e5 00', 'hex')) // act await ultra.cmdMf1SetDetectionEnable(true) // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0fa4 0000 0001 4c 01 ff')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0fa4 0000 0001 4c 01 ff', 'hex')]) }) test('#cmdMf1SetGen1aMode()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0fab 0068 0000 de 00')) + adapter.send.push(Buffer.from('11ef 0fab 0068 0000 de 00', 'hex')) // act await ultra.cmdMf1SetGen1aMode(false) // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0fab 0000 0001 45 00 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0fab 0000 0001 45 00 00', 'hex')]) }) test('#cmdMf1SetGen2Mode()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0fad 0068 0000 dc 00')) + adapter.send.push(Buffer.from('11ef 0fad 0068 0000 dc 00', 'hex')) // act await ultra.cmdMf1SetGen2Mode(false) // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0fad 0000 0001 43 00 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0fad 0000 0001 43 00 00', 'hex')]) }) test('#cmdMf1SetWriteMode()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 0fb1 0068 0000 d8 00')) + adapter.send.push(Buffer.from('11ef 0fb1 0068 0000 d8 00', 'hex')) // act await ultra.cmdMf1SetWriteMode(Mf1EmuWriteMode.NORMAL) // assert - expect(adapter.recv).toEqual([Buffer.fromHexString('11ef 0fb1 0000 0001 3f 00 00')]) + expect(adapter.recv).toEqual([Buffer.from('11ef 0fb1 0000 0001 3f 00 00', 'hex')]) }) test('#cmdMf1TestNtDistance()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 07d5 0000 0008 1c 877209e100000080 9d')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 07d5 0000 0008 1c 877209e100000080 9d', 'hex')) // act const actual = await ultra.cmdMf1TestNtDistance({ block: 0, - key: Buffer.fromHexString('FFFFFFFFFFFF'), + key: Buffer.from('FFFFFFFFFFFF', 'hex'), keyType: Mf1KeyType.KEY_A, }) // assert expect(actual).toMatchObject({ - dist: Buffer.fromHexString('00000080'), - uid: Buffer.fromHexString('877209e1'), + dist: Buffer.from('00000080', 'hex'), + uid: Buffer.from('877209e1', 'hex'), }) expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 07d5 0000 0008 1c 6000ffffffffffff a6'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 07d5 0000 0008 1c 6000ffffffffffff a6', 'hex'), ]) }) test('#cmdMf1TestPrngType()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 07d2 0000 0001 26 01 ff')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 07d2 0000 0001 26 01 ff', 'hex')) // act const actual = await ultra.cmdMf1TestPrngType() @@ -1013,16 +1014,16 @@ describe('ChameleonUltra with BufferMockAdapter', () => { // assert expect(actual).toEqual(Mf1PrngType.WEAK) expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 07d2 0000 0000 27 00'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 07d2 0000 0000 27 00', 'hex'), ]) }) test('#cmdMf1VblockManipulate()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 07db 0000 0000 1e 00')) - const key = Buffer.fromHexString('FFFFFFFFFFFF') + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 07db 0000 0000 1e 00', 'hex')) + const key = Buffer.from('FFFFFFFFFFFF', 'hex') // act await ultra.cmdMf1VblockManipulate( @@ -1033,37 +1034,37 @@ describe('ChameleonUltra with BufferMockAdapter', () => { // assert expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 07db 0000 0015 09 6004ffffffffffffc0000000016004ffffffffffff 83'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 07db 0000 0015 09 6004ffffffffffffc0000000016004ffffffffffff 83', 'hex'), ]) }) test('#cmdMf1WriteBlock()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 07d9 0000 0000 20 00')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 07d9 0000 0000 20 00', 'hex')) // act await ultra.cmdMf1WriteBlock({ block: 4, keyType: Mf1KeyType.KEY_A, - key: Buffer.fromHexString('FFFFFFFFFFFF'), - data: Buffer.fromHexString('00000000000000000000000000000000'), + key: Buffer.from('FFFFFFFFFFFF', 'hex'), + data: Buffer.from('00000000000000000000000000000000', 'hex'), }) // assert expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 07d9 0000 0018 08 6004ffffffffffff00000000000000000000000000000000 a2'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 07d9 0000 0018 08 6004ffffffffffff00000000000000000000000000000000 a2', 'hex'), ]) }) test('#hf14aInfo()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 07d0 0000 0009 20 0494194a3d04000800 bc')) // cmdHf14aScan - adapter.send.push(Buffer.fromHexString('11ef 07d1 0000 0000 28 00')) // cmdMf1IsSupport - adapter.send.push(Buffer.fromHexString('11ef 07d2 0000 0001 26 01 ff')) // cmdMf1TestPrngType + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 07d0 0000 0009 20 0494194a3d04000800 bc', 'hex')) // cmdHf14aScan + adapter.send.push(Buffer.from('11ef 07d1 0000 0000 28 00', 'hex')) // cmdMf1IsSupport + adapter.send.push(Buffer.from('11ef 07d2 0000 0001 26 01 ff', 'hex')) // cmdMf1TestPrngType // act const actual = await ultra.hf14aInfo() @@ -1071,10 +1072,10 @@ describe('ChameleonUltra with BufferMockAdapter', () => { // assert expect(actual).toMatchObject([{ antiColl: { - atqa: Buffer.fromHexString('0400'), - ats: Buffer.fromHexString(''), - sak: Buffer.fromHexString('08'), - uid: Buffer.fromHexString('94194a3d'), + atqa: Buffer.from('0400', 'hex'), + ats: Buffer.from('', 'hex'), + sak: Buffer.from('08', 'hex'), + uid: Buffer.from('94194a3d', 'hex'), }, nxpTypeBySak: 'MIFARE Classic 1K | Plus SE 1K | Plug S 2K | Plus X 2K', prngType: Mf1PrngType.WEAK, @@ -1083,142 +1084,142 @@ describe('ChameleonUltra with BufferMockAdapter', () => { test('#mf1VblockGetValue()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 07d8 0000 0010 11 01000000feffffff0100000001fe01fe 05')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 07d8 0000 0010 11 01000000feffffff0100000001fe01fe 05', 'hex')) // act const actual = await ultra.mf1VblockGetValue({ block: 1, - key: Buffer.fromHexString('FFFFFFFFFFFF'), + key: Buffer.from('FFFFFFFFFFFF', 'hex'), keyType: Mf1KeyType.KEY_A, }) // assert expect(actual).toMatchObject({ adr: 1, value: 1 }) expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 07d8 0000 0008 19 6001ffffffffffff a5'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 07d8 0000 0008 19 6001ffffffffffff a5', 'hex'), ]) }) test('#mf1VblockSetValue()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 07d9 0000 0000 20 00')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 07d9 0000 0000 20 00', 'hex')) // act await ultra.mf1VblockSetValue({ block: 4, keyType: Mf1KeyType.KEY_A, - key: Buffer.fromHexString('FFFFFFFFFFFF'), + key: Buffer.from('FFFFFFFFFFFF', 'hex'), }, { adr: 1, value: 1 }) // assert expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 07d9 0000 0018 08 6004ffffffffffff01000000feffffff0100000001fe01fe a7'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 07d9 0000 0018 08 6004ffffffffffff01000000feffffff0100000001fe01fe a7', 'hex'), ]) }) test('#mfuReadPages()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 07da 0000 0010 0f 040dc445420d2981e7480000e1100600 c7')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 07da 0000 0010 0f 040dc445420d2981e7480000e1100600 c7', 'hex')) // act const actual = await ultra.mfuReadPages({ pageOffset: 0 }) // assert - expect(actual).toEqual(Buffer.fromHexString('040dc445420d2981e7480000e1100600')) + expect(actual).toEqual(Buffer.from('040dc445420d2981e7480000e1100600', 'hex')) expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 07da 0000 0007 18 7403e800103000 61'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 07da 0000 0007 18 7403e800103000 61', 'hex'), ]) }) test('#mfuWritePage()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 07da 0000 0000 1f 00')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 07da 0000 0000 1f 00', 'hex')) // act - await ultra.mfuWritePage({ pageOffset: 9, data: Buffer.fromHexString('00000000') }) + await ultra.mfuWritePage({ pageOffset: 9, data: Buffer.from('00000000', 'hex') }) // assert expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 07da 0000 000b 14 7403e80030a20900000000 c6'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 07da 0000 000b 14 7403e80030a20900000000 c6', 'hex'), ]) }) test('#mf1Halt()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 07da 0000 0000 1f 00')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 07da 0000 0000 1f 00', 'hex')) // act await ultra.mf1Halt() // assert expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 07da 0000 0007 18 2003e800105000 95'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 07da 0000 0007 18 2003e800105000 95', 'hex'), ]) }) test('#mf1Gen1aReadBlocks()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 07da 0000 0000 1f 00')) - adapter.send.push(Buffer.fromHexString('11ef 07da 0000 0001 1e 0a f6')) - adapter.send.push(Buffer.fromHexString('11ef 07da 0000 0001 1e 0a f6')) - adapter.send.push(Buffer.fromHexString('11ef 07da 0000 0010 0f 877209e11d0804000392abdef258ec90 10')) - adapter.send.push(Buffer.fromHexString('11ef 07da 0000 0000 1f 00')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 07da 0000 0000 1f 00', 'hex')) + adapter.send.push(Buffer.from('11ef 07da 0000 0001 1e 0a f6', 'hex')) + adapter.send.push(Buffer.from('11ef 07da 0000 0001 1e 0a f6', 'hex')) + adapter.send.push(Buffer.from('11ef 07da 0000 0010 0f 877209e11d0804000392abdef258ec90 10', 'hex')) + adapter.send.push(Buffer.from('11ef 07da 0000 0000 1f 00', 'hex')) // act const actual = await ultra.mf1Gen1aReadBlocks(0, 1) // assert - expect(actual).toEqual(Buffer.fromHexString('877209e11d0804000392abdef258ec90')) + expect(actual).toEqual(Buffer.from('877209e11d0804000392abdef258ec90', 'hex')) expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 07da 0000 0007 18 2003e800105000 95'), - Buffer.fromHexString('11ef 07da 0000 0006 19 4803e8000740 86'), - Buffer.fromHexString('11ef 07da 0000 0006 19 4803e8000843 82'), - Buffer.fromHexString('11ef 07da 0000 0007 18 6c03e800103000 69'), - Buffer.fromHexString('11ef 07da 0000 0007 18 2003e800105000 95'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 07da 0000 0007 18 2003e800105000 95', 'hex'), + Buffer.from('11ef 07da 0000 0006 19 4803e8000740 86', 'hex'), + Buffer.from('11ef 07da 0000 0006 19 4803e8000843 82', 'hex'), + Buffer.from('11ef 07da 0000 0007 18 6c03e800103000 69', 'hex'), + Buffer.from('11ef 07da 0000 0007 18 2003e800105000 95', 'hex'), ]) }) test('#mf1Gen1aWriteBlocks()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 07da 0000 0000 1f 00')) - adapter.send.push(Buffer.fromHexString('11ef 07da 0000 0001 1e 0a f6')) - adapter.send.push(Buffer.fromHexString('11ef 07da 0000 0001 1e 0a f6')) - adapter.send.push(Buffer.fromHexString('11ef 07da 0000 0001 1e 0a f6')) - adapter.send.push(Buffer.fromHexString('11ef 07da 0000 0001 1e 0a f6')) - adapter.send.push(Buffer.fromHexString('11ef 07da 0000 0000 1f 00')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 07da 0000 0000 1f 00', 'hex')) + adapter.send.push(Buffer.from('11ef 07da 0000 0001 1e 0a f6', 'hex')) + adapter.send.push(Buffer.from('11ef 07da 0000 0001 1e 0a f6', 'hex')) + adapter.send.push(Buffer.from('11ef 07da 0000 0001 1e 0a f6', 'hex')) + adapter.send.push(Buffer.from('11ef 07da 0000 0001 1e 0a f6', 'hex')) + adapter.send.push(Buffer.from('11ef 07da 0000 0000 1f 00', 'hex')) // act await ultra.mf1Gen1aWriteBlocks(1, new Buffer(16)) // assert expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 07da 0000 0007 18 2003e800105000 95'), - Buffer.fromHexString('11ef 07da 0000 0006 19 4803e8000740 86'), - Buffer.fromHexString('11ef 07da 0000 0006 19 4803e8000843 82'), - Buffer.fromHexString('11ef 07da 0000 0007 18 6803e80010a001 fc'), - Buffer.fromHexString('11ef 07da 0000 0015 0a 6803e8008000000000000000000000000000000000 2d'), - Buffer.fromHexString('11ef 07da 0000 0007 18 2003e800105000 95'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 07da 0000 0007 18 2003e800105000 95', 'hex'), + Buffer.from('11ef 07da 0000 0006 19 4803e8000740 86', 'hex'), + Buffer.from('11ef 07da 0000 0006 19 4803e8000843 82', 'hex'), + Buffer.from('11ef 07da 0000 0007 18 6803e80010a001 fc', 'hex'), + Buffer.from('11ef 07da 0000 0015 0a 6803e8008000000000000000000000000000000000 2d', 'hex'), + Buffer.from('11ef 07da 0000 0007 18 2003e800105000 95', 'hex'), ]) }) test('#mf1CheckSectorKeys()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 07dc 0000 01ea 32 c0000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 4c')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 07dc 0000 01ea 32 c0000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 4c', 'hex')) // act const keys = Buffer.from('FFFFFFFFFFFF\n000000000000\nA0A1A2A3A4A5\nD3F7D3F7D3F7', 'hex').chunk(6) @@ -1226,23 +1227,23 @@ describe('ChameleonUltra with BufferMockAdapter', () => { // assert expect(actual).toMatchObject({ - [Mf1KeyType.KEY_A]: Buffer.fromHexString('FFFFFFFFFFFF'), - [Mf1KeyType.KEY_B]: Buffer.fromHexString('FFFFFFFFFFFF'), + [Mf1KeyType.KEY_A]: Buffer.from('FFFFFFFFFFFF', 'hex'), + [Mf1KeyType.KEY_B]: Buffer.from('FFFFFFFFFFFF', 'hex'), }) expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 07dc 0000 0022 fb 3fffffffffffffffffffffffffffffff000000000000a0a1a2a3a4a5d3f7d3f7d3f7 a3'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 07dc 0000 0022 fb 3fffffffffffffffffffffffffffffff000000000000a0a1a2a3a4a5d3f7d3f7d3f7 a3', 'hex'), ]) }) test('#mf1ReadSectorByKeys()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 07dc 0000 01ea 32 c0000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 4c')) - adapter.send.push(Buffer.fromHexString('11ef 07d8 0000 0010 11 877209e11d0804000392abdef258ec90 10')) - adapter.send.push(Buffer.fromHexString('11ef 07d8 0000 0010 11 00000000000000000000000000000000 00')) - adapter.send.push(Buffer.fromHexString('11ef 07d8 0000 0010 11 00000000000000000000000000000000 00')) - adapter.send.push(Buffer.fromHexString('11ef 07d8 0000 0010 11 000000000000ff078069ffffffffffff 17')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 07dc 0000 01ea 32 c0000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 4c', 'hex')) + adapter.send.push(Buffer.from('11ef 07d8 0000 0010 11 877209e11d0804000392abdef258ec90 10', 'hex')) + adapter.send.push(Buffer.from('11ef 07d8 0000 0010 11 00000000000000000000000000000000 00', 'hex')) + adapter.send.push(Buffer.from('11ef 07d8 0000 0010 11 00000000000000000000000000000000 00', 'hex')) + adapter.send.push(Buffer.from('11ef 07d8 0000 0010 11 000000000000ff078069ffffffffffff 17', 'hex')) // act const keys = Buffer.from('FFFFFFFFFFFF\n000000000000\nA0A1A2A3A4A5\nD3F7D3F7D3F7', 'hex').chunk(6) @@ -1251,31 +1252,31 @@ describe('ChameleonUltra with BufferMockAdapter', () => { // assert expect(actual).toEqual({ data: Buffer.concat([ - Buffer.fromHexString('877209e11d0804000392abdef258ec90'), - Buffer.fromHexString('00000000000000000000000000000000'), - Buffer.fromHexString('00000000000000000000000000000000'), - Buffer.fromHexString('ffffffffffffff078069ffffffffffff'), + Buffer.from('877209e11d0804000392abdef258ec90', 'hex'), + Buffer.from('00000000000000000000000000000000', 'hex'), + Buffer.from('00000000000000000000000000000000', 'hex'), + Buffer.from('ffffffffffffff078069ffffffffffff', 'hex'), ]), success: [true, true, true, true], }) expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 07dc 0000 0022 fb 3fffffffffffffffffffffffffffffff000000000000a0a1a2a3a4a5d3f7d3f7d3f7 a3'), - Buffer.fromHexString('11ef 07d8 0000 0008 19 6100ffffffffffff a5'), - Buffer.fromHexString('11ef 07d8 0000 0008 19 6101ffffffffffff a4'), - Buffer.fromHexString('11ef 07d8 0000 0008 19 6102ffffffffffff a3'), - Buffer.fromHexString('11ef 07d8 0000 0008 19 6103ffffffffffff a2'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 07dc 0000 0022 fb 3fffffffffffffffffffffffffffffff000000000000a0a1a2a3a4a5d3f7d3f7d3f7 a3', 'hex'), + Buffer.from('11ef 07d8 0000 0008 19 6100ffffffffffff a5', 'hex'), + Buffer.from('11ef 07d8 0000 0008 19 6101ffffffffffff a4', 'hex'), + Buffer.from('11ef 07d8 0000 0008 19 6102ffffffffffff a3', 'hex'), + Buffer.from('11ef 07d8 0000 0008 19 6103ffffffffffff a2', 'hex'), ]) }) test('#mf1WriteSectorByKeys()', async () => { // arrange - adapter.send.push(Buffer.fromHexString('11ef 03e9 0068 0000 ac 00')) // DeviceMode.READER - adapter.send.push(Buffer.fromHexString('11ef 07dc 0000 01ea 32 30000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 dc')) - adapter.send.push(Buffer.fromHexString('11ef 07d9 0000 0000 20 00')) - adapter.send.push(Buffer.fromHexString('11ef 07d9 0000 0000 20 00')) - adapter.send.push(Buffer.fromHexString('11ef 07d9 0000 0000 20 00')) - adapter.send.push(Buffer.fromHexString('11ef 07d9 0000 0000 20 00')) + adapter.send.push(Buffer.from('11ef 03e9 0068 0000 ac 00', 'hex')) // DeviceMode.READER + adapter.send.push(Buffer.from('11ef 07dc 0000 01ea 32 30000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 dc', 'hex')) + adapter.send.push(Buffer.from('11ef 07d9 0000 0000 20 00', 'hex')) + adapter.send.push(Buffer.from('11ef 07d9 0000 0000 20 00', 'hex')) + adapter.send.push(Buffer.from('11ef 07d9 0000 0000 20 00', 'hex')) + adapter.send.push(Buffer.from('11ef 07d9 0000 0000 20 00', 'hex')) // act const keys = Buffer.from('FFFFFFFFFFFF\n000000000000\nA0A1A2A3A4A5\nD3F7D3F7D3F7', 'hex').chunk(6) @@ -1290,12 +1291,12 @@ describe('ChameleonUltra with BufferMockAdapter', () => { // assert expect(actual).toEqual({ success: [true, true, true, true] }) expect(adapter.recv).toEqual([ - Buffer.fromHexString('11ef 03e9 0000 0001 13 01 ff'), // DeviceMode.READER - Buffer.fromHexString('11ef 07dc 0000 0022 fb cfffffffffffffffffffffffffffffff000000000000a0a1a2a3a4a5d3f7d3f7d3f7 13'), - Buffer.fromHexString('11ef 07d9 0000 0018 08 6104ffffffffffff00000000000000000000000000000000 a1'), - Buffer.fromHexString('11ef 07d9 0000 0018 08 6105ffffffffffff00000000000000000000000000000000 a0'), - Buffer.fromHexString('11ef 07d9 0000 0018 08 6106ffffffffffff00000000000000000000000000000000 9f'), - Buffer.fromHexString('11ef 07d9 0000 0018 08 6107ffffffffffffffffffffffffff078069ffffffffffff bb'), + Buffer.from('11ef 03e9 0000 0001 13 01 ff', 'hex'), // DeviceMode.READER + Buffer.from('11ef 07dc 0000 0022 fb cfffffffffffffffffffffffffffffff000000000000a0a1a2a3a4a5d3f7d3f7d3f7 13', 'hex'), + Buffer.from('11ef 07d9 0000 0018 08 6104ffffffffffff00000000000000000000000000000000 a1', 'hex'), + Buffer.from('11ef 07d9 0000 0018 08 6105ffffffffffff00000000000000000000000000000000 a0', 'hex'), + Buffer.from('11ef 07d9 0000 0018 08 6106ffffffffffff00000000000000000000000000000000 9f', 'hex'), + Buffer.from('11ef 07d9 0000 0018 08 6107ffffffffffffffffffffffffff078069ffffffffffff bb', 'hex'), ]) }) @@ -1307,7 +1308,7 @@ describe('ChameleonUltra with BufferMockAdapter', () => { { acl: '45687B3167880400C810002000000016 0E140001070208030904081000000000 00000000000000000000000000000000 BBF8D48E031978778869C19085AF2635', expected: true }, ])('#mf1IsValidAcl($acl) = $expected', async ({ acl, expected }) => { // act - const actual = ultra.mf1IsValidAcl(Buffer.fromHexString(acl)) + const actual = ultra.mf1IsValidAcl(Buffer.from(acl, 'hex')) // assert expect(actual).toBe(expected) diff --git a/src/ChameleonUltra.ts b/src/ChameleonUltra.ts index 5e417e1..97eb04a 100644 --- a/src/ChameleonUltra.ts +++ b/src/ChameleonUltra.ts @@ -1,9 +1,10 @@ import _ from 'lodash' import { Buffer } from '@taichunmin/buffer' import { errToJson, middlewareCompose, sleep, type MiddlewareComposeFn, versionCompare } from './helper' -import { type ReadableStream, type UnderlyingSink, WritableStream } from 'node:stream/web' +import { EventAsyncGenerator } from './EventAsyncGenerator' +import { EventEmitter } from './EventEmitter' +import { type ReadableStream, type UnderlyingSink, type WritableStreamDefaultController, WritableStream } from 'node:stream/web' import * as Decoder from './ResponseDecoder' -import createDebugger, { type Debugger } from 'debug' import { Cmd, @@ -36,7 +37,6 @@ import { const READ_DEFAULT_TIMEOUT = 5e3 const START_OF_FRAME = new Buffer(2).writeUInt16BE(0x11EF) const VERSION_SUPPORTED = { gte: '2.0', lt: '3.0' } as const - const WritableStream1: typeof WritableStream = (globalThis as any)?.WritableStream ?? WritableStream function isMf1BlockNo (block: any): boolean { @@ -50,31 +50,96 @@ function validateMf1BlockKey (block: any, keyType: any, key: any, prefix: string } /** - * The core library of "chameleon-ultra.js". The instance of this class must use exactly one adapter plugin to communication to ChameleonUltra. + * The core library of `chameleon-ultra.js`. You need to register exactly one adapter to the `ChameleonUltra` instance. + * + * @see You can learn how to use `@taichunmin/buffer` from {@link https://taichunmin.idv.tw/js-buffer/ | here}. + * @example + *
+ * Click here to expend import example. + * + * Example of import the library using `import` or `require`: + * + * ```js + * // import + * import { Buffer, ChameleonUltra } from 'chameleon-ultra.js' + * import SerialPortAdapter from 'chameleon-ultra.js/plugin/SerialPortAdapter' + * import WebbleAdapter from 'chameleon-ultra.js/plugin/WebbleAdapter' + * import WebserialAdapter from 'chameleon-ultra.js/plugin/WebserialAdapter' + * + * // require + * const { Buffer, ChameleonUltra } = require('chameleon-ultra.js') + * const SerialPortAdapter = require('chameleon-ultra.js/plugin/SerialPortAdapter') + * const WebbleAdapter = require('chameleon-ultra.js/plugin/WebbleAdapter') + * const WebserialAdapter = require('chameleon-ultra.js/plugin/WebserialAdapter') + * ``` + * + * Example of import the library in Browser (place at the end of body): + * + * ```html + * + * + * + * + * + * + * + * + * + * + * + * + * ``` + * + * After importing the SDK, you need to register exactly one adapter to the `ChameleonUltra` instance: + * + * ```js + * const ultraUsb = new ChameleonUltra() + * ultraUsb.use(new WebserialAdapter()) + * const ultraBle = new ChameleonUltra() + * ultraBle.use(new WebbleAdapter()) + * ``` + * + *
*/ export class ChameleonUltra { + #deviceMode: DeviceMode | null = null #isDisconnecting: boolean = false #rxSink?: ChameleonRxSink #supportedCmds: Set = new Set() - readonly #debug: boolean + readonly #hooks = new Map>() + readonly #middlewares = new Map() /** - * @internal - * @group Internal + * The supported version of SDK. + * @group Device Related */ - hooks: Record + static VERSION_SUPPORTED = VERSION_SUPPORTED /** * @internal * @group Internal */ - logger: Record = {} + readDefaultTimeout: number = READ_DEFAULT_TIMEOUT /** + * The event emitter of `ChameleonUltra`. + * - `disconnected`: Emitted when device is disconnected. + * - `connected`: Emitted when device is connected. + * - `debug`: Emitted when debug message is generated. `(logName: string, formatter: any, ...args: [] | any[]) => void` * @internal * @group Internal */ - plugins: Map + readonly emitter = new EventEmitter() /** * @internal @@ -82,93 +147,8 @@ export class ChameleonUltra { */ port?: ChameleonSerialPort - /** - * @internal - * @group Internal - */ - #deviceMode: DeviceMode | null = null - - /** - * The supported version of SDK. - * @group Device Related - */ - static VERSION_SUPPORTED = VERSION_SUPPORTED - - /** - * Create a new instance of ChameleonUltra. - * @param debug - Enable debug mode. - * @example - * Example usage in Browser (place at the end of body): - * - * ```html - * - * ``` - * - * Example usage in CommonJS: - * - * ```js - * const { Buffer, ChameleonUltra } = require('chameleon-ultra.js') - * const SerialPortAdapter = require('chameleon-ultra.js/plugin/SerialPortAdapter') - * const WebbleAdapter = require('chameleon-ultra.js/plugin/WebbleAdapter') - * const WebserialAdapter = require('chameleon-ultra.js/plugin/WebserialAdapter') - * - * const ultraNode = new ChameleonUltra() - * ultraNode.use(new SerialPortAdapter()) - * - * const ultraUsb = new ChameleonUltra() - * ultraUsb.use(new WebserialAdapter()) - * - * const ultraBle = new ChameleonUltra() - * ultraBle.use(new WebbleAdapter()) - * ``` - * - * Example usage in ESM: - * - * ```js - * import { Buffer, ChameleonUltra } from 'chameleon-ultra.js' - * import SerialPortAdapter from 'chameleon-ultra.js/plugin/SerialPortAdapter' - * import WebbleAdapter from 'chameleon-ultra.js/plugin/WebbleAdapter' - * import WebserialAdapter from 'chameleon-ultra.js/plugin/WebserialAdapter' - * - * const ultraNode = new ChameleonUltra() - * ultraNode.use(new SerialPortAdapter()) - * - * const ultraUsb = new ChameleonUltra() - * ultraUsb.use(new WebserialAdapter()) - * - * const ultraBle = new ChameleonUltra() - * ultraBle.use(new WebbleAdapter()) - * ``` - */ - constructor (debug = false) { - this.hooks = {} - this.plugins = new Map() - this.#debug = debug - _.extend(this.logger, { - core: this.createDebugger('core'), - resp: this.createDebugger('resp'), - respError: this.createDebugger('respError'), - send: this.createDebugger('send'), - }) - } - - /** - * @internal - * @group Plugin Related - */ - createDebugger (name: string): Logger { - if (!this.#debug) return (...args: any[]) => {} - return createDebugger(`ultra:${name}`) + #debug (namespace: string, formatter: any, ...args: [] | any[]): void { + this.emitter.emit('debug', namespace, formatter, ...args) } /** @@ -186,27 +166,30 @@ export class ChameleonUltra { /** * Register a hook. - * @param hook - The hook name. + * @param hookName - The hook name. * @param fn - The function to register. * @group Plugin Related */ - addHook (hook: string, fn: MiddlewareComposeFn): this { - if (!_.isArray(this.hooks[hook])) this.hooks[hook] = [] - this.hooks[hook].push(fn) + addHook (hookName: string, fn: MiddlewareComposeFn): this { + const middlewares = this.#middlewares.get(hookName) ?? [] + if (!this.#middlewares.has(hookName)) this.#middlewares.set(hookName, middlewares) + middlewares.push(fn) + this.#hooks.set(hookName, middlewareCompose(middlewares)) return this } /** * Invoke a hook with context. - * @param hook - The hook name. + * @param hookName - The hook name. * @param ctx - The context will be passed to every middleware. * @param next - The next middleware function. * @returns The return value depent on the middlewares * @group Plugin Related */ - async invokeHook (hook: string, ctx: any = {}, next?: MiddlewareComposeFn): Promise { - ctx.me = this - return await middlewareCompose(this.hooks[hook] ?? [])(ctx, next) + async invokeHook (hookName: string, ctx: any = {}, next?: MiddlewareComposeFn): Promise { + const hook = this.#hooks.get(hookName) ?? middlewareCompose([]) + if (!this.#hooks.has(hookName)) this.#hooks.set(hookName, hook) + return await hook({ ...ctx, ultra: this }, next) } /** @@ -219,19 +202,19 @@ export class ChameleonUltra { if (_.isNil(this.port)) throw new Error('this.port is undefined. Did you remember to use adapter plugin?') // serial.readable pipeTo this.rxSink - this.#rxSink = new ChameleonRxSink() - void this.port.readable.pipeTo(new WritableStream1(this.#rxSink), { - signal: this.#rxSink.signal, - }).catch(async err => { - if (err.message === 'disconnect()') return // disconnected by invoke disconnect() - await this.disconnect(_.merge(new Error(`Failed to read resp: ${err.message}`), { originalError: err })) - }) - - this.logger.core('chameleon connected') + const promiseConnected = new Promise(resolve => this.emitter.once('connected', resolve)) + this.#rxSink = new ChameleonRxSink(this) + void this.port.readable.pipeTo(new WritableStream1(this.#rxSink), this.#rxSink.abortController) + .catch(err => { this.#debug('rxSink', err) }) + + const connectedAt = await promiseConnected + this.#debug('core', `connected at ${connectedAt.toISOString()}`) } catch (err) { - this.logger.core(`Failed to connect: ${err.message as string}`) + err.message = `Failed to connect: ${err.message}` + this.emitter.emit('error', err) + this.#debug('error', `${err.message}\n${err.stack}`) if (this.isConnected()) await this.disconnect(err) - throw _.merge(new Error(err.message ?? 'Failed to connect'), { originalError: err }) + throw err } }) } @@ -243,20 +226,26 @@ export class ChameleonUltra { async disconnect (err: Error = new Error('disconnect()')): Promise { try { if (this.#isDisconnecting) return - this.logger.core('%s %O', err.message, errToJson(err)) this.#isDisconnecting = true // 避免重複執行 + this.#debug('core', '%s %O', err.message, errToJson(err)) + this.#debug('core', 'disconnecting...') await this.invokeHook('disconnect', { err }, async (ctx, next) => { try { // clean up this.#deviceMode = null this.#supportedCmds.clear() - // close port - this.#rxSink?.controller.abort(err) + const promiseDisconnected = new Promise<[Date, string | undefined]>(resolve => { + this.emitter.once('disconnected', (disconnected: Date, reason?: string) => { resolve([disconnected, reason]) }) + }) + this.#rxSink?.abortController.abort(err) while (this.port?.readable?.locked === true) await sleep(10) await this.port?.readable?.cancel(err) await this.port?.writable?.close() delete this.port + + const [disconnectedAt, reason] = await promiseDisconnected + this.#debug('core', `disconnected at ${disconnectedAt.toISOString()}, reason = ${reason ?? '?'}`) } catch (err) { throw _.merge(new Error(err.message ?? 'Failed to disconnect'), { originalError: err }) } @@ -274,31 +263,20 @@ export class ChameleonUltra { return this?.port?.isOpen?.() ?? false } - /** - * Calculate the LRC byte of a buffer. - * @internal - * @group Internal - */ - _calcLrc (buf: Buffer): number { - return 0x100 - _.sum(buf) & 0xFF - } - /** * Send a buffer to device. * @param buf - The buffer to be sent to device. * @internal * @group Internal */ - async _writeBuffer (buf: Buffer): Promise { - await this.invokeHook('_writeBuffer', { buf }, async (ctx, next) => { - if (!Buffer.isBuffer(ctx.buf)) throw new TypeError('buf should be a Buffer') - if (!this.isConnected()) await this.connect() - this.logger.send(ChameleonUltraFrame.inspect(ctx.buf)) - const writer = (this.port?.writable as any)?.getWriter() - if (_.isNil(writer)) throw new Error('Failed to getWriter(). Did you remember to use adapter plugin?') - await writer.write(ctx.buf) - writer.releaseLock() - }) + async #sendBuffer (buf: Buffer): Promise { + if (!Buffer.isBuffer(buf)) throw new TypeError('buf should be a Buffer') + if (!this.isConnected()) await this.connect() + this.#debug('send', ChameleonUltraFrame.inspect(buf)) + const writer = (this.port?.writable as any)?.getWriter() + if (_.isNil(writer)) throw new Error('Failed to getWriter(). Did you remember to use adapter plugin?') + await writer.write(buf) + writer.releaseLock() } /** @@ -309,34 +287,16 @@ export class ChameleonUltra { * @internal * @group Internal */ - async _writeCmd (opts: { + async #sendCmd (opts: { cmd: Cmd status?: RespStatus data?: Buffer }): Promise { const { cmd, status = 0, data = Buffer.allocUnsafe(0) } = opts - const buf = Buffer.allocUnsafe(data.length + 10) - START_OF_FRAME.copy(buf, 0) // SOF + SOF LRC Byte - // head info - buf.writeUInt16BE(cmd, 2) - buf.writeUInt16BE(status, 4) - buf.writeUInt16BE(data.length, 6) - buf[8] = this._calcLrc(buf.subarray(2, 8)) // head lrc byte - // data - if (data.length > 0) data.copy(buf, 9) - // lrc byte of buf - buf[buf.length - 1] = this._calcLrc(buf.subarray(9, -1)) - await this._writeBuffer(buf) - } - - /** - * Return the buffers in rxSink and clear rxSink. - * @returns The buffers in rxSink. - * @internal - * @group Internal - */ - _clearRxBufs (): Buffer[] { - return this.#rxSink?.bufs.splice(0, this.#rxSink.bufs.length) ?? [] + const buf = Buffer.pack(`!2sHHHx${data.length}sx`, START_OF_FRAME, cmd, status, data.length, data) + buf[8] = bufLrc(buf.subarray(2, 8)) // head lrc byte + buf[buf.length - 1] = bufLrc(buf.subarray(9, -1)) // lrc of buf + await this.#sendBuffer(buf) } /** @@ -345,56 +305,49 @@ export class ChameleonUltra { * @internal * @group Internal */ - async _readRespTimeout ({ cmd, timeout }: { cmd?: Cmd, timeout?: number } = {}): Promise { - interface Context { - startedAt?: number - nowts?: number - timeout?: number - resp?: ChameleonUltraFrame - } - return await this.invokeHook('_readRespTimeout', { timeout }, async (ctx: Context, next) => { + async #createReadRespFn (args: { + cmd?: Cmd + filter?: (resp: ChameleonUltraFrame) => boolean + timeout?: number + }): Promise<() => Promise> { + try { if (!this.isConnected()) await this.connect() if (_.isNil(this.#rxSink)) throw new Error('rxSink is undefined') - ctx.timeout = ctx.timeout ?? READ_DEFAULT_TIMEOUT - ctx.startedAt = Date.now() - while (true) { - if (!this.isConnected()) throw new Error('device disconnected') - ctx.nowts = Date.now() - if (ctx.nowts > ctx.startedAt + ctx.timeout) throw new Error(`readRespTimeout ${ctx.timeout}ms`) - let buf = Buffer.concat(this._clearRxBufs()) - try { - const sofIdx = buf.indexOf(START_OF_FRAME) - if (sofIdx < 0) throw new Error('SOF not found') - else if (sofIdx > 0) throw _.merge(new Error('ignore bytes before SOF'), { skip: sofIdx }) - // sof + sof lrc + cmd (2) + status (2) + data len (2) + head lrc + data + data lrc - if (buf.length < 10) throw new Error('buf.length < 10') - if (this._calcLrc(buf.subarray(2, 8)) !== buf[8]) throw _.merge(new Error('head lrc mismatch'), { skip: 1 }) - const lenFrame = buf.readUInt16BE(6) + 10 - if (buf.length < lenFrame) throw new Error('waiting for more data') - if (this._calcLrc(buf.subarray(9, -1)) !== buf[buf.length - 1]) throw _.merge(new Error('data lrc mismatch'), { skip: 1 }) - ctx.resp = new ChameleonUltraFrame(buf.subarray(0, lenFrame)) - if (!_.isNil(cmd) && ctx.resp.cmd !== cmd) throw _.merge(new Error(`expect cmd=${cmd} but receive cmd=${ctx.resp.cmd}`), { skip: lenFrame }) - // resp is valid - if (buf.length > lenFrame) this.#rxSink.bufs.unshift(buf.subarray(lenFrame)) - break - } catch (err) { - const skip = err.skip ?? 0 - if (skip > 0) { - this.logger.respError(`readRespTimeout skip ${skip} byte(s), reason = ${err.message}`) - buf = buf.subarray(skip) + if (_.isNil(args.timeout)) args.timeout = this.readDefaultTimeout + const respGenerator = new EventAsyncGenerator() + this.emitter.on('resp', respGenerator.onData) + this.emitter.once('disconnected', respGenerator.onClose) + let timeout: any | undefined + respGenerator.removeCallback = () => { + this.emitter.removeListener('resp', respGenerator.onData) + this.emitter.removeListener('disconnected', respGenerator.onClose) + if (!_.isNil(timeout)) clearTimeout(timeout) + } + return async () => { + timeout = setTimeout(() => { + respGenerator.onError(new Error(`read resp timeout (${args.timeout}ms)`)) + }, args.timeout) + let resp: ChameleonUltraFrame | null = null + for await (const buf of respGenerator) { + const resp1 = new ChameleonUltraFrame(buf) + if (!_.isNil(args.cmd) && resp1.cmd !== args.cmd) continue + if (!(args.filter?.(resp1) ?? true)) continue + if (RespStatusFail.has(resp1.status)) { + this.#debug('respError', resp1.inspect) + const status = resp1.status + throw _.merge(new Error(RespStatusMsg.get(status)), { status, data: { resp } }) } - this.#rxSink.bufs.unshift(buf) + this.#debug('resp', resp1.inspect) + resp = resp1 + break } - await sleep(10) - } - if (RespStatusFail.has(ctx.resp.status)) { - const status = ctx.resp.status - this.logger.respError(ctx.resp.inspect) - throw _.merge(new Error(RespStatusMsg.get(status)), { status, data: { resp: ctx.resp } }) + if (_.isNil(resp)) throw new Error('device disconnected') + return resp } - this.logger.resp(ctx.resp.inspect) - return ctx.resp - }) as ChameleonUltraFrame + } catch (err) { + this.#debug('error', `${err.message}\n${err.stack}`) + throw err + } } /** @@ -411,10 +364,10 @@ export class ChameleonUltra { * ``` */ async cmdGetAppVersion (): Promise<`${number}.${number}`> { - this._clearRxBufs() const cmd = Cmd.GET_APP_VERSION // cmd = 1000 - await this._writeCmd({ cmd }) - const { status, data } = await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + const { status, data } = await readResp() if (status === RespStatus.HF_TAG_OK && data.readUInt16BE(0) === 0x0001) throw new Error('Unsupported protocol. Firmware update is required.') return `${data[0]}.${data[1]}` } @@ -435,10 +388,10 @@ export class ChameleonUltra { */ async cmdChangeDeviceMode (mode: DeviceMode): Promise { if (!isDeviceMode(mode)) throw new TypeError('Invalid device mode') - this._clearRxBufs() const cmd = Cmd.CHANGE_DEVICE_MODE // cmd = 1001 - await this._writeCmd({ cmd, data: Buffer.pack('!B', mode) }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!B', mode) }) + await readResp() this.#deviceMode = mode } @@ -458,10 +411,10 @@ export class ChameleonUltra { * ``` */ async cmdGetDeviceMode (): Promise { - this._clearRxBufs() const cmd = Cmd.GET_DEVICE_MODE // cmd = 1002 - await this._writeCmd({ cmd }) - const data = (await this._readRespTimeout({ cmd }))?.data + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + const data = (await readResp()).data this.#deviceMode = data[0] return this.#deviceMode } @@ -491,10 +444,10 @@ export class ChameleonUltra { */ async cmdSlotSetActive (slot: Slot): Promise { if (!isSlot(slot)) throw new TypeError('Invalid slot') - this._clearRxBufs() const cmd = Cmd.SET_ACTIVE_SLOT // cmd = 1003 - await this._writeCmd({ cmd, data: Buffer.pack('!B', slot) }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!B', slot) }) + await readResp() } /** @@ -515,10 +468,10 @@ export class ChameleonUltra { async cmdSlotChangeTagType (slot: Slot, tagType: TagType): Promise { if (!isSlot(slot)) throw new TypeError('Invalid slot') if (!isTagType(tagType)) throw new TypeError('Invalid tagType') - this._clearRxBufs() const cmd = Cmd.SET_SLOT_TAG_TYPE // cmd = 1004 - await this._writeCmd({ cmd, data: Buffer.pack('!BH', slot, tagType) }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!BH', slot, tagType) }) + await readResp() } /** @@ -539,10 +492,10 @@ export class ChameleonUltra { async cmdSlotResetTagType (slot: Slot, tagType: TagType): Promise { if (!isSlot(slot)) throw new TypeError('Invalid slot') if (!isTagType(tagType)) throw new TypeError('Invalid tagType') - this._clearRxBufs() const cmd = Cmd.SET_SLOT_DATA_DEFAULT // cmd = 1005 - await this._writeCmd({ cmd, data: Buffer.pack('!BH', slot, tagType) }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!BH', slot, tagType) }) + await readResp() } /** @@ -564,10 +517,10 @@ export class ChameleonUltra { if (!isSlot(slot)) throw new TypeError('Invalid slot') if (!isValidFreqType(freq)) throw new TypeError('Invalid freq') if (_.isNil(enable)) throw new TypeError('enable is required') - this._clearRxBufs() const cmd = Cmd.SET_SLOT_ENABLE // cmd = 1006 - await this._writeCmd({ cmd, data: Buffer.pack('!BB?', slot, freq, enable) }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!BB?', slot, freq, enable) }) + await readResp() } /** @@ -592,10 +545,10 @@ export class ChameleonUltra { if (!_.isString(name)) throw new TypeError('name should be a string') const buf1 = Buffer.from(name) if (!_.inRange(buf1.length, 1, 33)) throw new TypeError('byteLength of name should between 1 and 32') - this._clearRxBufs() const cmd = Cmd.SET_SLOT_TAG_NICK // cmd = 1007 - await this._writeCmd({ cmd, data: Buffer.pack(`!BB${buf1.length}s`, slot, freq, buf1) }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack(`!BB${buf1.length}s`, slot, freq, buf1) }) + await readResp() } /** @@ -619,10 +572,10 @@ export class ChameleonUltra { try { if (!isSlot(slot)) throw new TypeError('Invalid slot') if (!isValidFreqType(freq)) throw new TypeError('Invalid freq') - this._clearRxBufs() const cmd = Cmd.GET_SLOT_TAG_NICK // cmd = 1008 - await this._writeCmd({ cmd, data: Buffer.pack('!BB', slot, freq) }) - return (await this._readRespTimeout({ cmd }))?.data.toString('utf8') + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!BB', slot, freq) }) + return (await readResp()).data.toString('utf8') } catch (err) { if (err.status === RespStatus.FLASH_READ_FAIL) return // slot name is empty throw err @@ -644,10 +597,10 @@ export class ChameleonUltra { * ``` */ async cmdSlotSaveSettings (): Promise { - this._clearRxBufs() const cmd = Cmd.SLOT_DATA_CONFIG_SAVE // cmd = 1009 - await this._writeCmd({ cmd }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + await readResp() } /** @@ -663,9 +616,8 @@ export class ChameleonUltra { * ``` */ async cmdEnterBootloader (): Promise { - this._clearRxBufs() const cmd = Cmd.ENTER_BOOTLOADER // cmd = 1010 - await this._writeCmd({ cmd }) + await this.#sendCmd({ cmd }) } /** @@ -682,10 +634,10 @@ export class ChameleonUltra { * ``` */ async cmdGetDeviceChipId (): Promise { - this._clearRxBufs() const cmd = Cmd.GET_DEVICE_CHIP_ID // cmd = 1011 - await this._writeCmd({ cmd }) - const data = (await this._readRespTimeout({ cmd }))?.data + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + const data = (await readResp()).data return data.toString('hex') } @@ -703,10 +655,10 @@ export class ChameleonUltra { * ``` */ async cmdBleGetAddress (): Promise { - this._clearRxBufs() const cmd = Cmd.GET_DEVICE_ADDRESS // cmd = 1012 - await this._writeCmd({ cmd }) - const data = (await this._readRespTimeout({ cmd }))?.data + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + const data = (await readResp()).data return (_.toUpper(data.toString('hex')).match(/.{2}/g) ?? []).join(':') } @@ -723,10 +675,10 @@ export class ChameleonUltra { * ``` */ async cmdSaveSettings (): Promise { - this._clearRxBufs() const cmd = Cmd.SAVE_SETTINGS // cmd = 1013 - await this._writeCmd({ cmd }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + await readResp() } /** @@ -742,10 +694,10 @@ export class ChameleonUltra { * ``` */ async cmdResetSettings (): Promise { - this._clearRxBufs() const cmd = Cmd.RESET_SETTINGS // cmd = 1014 - await this._writeCmd({ cmd }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + await readResp() } /** @@ -764,10 +716,10 @@ export class ChameleonUltra { */ async cmdSetAnimationMode (mode: AnimationMode): Promise { if (!isAnimationMode(mode)) throw new TypeError('Invalid mode') - this._clearRxBufs() const cmd = Cmd.SET_ANIMATION_MODE // cmd = 1015 - await this._writeCmd({ cmd, data: Buffer.pack('!B', mode) }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!B', mode) }) + await readResp() } /** @@ -786,10 +738,10 @@ export class ChameleonUltra { * ``` */ async cmdGetAnimationMode (): Promise { - this._clearRxBufs() const cmd = Cmd.GET_ANIMATION_MODE // cmd = 1016 - await this._writeCmd({ cmd }) - return (await this._readRespTimeout({ cmd }))?.data[0] + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + return (await readResp()).data[0] } /** @@ -806,11 +758,10 @@ export class ChameleonUltra { * ``` */ async cmdGetGitVersion (): Promise { - this._clearRxBufs() const cmd = Cmd.GET_GIT_VERSION // cmd = 1017 - await this._writeCmd({ cmd }) - const data = (await this._readRespTimeout({ cmd }))?.data - return data.toString('utf8') + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + return (await readResp()).data.toString('utf8') } /** @@ -829,10 +780,10 @@ export class ChameleonUltra { * ``` */ async cmdSlotGetActive (): Promise { - this._clearRxBufs() const cmd = Cmd.GET_ACTIVE_SLOT // cmd = 1018 - await this._writeCmd({ cmd }) - return (await this._readRespTimeout({ cmd }))?.data[0] + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + return (await readResp()).data[0] } /** @@ -862,10 +813,10 @@ export class ChameleonUltra { * ``` */ async cmdSlotGetInfo (): Promise { - this._clearRxBufs() const cmd = Cmd.GET_SLOT_INFO // cmd = 1019 - await this._writeCmd({ cmd }) - return Decoder.SlotInfo.fromCmd1019((await this._readRespTimeout({ cmd }))?.data) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + return Decoder.SlotInfo.fromCmd1019((await readResp()).data) } /** @@ -881,10 +832,10 @@ export class ChameleonUltra { * ``` */ async cmdWipeFds (): Promise { - this._clearRxBufs() const cmd = Cmd.WIPE_FDS // cmd = 1020 - await this._writeCmd({ cmd }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + await readResp() } /** @@ -907,10 +858,10 @@ export class ChameleonUltra { try { if (!isSlot(slot)) throw new TypeError('Invalid slot') if (!isValidFreqType(freq)) throw new TypeError('Invalid freq') - this._clearRxBufs() const cmd = Cmd.DELETE_SLOT_TAG_NICK // cmd = 1021 - await this._writeCmd({ cmd, data: Buffer.pack('!BB', slot, freq) }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!BB', slot, freq) }) + await readResp() return true } catch (err) { if (err.status === RespStatus.FLASH_WRITE_FAIL) return false // slot name is empty @@ -943,10 +894,10 @@ export class ChameleonUltra { * ``` */ async cmdSlotGetIsEnable (): Promise { - this._clearRxBufs() const cmd = Cmd.GET_ENABLED_SLOTS // cmd = 1023 - await this._writeCmd({ cmd }) - return Decoder.SlotFreqIsEnable.fromCmd1023((await this._readRespTimeout({ cmd }))?.data) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + return Decoder.SlotFreqIsEnable.fromCmd1023((await readResp()).data) } /** @@ -967,10 +918,10 @@ export class ChameleonUltra { async cmdSlotDeleteFreqType (slot: Slot, freq: FreqType): Promise { if (!isSlot(slot)) throw new TypeError('Invalid slot') if (!isValidFreqType(freq)) throw new TypeError('Invalid freq') - this._clearRxBufs() const cmd = Cmd.DELETE_SLOT_SENSE_TYPE // cmd = 1024 - await this._writeCmd({ cmd, data: Buffer.pack('!BB', slot, freq) }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!BB', slot, freq) }) + await readResp() } /** @@ -988,10 +939,10 @@ export class ChameleonUltra { * ``` */ async cmdGetBatteryInfo (): Promise { - this._clearRxBufs() const cmd = Cmd.GET_BATTERY_INFO // cmd = 1025 - await this._writeCmd({ cmd }) - return Decoder.BatteryInfo.fromCmd1025((await this._readRespTimeout({ cmd }))?.data) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + return Decoder.BatteryInfo.fromCmd1025((await readResp()).data) } /** @@ -1012,10 +963,10 @@ export class ChameleonUltra { */ async cmdGetButtonPressAction (btn: ButtonType): Promise { if (!isButtonType(btn)) throw new TypeError('Invalid btn') - this._clearRxBufs() const cmd = Cmd.GET_BUTTON_PRESS_CONFIG // cmd = 1026 - await this._writeCmd({ cmd, data: Buffer.pack('!B', btn) }) - return (await this._readRespTimeout({ cmd }))?.data[0] + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!B', btn) }) + return (await readResp()).data[0] } /** @@ -1036,10 +987,10 @@ export class ChameleonUltra { async cmdSetButtonPressAction (btn: ButtonType, action: ButtonAction): Promise { if (!isButtonType(btn)) throw new TypeError('Invalid btn') if (!isButtonAction(action)) throw new TypeError('Invalid action') - this._clearRxBufs() const cmd = Cmd.SET_BUTTON_PRESS_CONFIG // cmd = 1027 - await this._writeCmd({ cmd, data: Buffer.pack('!BB', btn, action) }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!BB', btn, action) }) + await readResp() } /** @@ -1060,10 +1011,10 @@ export class ChameleonUltra { */ async cmdGetButtonLongPressAction (btn: ButtonType): Promise { if (!isButtonType(btn)) throw new TypeError('Invalid btn') - this._clearRxBufs() const cmd = Cmd.GET_LONG_BUTTON_PRESS_CONFIG // cmd = 1028 - await this._writeCmd({ cmd, data: Buffer.pack('!B', btn) }) - return (await this._readRespTimeout({ cmd }))?.data[0] + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!B', btn) }) + return (await readResp()).data[0] } /** @@ -1084,10 +1035,10 @@ export class ChameleonUltra { async cmdSetButtonLongPressAction (btn: ButtonType, action: ButtonAction): Promise { if (!isButtonType(btn)) throw new TypeError('Invalid btn') if (!isButtonAction(action)) throw new TypeError('Invalid action') - this._clearRxBufs() const cmd = Cmd.SET_LONG_BUTTON_PRESS_CONFIG // cmd = 1029 - await this._writeCmd({ cmd, data: Buffer.pack('!BB', btn, action) }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!BB', btn, action) }) + await readResp() } /** @@ -1105,10 +1056,10 @@ export class ChameleonUltra { */ async cmdBleSetPairingKey (key: string): Promise { if (!_.isString(key) || !/^\d{6}$/.test(key)) throw new TypeError('Invalid key, must be 6 digits') - this._clearRxBufs() const cmd = Cmd.SET_BLE_PAIRING_KEY // cmd = 1030 - await this._writeCmd({ cmd, data: Buffer.from(key) }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.from(key) }) + await readResp() } /** @@ -1125,10 +1076,10 @@ export class ChameleonUltra { * ``` */ async cmdBleGetPairingKey (): Promise { - this._clearRxBufs() const cmd = Cmd.GET_BLE_PAIRING_KEY // cmd = 1031 - await this._writeCmd({ cmd }) - return (await this._readRespTimeout({ cmd }))?.data.toString('utf8') + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + return (await readResp()).data.toString('utf8') } /** @@ -1144,10 +1095,10 @@ export class ChameleonUltra { * ``` */ async cmdBleDeleteAllBonds (): Promise { - this._clearRxBufs() const cmd = Cmd.DELETE_ALL_BLE_BONDS // cmd = 1032 - await this._writeCmd({ cmd }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + await readResp() } /** @@ -1166,11 +1117,10 @@ export class ChameleonUltra { * ``` */ async cmdGetDeviceModel (): Promise { - this._clearRxBufs() const cmd = Cmd.GET_DEVICE_MODEL // cmd = 1033 - await this._writeCmd({ cmd }) - const data = (await this._readRespTimeout({ cmd }))?.data - return data[0] + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + return (await readResp()).data[0] } /** @@ -1198,10 +1148,10 @@ export class ChameleonUltra { * ``` */ async cmdGetDeviceSettings (): Promise { - this._clearRxBufs() const cmd = Cmd.GET_DEVICE_SETTINGS // cmd = 1034 - await this._writeCmd({ cmd }) - return Decoder.DeviceSettings.fromCmd1034((await this._readRespTimeout({ cmd }))?.data) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + return Decoder.DeviceSettings.fromCmd1034((await readResp()).data) } /** @@ -1219,10 +1169,10 @@ export class ChameleonUltra { * ``` */ async cmdGetSupportedCmds (): Promise> { - this._clearRxBufs() const cmd = Cmd.GET_DEVICE_CAPABILITIES // cmd = 1035 - await this._writeCmd({ cmd }) - const data = (await this._readRespTimeout({ cmd }))?.data + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + const data = (await readResp()).data const cmds = new Set() for (let i = 0; i < data.length; i += 2) cmds.add(data.readUInt16BE(i)) return cmds @@ -1260,10 +1210,10 @@ export class ChameleonUltra { * ``` */ async cmdBleGetPairingMode (): Promise { - this._clearRxBufs() const cmd = Cmd.GET_BLE_PAIRING_ENABLE // cmd = 1036 - await this._writeCmd({ cmd }) - return (await this._readRespTimeout({ cmd }))?.data[0] === 1 + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + return (await readResp()).data[0] === 1 } /** @@ -1281,10 +1231,10 @@ export class ChameleonUltra { */ async cmdBleSetPairingMode (enable: boolean | number): Promise { if (_.isNil(enable)) throw new TypeError('enable is required') - this._clearRxBufs() const cmd = Cmd.SET_BLE_PAIRING_ENABLE // cmd = 1037 - await this._writeCmd({ cmd, data: Buffer.pack('!?', enable) }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!?', enable) }) + await readResp() } /** @@ -1305,10 +1255,10 @@ export class ChameleonUltra { */ async cmdHf14aScan (): Promise { await this.assureDeviceMode(DeviceMode.READER) - this._clearRxBufs() const cmd = Cmd.HF14A_SCAN // cmd = 2000 - await this._writeCmd({ cmd }) - return Decoder.Hf14aAntiColl.fromCmd2000((await this._readRespTimeout({ cmd }))?.data) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + return Decoder.Hf14aAntiColl.fromCmd2000((await readResp()).data) } /** @@ -1327,10 +1277,10 @@ export class ChameleonUltra { async cmdMf1IsSupport (): Promise { try { await this.assureDeviceMode(DeviceMode.READER) - this._clearRxBufs() const cmd = Cmd.MF1_DETECT_SUPPORT // cmd = 2001 - await this._writeCmd({ cmd }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + await readResp() return true } catch (err) { if (err.status === RespStatus.HF_ERR_STAT) return false @@ -1354,10 +1304,10 @@ export class ChameleonUltra { */ async cmdMf1TestPrngType (): Promise { await this.assureDeviceMode(DeviceMode.READER) - this._clearRxBufs() const cmd = Cmd.MF1_DETECT_NT_LEVEL // cmd = 2002 - await this._writeCmd({ cmd }) - return (await this._readRespTimeout({ cmd }))?.data[0] + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + return (await readResp()).data[0] } /** @@ -1409,10 +1359,10 @@ export class ChameleonUltra { if (!isMf1BlockNo(target.block)) throw new TypeError('Invalid target.block') if (!isMf1KeyType(target.keyType)) throw new TypeError('Invalid target.keyType') await this.assureDeviceMode(DeviceMode.READER) - this._clearRxBufs() const cmd = Cmd.MF1_STATIC_NESTED_ACQUIRE // cmd = 2003 - await this._writeCmd({ cmd, data: Buffer.pack('!BB6sBB', known.keyType, known.block, known.key, target.keyType, target.block) }) - return Decoder.Mf1AcquireStaticNestedRes.fromCmd2003((await this._readRespTimeout({ cmd }))?.data) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!BB6sBB', known.keyType, known.block, known.key, target.keyType, target.block) }) + return Decoder.Mf1AcquireStaticNestedRes.fromCmd2003((await readResp()).data) } /** @@ -1491,10 +1441,10 @@ export class ChameleonUltra { if (_.isNil(isFirst)) throw new TypeError('Invalid isFirst') if (!_.isSafeInteger(syncMax)) throw new TypeError('Invalid syncMax') await this.assureDeviceMode(DeviceMode.READER) - this._clearRxBufs() const cmd = Cmd.MF1_DARKSIDE_ACQUIRE // cmd = 2004 - await this._writeCmd({ cmd, data: Buffer.pack('!BB?B', keyType, block, isFirst, syncMax) }) - return Decoder.Mf1DarksideRes.fromCmd2004((await this._readRespTimeout({ cmd, timeout: syncMax * 1e4 }))?.data) + const readResp = await this.#createReadRespFn({ cmd, timeout: syncMax * 1e4 }) + await this.#sendCmd({ cmd, data: Buffer.pack('!BB?B', keyType, block, isFirst, syncMax) }) + return Decoder.Mf1DarksideRes.fromCmd2004((await readResp()).data) } /** @@ -1541,10 +1491,10 @@ export class ChameleonUltra { async cmdMf1TestNtDistance (known: { block: number, key: Buffer, keyType: Mf1KeyType }): Promise { validateMf1BlockKey(known.block, known.keyType, known.key, 'known.') await this.assureDeviceMode(DeviceMode.READER) - this._clearRxBufs() const cmd = Cmd.MF1_DETECT_NT_DIST // cmd = 2005 - await this._writeCmd({ cmd, data: Buffer.pack('!BB6s', known.keyType, known.block, known.key) }) - return Decoder.Mf1NtDistanceRes.fromCmd2005((await this._readRespTimeout({ cmd }))?.data) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!BB6s', known.keyType, known.block, known.key) }) + return Decoder.Mf1NtDistanceRes.fromCmd2005((await readResp()).data) } /** @@ -1599,10 +1549,10 @@ export class ChameleonUltra { if (!_.isSafeInteger(target.block)) throw new TypeError('Invalid target.block') if (!isMf1KeyType(target.keyType)) throw new TypeError('Invalid target.keyType') await this.assureDeviceMode(DeviceMode.READER) - this._clearRxBufs() const cmd = Cmd.MF1_NESTED_ACQUIRE // cmd = 2006 - await this._writeCmd({ cmd, data: Buffer.pack('!BB6sBB', known.keyType, known.block, known.key, target.keyType, target.block) }) - return Decoder.Mf1NestedRes.fromCmd2006((await this._readRespTimeout({ cmd }))?.data) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!BB6sBB', known.keyType, known.block, known.key, target.keyType, target.block) }) + return Decoder.Mf1NestedRes.fromCmd2006((await readResp()).data) } /** @@ -1633,10 +1583,10 @@ export class ChameleonUltra { try { validateMf1BlockKey(block, keyType, key) await this.assureDeviceMode(DeviceMode.READER) - this._clearRxBufs() const cmd = Cmd.MF1_AUTH_ONE_KEY_BLOCK // cmd = 2007 - await this._writeCmd({ cmd, data: Buffer.pack('!BB6s', keyType, block, key) }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!BB6s', keyType, block, key) }) + await readResp() return true } catch (err) { if (err.status === RespStatus.MF_ERR_AUTH) return false @@ -1672,10 +1622,10 @@ export class ChameleonUltra { const { block, keyType, key } = opts validateMf1BlockKey(block, keyType, key) await this.assureDeviceMode(DeviceMode.READER) - this._clearRxBufs() const cmd = Cmd.MF1_READ_ONE_BLOCK // cmd = 2008 - await this._writeCmd({ cmd, data: Buffer.pack('!BB6s', keyType, block, key) }) - return (await this._readRespTimeout({ cmd }))?.data + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!BB6s', keyType, block, key) }) + return (await readResp()).data } /** @@ -1713,10 +1663,10 @@ export class ChameleonUltra { validateMf1BlockKey(block, keyType, key) if (!Buffer.isBuffer(data) || data.length !== 16) throw new TypeError('data should be a Buffer with length 16') await this.assureDeviceMode(DeviceMode.READER) - this._clearRxBufs() const cmd = Cmd.MF1_WRITE_ONE_BLOCK // cmd = 2009 - await this._writeCmd({ cmd, data: Buffer.pack('!BB6s16s', keyType, block, key, data) }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!BB6s16s', keyType, block, key, data) }) + await readResp() } /** @@ -1759,8 +1709,8 @@ export class ChameleonUltra { * @param opts.data - The data to be send. If `appendCrc` is `true`, the maximum length of data is `62`, otherwise is `64`. * @param opts.dataBitLength - Number of bits to send. Useful for send partial byte. `dataBitLength` is incompatible with `appendCrc`. * @param opts.keepRfField - Set `true` to keep the RF field active after sending. - * @param opts.waitResponse - Default value is `true`. Set `false` to skip reading tag response. - * @param opts.timeout - Default value is `1000 ms`. Maximum timeout for reading tag response in ms while `waitResponse` is `true`. + * @param opts.readResponse - Default value is `true`. Set `false` to skip reading tag response. + * @param opts.timeout - Default value is `1000 ms`. Maximum timeout for reading tag response in ms while `readResponse` is `true`. * @returns The response from tag. * @group Reader Related */ @@ -1806,10 +1756,10 @@ export class ChameleonUltra { ] as Array<[number, boolean]>) buf1.writeBitMSB(val, bitOffset) await this.assureDeviceMode(DeviceMode.READER) - this._clearRxBufs() const cmd = Cmd.HF14A_RAW // cmd = 2010 - await this._writeCmd({ cmd, data: buf1 }) - return (await this._readRespTimeout({ cmd, timeout: READ_DEFAULT_TIMEOUT + timeout }))?.data + const readResp = await this.#createReadRespFn({ cmd, timeout: READ_DEFAULT_TIMEOUT + timeout }) + await this.#sendCmd({ cmd, data: buf1 }) + return (await readResp()).data } /** @@ -1860,11 +1810,11 @@ export class ChameleonUltra { if (!isMf1VblockOperator(operator)) throw new TypeError('Invalid operator') if (!_.isSafeInteger(operand)) throw new TypeError('Invalid operand') await this.assureDeviceMode(DeviceMode.READER) - this._clearRxBufs() const cmd = Cmd.MF1_MANIPULATE_VALUE_BLOCK // cmd = 2011 const data = Buffer.pack('!BB6sBiBB6s', src.keyType, src.block, src.key, operator, operand, dst.keyType, dst.block, dst.key) - await this._writeCmd({ cmd, data }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data }) + await readResp() } /** @@ -2000,12 +1950,12 @@ export class ChameleonUltra { if (bitsCnt < 1) return null await this.assureDeviceMode(DeviceMode.READER) - this._clearRxBufs() const cmd = Cmd.MF1_CHECK_KEYS_OF_SECTORS // cmd = 2012 const data = Buffer.concat([mask, ...keys]) const timeout = READ_DEFAULT_TIMEOUT + bitsCnt * (keys.length + 1) * 100 - await this._writeCmd({ cmd, data }) - return Decoder.Mf1CheckKeysOfSectorsRes.fromCmd2012((await this._readRespTimeout({ cmd, timeout }))?.data) + const readResp = await this.#createReadRespFn({ cmd, timeout }) + await this.#sendCmd({ cmd, data }) + return Decoder.Mf1CheckKeysOfSectorsRes.fromCmd2012((await readResp()).data) } /** @@ -2024,10 +1974,10 @@ export class ChameleonUltra { */ async cmdEm410xScan (): Promise { await this.assureDeviceMode(DeviceMode.READER) - this._clearRxBufs() const cmd = Cmd.EM410X_SCAN // cmd = 3000 - await this._writeCmd({ cmd }) - return (await this._readRespTimeout({ cmd }))?.data + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + return (await readResp()).data } /** @@ -2047,12 +1997,12 @@ export class ChameleonUltra { async cmdEm410xWriteToT55xx (id: Buffer): Promise { if (!Buffer.isBuffer(id) || id.length !== 5) throw new TypeError('id should be a Buffer with length 5') await this.assureDeviceMode(DeviceMode.READER) - this._clearRxBufs() const cmd = Cmd.EM410X_WRITE_TO_T55XX // cmd = 3001 const oldKeys = [0x51243648, 0x19920427] const data = Buffer.pack(`!5sI${oldKeys.length}I`, id, 0x20206666, ...oldKeys) - await this._writeCmd({ cmd, data }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data }) + await readResp() } /** @@ -2073,10 +2023,10 @@ export class ChameleonUltra { async cmdMf1EmuWriteBlock (offset: number, data: Buffer): Promise { if (!_.isSafeInteger(offset)) throw new TypeError('Invalid offset') if (!Buffer.isBuffer(data) || data.length % 16 !== 0) throw new TypeError('data should be a Buffer with length be multiples of 16') - this._clearRxBufs() const cmd = Cmd.MF1_WRITE_EMU_BLOCK_DATA // cmd = 4000 - await this._writeCmd({ cmd, data: Buffer.pack(`!B${data.length}s`, offset, data) }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack(`!B${data.length}s`, offset, data) }) + await readResp() } /** @@ -2111,10 +2061,10 @@ export class ChameleonUltra { if (!Buffer.isBuffer(atqa) || atqa.length !== 2) throw new TypeError('atqa should be a Buffer with length 2') if (!Buffer.isBuffer(sak) || sak.length !== 1) throw new TypeError('sak should be a Buffer with length 1') if (!Buffer.isBuffer(ats)) throw new TypeError('ats should be a Buffer') - this._clearRxBufs() const cmd = Cmd.HF14A_SET_ANTI_COLL_DATA // cmd = 4001 - await this._writeCmd({ cmd, data: Buffer.pack(`!${uid.length + 1}p2ss${ats.length + 1}p`, uid, atqa, sak, ats) }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack(`!${uid.length + 1}p2ss${ats.length + 1}p`, uid, atqa, sak, ats) }) + await readResp() } /** @@ -2132,10 +2082,10 @@ export class ChameleonUltra { */ async cmdMf1SetDetectionEnable (enable: boolean | number): Promise { if (_.isNil(enable)) throw new TypeError('enable is required') - this._clearRxBufs() const cmd = Cmd.MF1_SET_DETECTION_ENABLE // cmd = 4004 - await this._writeCmd({ cmd, data: Buffer.pack('!?', enable) }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!?', enable) }) + await readResp() } /** @@ -2152,10 +2102,10 @@ export class ChameleonUltra { * ``` */ async cmdMf1GetDetectionCount (): Promise { - this._clearRxBufs() const cmd = Cmd.MF1_GET_DETECTION_COUNT // cmd = 4005 - await this._writeCmd({ cmd }) - return (await this._readRespTimeout({ cmd }))?.data.readUInt32BE() + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + return (await readResp()).data.readUInt32BE() } /** @@ -2186,10 +2136,10 @@ export class ChameleonUltra { */ async cmdMf1GetDetectionLogs (offset: number = 0): Promise { if (!_.isSafeInteger(offset)) throw new TypeError('Invalid offset') - this._clearRxBufs() const cmd = Cmd.MF1_GET_DETECTION_LOG // cmd = 4006 - await this._writeCmd({ cmd, data: Buffer.pack('!I', offset) }) - return Decoder.Mf1DetectionLog.fromCmd4006((await this._readRespTimeout({ cmd }))?.data) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!I', offset) }) + return Decoder.Mf1DetectionLog.fromCmd4006((await readResp()).data) } /** @@ -2206,10 +2156,10 @@ export class ChameleonUltra { * ``` */ async cmdMf1GetDetectionEnable (): Promise { - this._clearRxBufs() const cmd = Cmd.MF1_GET_DETECTION_ENABLE // cmd = 4007 - await this._writeCmd({ cmd }) - return (await this._readRespTimeout({ cmd }))?.data[0] === 1 + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + return (await readResp()).data[0] === 1 } /** @@ -2229,10 +2179,10 @@ export class ChameleonUltra { * ``` */ async cmdMf1EmuReadBlock (offset: number = 0, length: number = 1): Promise { - this._clearRxBufs() const cmd = Cmd.MF1_READ_EMU_BLOCK_DATA // cmd = 4008 - await this._writeCmd({ cmd, data: Buffer.pack('!BB', offset, length) }) - return (await this._readRespTimeout({ cmd }))?.data + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!BB', offset, length) }) + return (await readResp()).data } /** @@ -2259,10 +2209,10 @@ export class ChameleonUltra { * ``` */ async cmdMf1GetEmuSettings (): Promise { - this._clearRxBufs() const cmd = Cmd.MF1_GET_EMULATOR_CONFIG // cmd = 4009 - await this._writeCmd({ cmd }) - return Decoder.Mf1EmuSettings.fromCmd4009((await this._readRespTimeout({ cmd }))?.data) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + return Decoder.Mf1EmuSettings.fromCmd4009((await readResp()).data) } /** @@ -2279,10 +2229,10 @@ export class ChameleonUltra { * ``` */ async cmdMf1GetGen1aMode (): Promise { - this._clearRxBufs() const cmd = Cmd.MF1_GET_GEN1A_MODE // cmd = 4010 - await this._writeCmd({ cmd }) - return (await this._readRespTimeout({ cmd }))?.data[0] === 1 + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + return (await readResp()).data[0] === 1 } /** @@ -2300,10 +2250,10 @@ export class ChameleonUltra { */ async cmdMf1SetGen1aMode (enable: boolean | number): Promise { if (_.isNil(enable)) throw new TypeError('enable is required') - this._clearRxBufs() const cmd = Cmd.MF1_SET_GEN1A_MODE // cmd = 4011 - await this._writeCmd({ cmd, data: Buffer.pack('!?', enable) }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!?', enable) }) + await readResp() } /** @@ -2320,10 +2270,10 @@ export class ChameleonUltra { * ``` */ async cmdMf1GetGen2Mode (): Promise { - this._clearRxBufs() const cmd = Cmd.MF1_GET_GEN2_MODE // cmd = 4012 - await this._writeCmd({ cmd }) - return (await this._readRespTimeout({ cmd }))?.data[0] === 1 + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + return (await readResp()).data[0] === 1 } /** @@ -2341,10 +2291,10 @@ export class ChameleonUltra { */ async cmdMf1SetGen2Mode (enable: boolean | number): Promise { if (_.isNil(enable)) throw new TypeError('enable is required') - this._clearRxBufs() const cmd = Cmd.MF1_SET_GEN2_MODE // cmd = 4013 - await this._writeCmd({ cmd, data: Buffer.pack('!?', enable) }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!?', enable) }) + await readResp() } /** @@ -2361,10 +2311,10 @@ export class ChameleonUltra { * ``` */ async cmdMf1GetAntiCollMode (): Promise { - this._clearRxBufs() const cmd = Cmd.HF14A_GET_BLOCK_ANTI_COLL_MODE // cmd = 4014 - await this._writeCmd({ cmd }) - return (await this._readRespTimeout({ cmd }))?.data[0] === 1 + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + return (await readResp()).data[0] === 1 } /** @@ -2382,10 +2332,10 @@ export class ChameleonUltra { */ async cmdMf1SetAntiCollMode (enable: boolean | number): Promise { if (_.isNil(enable)) throw new TypeError('enable is required') - this._clearRxBufs() const cmd = Cmd.HF14A_SET_BLOCK_ANTI_COLL_MODE // cmd = 4015 - await this._writeCmd({ cmd, data: Buffer.pack('!?', enable) }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!?', enable) }) + await readResp() } /** @@ -2402,10 +2352,10 @@ export class ChameleonUltra { * ``` */ async cmdMf1GetWriteMode (): Promise { - this._clearRxBufs() const cmd = Cmd.MF1_GET_WRITE_MODE // cmd = 4016 - await this._writeCmd({ cmd }) - return (await this._readRespTimeout({ cmd }))?.data[0] + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + return (await readResp()).data[0] } /** @@ -2424,10 +2374,10 @@ export class ChameleonUltra { */ async cmdMf1SetWriteMode (mode: Mf1EmuWriteMode): Promise { if (!isMf1EmuWriteMode(mode)) throw new TypeError('Invalid mode') - this._clearRxBufs() const cmd = Cmd.MF1_SET_WRITE_MODE // cmd = 4017 - await this._writeCmd({ cmd, data: Buffer.pack('!B', mode) }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: Buffer.pack('!B', mode) }) + await readResp() } /** @@ -2451,10 +2401,10 @@ export class ChameleonUltra { * ``` */ async cmdHf14aGetAntiCollData (): Promise { - this._clearRxBufs() const cmd = Cmd.HF14A_GET_ANTI_COLL_DATA // cmd = 4018 - await this._writeCmd({ cmd }) - const data = (await this._readRespTimeout({ cmd }))?.data + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + const data = (await readResp()).data return data.length > 0 ? Decoder.Hf14aAntiColl.fromBuffer(data) : null } @@ -2474,10 +2424,10 @@ export class ChameleonUltra { */ async cmdEm410xSetEmuId (id: Buffer): Promise { if (!Buffer.isBuffer(id) || id.length !== 5) throw new TypeError('id should be a Buffer with length 5') - this._clearRxBufs() const cmd = Cmd.EM410X_SET_EMU_ID // cmd = 5000 - await this._writeCmd({ cmd, data: id }) - await this._readRespTimeout({ cmd }) + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd, data: id }) + await readResp() } /** @@ -2495,10 +2445,10 @@ export class ChameleonUltra { * ``` */ async cmdEm410xGetEmuId (): Promise { - this._clearRxBufs() const cmd = Cmd.EM410X_GET_EMU_ID // cmd = 5001 - await this._writeCmd({ cmd }) - return (await this._readRespTimeout({ cmd }))?.data + const readResp = await this.#createReadRespFn({ cmd }) + await this.#sendCmd({ cmd }) + return (await readResp()).data } /** @@ -2807,7 +2757,7 @@ export class ChameleonUltra { break } catch (err) { if (!this.isConnected()) throw err - this.logger.core(`Failed to read block ${sector * 4 + i} with ${Mf1KeyType[keyType]} = ${key.toString('hex')}`) + this.#debug('mf1', `Failed to read block ${sector * 4 + i} with ${Mf1KeyType[keyType]} = ${key.toString('hex')}`) } } } @@ -2858,7 +2808,7 @@ export class ChameleonUltra { break } catch (err) { if (!this.isConnected()) throw err - this.logger.core(`Failed to write block ${sector * 4 + i} with ${Mf1KeyType[keyType]} = ${key.toString('hex')}`) + this.#debug('mf1', `Failed to write block ${sector * 4 + i} with ${Mf1KeyType[keyType]} = ${key.toString('hex')}`) } } } @@ -2891,12 +2841,6 @@ export class ChameleonUltra { } } -/** - * @internal - * @group Internal - */ -export type Logger = Debugger | ((...args: any[]) => void) - const RespStatusMsg = new Map([ [RespStatus.HF_TAG_OK, 'HF tag operation succeeded'], [RespStatus.HF_TAG_NOT_FOUND, 'HF tag not found error'], @@ -2947,17 +2891,65 @@ export interface ChameleonSerialPort { } class ChameleonRxSink implements UnderlyingSink { + #closed: boolean = false + #started: boolean = false + abortController: AbortController = new AbortController() bufs: Buffer[] = [] - controller: AbortController + readonly #ultra: ChameleonUltra + + constructor (ultra: ChameleonUltra) { + this.#ultra = ultra + } - constructor () { - this.controller = new AbortController() + start (controller: WritableStreamDefaultController): void { + if (this.#closed) throw new Error('rxSink is closed') + if (this.#started) throw new Error('rxSink is already started') + this.#ultra.emitter.emit('connected', new Date()) + this.#started = true } - get signal (): AbortSignal { return this.controller.signal } + write (chunk: Buffer, controller: WritableStreamDefaultController): void { + if (!this.#started || this.#closed) return + if (!Buffer.isBuffer(chunk)) chunk = Buffer.fromView(chunk) + this.bufs.push(chunk) + let buf = Buffer.concat(this.bufs.splice(0, this.bufs.length)) + try { + while (buf.length > 0) { + const sofIdx = buf.indexOf(START_OF_FRAME) + if (sofIdx < 0) break // end, SOF not found + else if (sofIdx > 0) buf = buf.subarray(sofIdx) // ignore bytes before SOF + // sof + sof lrc + cmd (2) + status (2) + data len (2) + head lrc + data + data lrc + if (buf.length < 10) break // end, buf.length < 10 + if (bufLrc(buf.subarray(2, 8)) !== buf[8]) { + buf = buf.subarray(1) // skip 1 byte, head lrc mismatch + continue + } + const lenFrame = buf.readUInt16BE(6) + 10 + if (buf.length < lenFrame) break // end, wait for more data + if (bufLrc(buf.subarray(9, lenFrame - 1)) !== buf[lenFrame - 1]) { + buf = buf.subarray(1) // skip 1 byte, data lrc mismatch + continue + } + this.#ultra.emitter.emit('resp', buf.slice(0, lenFrame)) + buf = buf.subarray(lenFrame) + } + } finally { + if (buf.length > 0) this.bufs.push(buf) + } + } + + close (): void { + if (this.#closed) return + this.#closed = true + this.abortController.abort() + this.#ultra.emitter.emit('disconnected', new Date()) + } - write (chunk: Buffer): void { - this.bufs.push(Buffer.from(chunk)) + abort (reason: any): void { + if (this.#closed) return + this.#closed = true + this.abortController.abort() + this.#ultra.emitter.emit('disconnected', new Date(), reason) } } @@ -3022,4 +3014,10 @@ const NxpTypeBySak = new Map([ [0x38, 'SmartMX with MIFARE Classic 4K'], ]) +function bufLrc (buf: Buffer): number { + let sum = 0 + for (const u8 of buf) sum += u8 + return 0x100 - sum & 0xFF +} + export { Decoder as ResponseDecoder } diff --git a/src/EventAsyncGenerator.test.ts b/src/EventAsyncGenerator.test.ts new file mode 100644 index 0000000..cbaba9d --- /dev/null +++ b/src/EventAsyncGenerator.test.ts @@ -0,0 +1,215 @@ +import { EventEmitter } from 'events' +import { EventAsyncGenerator } from './EventAsyncGenerator' + +const sleep = async (t: number): Promise => new Promise(resolve => setTimeout(resolve, t)) + +describe('EventIterable', () => { + test('should await immediate onData value', async () => { + const it = new EventAsyncGenerator() + it.onData(1) + expect(await it.next()).toEqual({ value: 1, done: false }) + }) + + test('should await dalayed onData value', async () => { + const it = new EventAsyncGenerator() + void sleep(10).then(() => { it.onData(1) }) // no wait + expect(await it.next()).toEqual({ value: 1, done: false }) + }) + + test('should await immediate end', async () => { + const it = new EventAsyncGenerator() + it.onClose() + expect(await it.next()).toEqual({ value: undefined, done: true }) + }) + + test('should await delayed end', async () => { + const it = new EventAsyncGenerator() + void sleep(10).then(async () => { it.onClose() }) // no wait + expect(await it.next()).toEqual({ value: undefined, done: true }) + }) + + test('should await immediate error', async () => { + expect.assertions(1) + + try { + const it = new EventAsyncGenerator() + it.onError(new Error()) + await it.next() + } catch (err) { + expect(err).toBeInstanceOf(Error) + } + }) + + test('should await delayed error', async () => { + expect.assertions(1) + + try { + const it = new EventAsyncGenerator() + void sleep(10).then(async () => { it.onError(new Error()) }) // no wait + await it.next() + } catch (err) { + expect(err).toBeInstanceOf(Error) + } + }) + + test('does not yield new items if return has been called', async () => { + const it = new EventAsyncGenerator() + void it.return(undefined) + expect(await it.next()).toEqual({ value: undefined, done: true }) + }) + + test('does not queue for new items if return has been called', async () => { + const it = new EventAsyncGenerator() + it.onData(1) + expect(await it.next()).toEqual({ value: 1, done: false }) + expect(await it.return(undefined)).toEqual({ value: undefined, done: true }) + it.onData(2) + expect(await it.next()).toEqual({ value: undefined, done: true }) + }) + + test('should call remove handler once with no arguments on immediate end', async () => { + const it = new EventAsyncGenerator() + it.removeCallback = jest.fn() + it.onClose() + expect(it.removeCallback).toHaveBeenCalledTimes(1) + expect(it.removeCallback).toHaveBeenCalledWith() + }) + + test('should call remove handler once with no arguments on delayed end', async () => { + const it = new EventAsyncGenerator() + it.removeCallback = jest.fn() + void sleep(10).then(() => { it.onClose() }) // no wait + await sleep(20) + expect(it.removeCallback).toHaveBeenCalledTimes(1) + expect(it.removeCallback).toHaveBeenCalledWith() + }) + + test('should call remove handler on immediate return', async () => { + const it = new EventAsyncGenerator() + it.removeCallback = jest.fn() + await it.return(undefined) + expect(it.removeCallback).toHaveBeenCalledTimes(1) + expect(it.removeCallback).toHaveBeenCalledWith() + }) + + test('should call remove handler on delayed return', async () => { + const it = new EventAsyncGenerator() + it.removeCallback = jest.fn() + void sleep(10).then(async () => { await it.return(undefined) }) // no wait + await sleep(20) + expect(it.removeCallback).toHaveBeenCalledTimes(1) + expect(it.removeCallback).toHaveBeenCalledWith() + }) + + test('should call remove handler on immediate error', async () => { + expect.hasAssertions() + const it = new EventAsyncGenerator() + try { + it.removeCallback = jest.fn() + it.onError(new Error()) + await it.next() + } catch (err) { + expect(err).toBeInstanceOf(Error) + expect(it.removeCallback).toHaveBeenCalledTimes(1) + expect(it.removeCallback).toHaveBeenCalledWith() + } + }) + + test('should call remove handler on delayed error', async () => { + expect.hasAssertions() + const it = new EventAsyncGenerator() + try { + it.removeCallback = jest.fn() + void sleep(10).then(() => { it.onError(new Error()) }) // no wait + await it.next() + } catch (err) { + expect(err).toBeInstanceOf(Error) + expect(it.removeCallback).toHaveBeenCalledTimes(1) + expect(it.removeCallback).toHaveBeenCalledWith() + } + }) + + test('should buffer iterator calls when the queue is empty', async () => { + const event = new EventEmitter() + const it = new EventAsyncGenerator() + event.on('data', it.onData) + it.removeCallback = () => { + event.removeListener('data', it.onData) + } + + const reqs = Promise.all([it.next(), it.next()]) + event.emit('data', 1) + event.emit('data', 2) + + const actual = await reqs + expect(actual).toMatchObject([ + { value: 1, done: false }, + { value: 2, done: false }, + ]) + }) + + test('should broadcast all reqs when event emitter closes', async () => { + const event = new EventEmitter() + const it = new EventAsyncGenerator() + event.on('data', it.onData) + event.on('close', it.onClose) + it.removeCallback = () => { + event.removeListener('data', it.onData) + event.removeListener('close', it.onClose) + } + + event.emit('data', 1) + event.emit('data', 2) + event.emit('close') + event.emit('data', 3) + + const actual = await Promise.all([it.next(), it.next(), it.next(), it.next()]) + expect(actual).toMatchObject([ + { value: 1, done: false }, + { value: 2, done: false }, + { value: undefined, done: true }, + { value: undefined, done: true }, + ]) + }) + + test('it should buffer iterator calls and yield undefined after return', async () => { + const event = new EventEmitter() + const it = new EventAsyncGenerator() + event.on('data', it.onData) + it.removeCallback = () => { + event.removeListener('data', it.onData) + } + + const reqs = Promise.all([it.next(), it.return(undefined), it.next()]) + void sleep(10).then(() => { event.emit('data', 1) }) // no wait + void sleep(20).then(() => { event.emit('data', 2) }) // no wait + void sleep(30).then(() => { event.emit('data', 3) }) // no wait + + const actual = await reqs + expect(actual).toMatchObject([ + { value: 1, done: false }, + { value: undefined, done: true }, + { value: undefined, done: true }, + ]) + }) + + test('it should buffer values and yield undefined after return', async () => { + const event = new EventEmitter() + const it = new EventAsyncGenerator() + event.on('data', it.onData) + it.removeCallback = () => { + event.removeListener('data', it.onData) + } + + event.emit('data', 1) + event.emit('data', 2) + event.emit('data', 3) + + const actual = await Promise.all([it.next(), it.return(undefined), it.next()]) + expect(actual).toMatchObject([ + { value: 1, done: false }, + { value: undefined, done: true }, + { value: undefined, done: true }, + ]) + }) +}) diff --git a/src/EventAsyncGenerator.ts b/src/EventAsyncGenerator.ts new file mode 100644 index 0000000..5d89260 --- /dev/null +++ b/src/EventAsyncGenerator.ts @@ -0,0 +1,90 @@ +const symbolClose = Symbol.for('EventAsyncGenerator.close') + +export class EventAsyncGenerator implements AsyncGenerator { + #isFinally = false + #pullPromise: Resolvable | null = null + readonly #it: AsyncGenerator + readonly #queue: Array = [] + + onClose: () => void + onData: (value: T) => void + onError: (err: Error) => void + removeCallback?: () => void | Promise + + constructor (init?: (me: EventAsyncGenerator) => void | Promise) { + const me = this // eslint-disable-line @typescript-eslint/no-this-alias + this.onData = (value: T) => { + if (this.#pullPromise !== null) this.#pullPromise.resolve?.(value) + else this.#queue.push(value) + } + this.onClose = () => { + if (this.#pullPromise !== null) this.#pullPromise.resolve?.(symbolClose) + else this.#queue.push(symbolClose) + void this.finally() + } + this.onError = (err: Error) => { + if (this.#pullPromise !== null) this.#pullPromise.reject?.(err) + else this.#queue.push(err) + void this.finally() + } + this.#it = (async function * () { + try { + await init?.(me) + while (true) { + let valueOrErr: T | typeof symbolClose | Error + if (me.#queue.length > 0) valueOrErr = me.#queue.shift() as T | typeof symbolClose | Error + else { + me.#pullPromise = createResolvable() + valueOrErr = await me.#pullPromise.catch(err => err) + me.#pullPromise = null + } + if (valueOrErr === symbolClose) return + if (valueOrErr instanceof Error) throw valueOrErr + yield valueOrErr + } + } finally { + await me.finally() + } + })() as AsyncGenerator + } + + async next (...args: [] | [TNext]): Promise> { + return await this.#it.next(...args) + } + + async return (value: TReturn | PromiseLike): Promise> { + const result = await this.#it.return(value) + await this.finally() + return result + } + + async throw (err: Error): Promise> { + const result = await this.#it.throw(err) + await this.finally() + return result + } + + async finally (): Promise { + if (this.#isFinally) return + this.#isFinally = true + await this.removeCallback?.() + } + + [Symbol.asyncIterator] (): this { + return this + } +} + +type Resolvable = Promise & { + resolve: (t: T) => void + reject: (err: Error) => void +} + +function createResolvable (): Resolvable { + let resolve, reject + const resolvable = new Promise((...args) => { + ;[resolve, reject] = args + }) as Resolvable + Object.assign(resolvable, { resolve, reject }) + return resolvable +} diff --git a/src/EventEmitter.test.ts b/src/EventEmitter.test.ts new file mode 100644 index 0000000..b19340d --- /dev/null +++ b/src/EventEmitter.test.ts @@ -0,0 +1,415 @@ +import { EventEmitter } from './EventEmitter' +// import { EventEmitter } from 'node:events' + +describe('Event: error', () => { + test('should emit error event', () => { + expect.hasAssertions() + const emitter = new EventEmitter() + emitter.on('error', err => { + expect(err).toBeInstanceOf(Error) + expect(err.message).toBe('test') + }) + emitter.on('test', () => { + throw new Error('test') + }) + emitter.emit('test') + }) + + test('should capture rejections of promises', async () => { + expect.hasAssertions() + const emitter = new EventEmitter() + emitter.on('error', err => { + expect(err).toBeInstanceOf(Error) + expect(err.message).toBe('test') + }) + emitter.on('test', (async () => { + throw new Error('test') + }) as any) + emitter.emit('test') + }) +}) + +describe('Event: newListener', () => { + test('should emit newListener event', () => { + class TestEmitter1 extends EventEmitter {} + const emitter = new TestEmitter1() + const actual: string[] = [] + emitter.once('newListener', (eventName, listener) => { + if (eventName !== 'test') return + emitter.on('test', () => { actual.push('newListener') }) + }) + expect(emitter.listenerCount('test')).toBe(0) + emitter.on('test', () => { actual.push('test') }) + expect(emitter.listenerCount('test')).toBe(2) + emitter.emit('test') + expect(actual).toEqual(['newListener', 'test']) + }) +}) + +describe('Event: removeListener', () => { + test('should emit removeListener event', () => { + const emitter = new EventEmitter() + const actual: string[] = [] + emitter.once('removeListener', (eventName, listener) => { + if (eventName !== 'test') return + actual.push('removeListener') + }) + emitter.once('test', () => { actual.push('test') }) + emitter.emit('test') + expect(actual).toEqual(['removeListener', 'test']) + }) +}) + +describe('function alias test', () => { + test.each([ + { a: 'addListener', b: 'on' }, + { a: 'off', b: 'removeListener' }, + ] as const)('EventEmitter#$a should be alias of EventEmitter#$b', ({ a, b }) => { + const emitter = new EventEmitter() + expect(emitter[a]).toBe(emitter[b]) + }) +}) + +describe('#emit', () => { + test('should call all listeners with same arguments', () => { + const emitter = new EventEmitter() + const actual: any[] = [] + emitter.on('test', () => { + actual.push('first') + }) + emitter.on('test', (arg1, arg2) => { + actual.push(`second: ${arg1}, ${arg2}`) + }) + emitter.on('test', (...args) => { + actual.push(`third: ${args.join(', ')}`) + }) + emitter.emit('test', 1, 2, 3, 4, 5) + expect(actual).toEqual([ + 'first', + 'second: 1, 2', + 'third: 1, 2, 3, 4, 5', + ]) + }) +}) + +describe('#eventNames', () => { + test('should return all event names', () => { + const emitter = new EventEmitter() + emitter.on('foo', () => {}) + emitter.on('bar', () => {}) + const sym = Symbol('symbol') + emitter.on(sym, () => {}) + + const actual = emitter.eventNames() + expect(actual).toEqual(['foo', 'bar', sym]) + }) +}) + +describe('#getMaxListeners, #setMaxListeners', () => { + test('should return the max listeners count', () => { + const emitter = new EventEmitter() + expect(emitter.getMaxListeners()).toBe(10) + + emitter.setMaxListeners(5) + expect(emitter.getMaxListeners()).toBe(5) + }) + + test('should throw an error when max listeners exceeded', () => { + expect.hasAssertions() + try { + const emitter = new EventEmitter() + emitter.setMaxListeners(1) + emitter.on('test', () => {}) + emitter.on('test', () => {}) + } catch (err) { + expect(err).toBeInstanceOf(Error) + expect(err.message).toMatch('Max listeners exceeded for event: ') + } + }) + + test('should throw an error when max listeners exceeded', () => { + expect.hasAssertions() + try { + const emitter = new EventEmitter() + emitter.setMaxListeners(1) + emitter.prependListener('test', () => {}) + emitter.prependListener('test', () => {}) + } catch (err) { + expect(err).toBeInstanceOf(Error) + expect(err.message).toMatch('Max listeners exceeded for event: ') + } + }) +}) + +describe('#listenerCount', () => { + test('should return the number of listeners for the eventName', () => { + const emitter = new EventEmitter() + const [listener1, listener2, listener3] = [jest.fn(), jest.fn(), jest.fn()] + emitter.on('test', listener1) + expect(emitter.listenerCount('test')).toEqual(1) + emitter.on('test', listener2) + expect(emitter.listenerCount('test')).toEqual(2) + emitter.on('test', listener3) + expect(emitter.listenerCount('test')).toEqual(3) + }) + + test('should return 0 when there are no listeners', () => { + const emitter = new EventEmitter() + const [listener1, listener2, listener3] = [jest.fn(), jest.fn(), jest.fn()] + emitter.on('test', listener1) + expect(emitter.listenerCount('test', listener1)).toEqual(1) + emitter.on('test', listener2) + expect(emitter.listenerCount('test', listener1)).toEqual(1) + + emitter.on('test', listener1) + expect(emitter.listenerCount('test', listener1)).toEqual(2) + emitter.on('test', listener3) + expect(emitter.listenerCount('test', listener1)).toEqual(2) + + emitter.on('test', listener1) + expect(emitter.listenerCount('test', listener1)).toEqual(3) + }) +}) + +describe('#listeners', () => { + test('should return all listeners for the eventName', () => { + const emitter = new EventEmitter() + const [listener1, listener2, listener3] = [jest.fn(), jest.fn(), jest.fn()] + emitter.on('test1', listener1) + emitter.on('test2', listener2) + emitter.on('test1', listener3) + expect(emitter.listeners('test1')).toEqual([listener1, listener3]) + expect(emitter.listeners('test2')).toEqual([listener2]) + }) +}) + +describe('#on', () => { + test('should trigger the listener 2 times', () => { + const emitter = new EventEmitter() + const actual = jest.fn() + emitter.on('test', actual) + emitter.emit('test') + emitter.emit('test') + expect(actual).toHaveBeenCalledTimes(2) + }) + + test('should return this so that calls can be chained', () => { + const emitter = new EventEmitter() + const actual = emitter.on('test', () => {}) + expect(actual).toBe(emitter) + }) + + test('listeners should be invoked in the order they are added', () => { + const emitter = new EventEmitter() + const actual: string[] = [] + emitter.on('test', () => { actual.push('first') }) + emitter.on('test', () => { actual.push('second') }) + emitter.emit('test') + expect(actual).toEqual(['first', 'second']) + }) + + test('prependListener() should add the listener to the beginning of the listeners', () => { + const emitter = new EventEmitter() + const actual: string[] = [] + emitter.on('test', () => { actual.push('second') }) + emitter.prependListener('test', () => { actual.push('first') }) + emitter.emit('test') + expect(actual).toEqual(['first', 'second']) + }) +}) + +describe('#once', () => { + test('should trigger the listener once', () => { + const emitter = new EventEmitter() + const actual = jest.fn() + emitter.once('test', actual) + emitter.emit('test') + emitter.emit('test') + expect(actual).toHaveBeenCalledTimes(1) + }) + + test('should trigger newListener and removeListener events', () => { + const emitter = new EventEmitter() + const actual: string[] = [] + emitter.on('newListener', () => { actual.push('newListener') }) + emitter.on('removeListener', () => { actual.push('removeListener') }) + emitter.once('test', () => { actual.push('test') }) + emitter.emit('test') + emitter.emit('test') + expect(actual).toEqual(['newListener', 'newListener', 'removeListener', 'test']) + }) +}) + +describe('#prependOnceListener', () => { + test('should trigger the listener once', () => { + const emitter = new EventEmitter() + const actual = jest.fn() + emitter.prependOnceListener('test', actual) + emitter.emit('test') + emitter.emit('test') + expect(actual).toHaveBeenCalledTimes(1) + }) + + test('should trigger newListener and removeListener events', () => { + const emitter = new EventEmitter() + const actual: string[] = [] + emitter.on('newListener', () => { actual.push('newListener') }) + emitter.on('removeListener', () => { actual.push('removeListener') }) + emitter.prependOnceListener('test', () => { actual.push('test') }) + emitter.emit('test') + emitter.emit('test') + expect(actual).toEqual(['newListener', 'newListener', 'removeListener', 'test']) + }) +}) + +describe('#removeAllListeners', () => { + test('should remove all listeners for the eventName', () => { + const emitter = new EventEmitter() + const actual: string[] = [] + emitter.on('test', () => { actual.push('first') }) + emitter.on('test', () => { actual.push('second') }) + emitter.emit('test') + emitter.removeAllListeners('test') + emitter.emit('test') + expect(actual).toEqual(['first', 'second']) + }) + + test('should remove all listeners', () => { + const emitter = new EventEmitter() + const actual: string[] = [] + emitter.on('test1', () => { actual.push('test1') }) + emitter.on('test2', () => { actual.push('test2') }) + emitter.removeAllListeners() + emitter.emit('test1') + emitter.emit('test2') + expect(actual).toEqual([]) + }) +}) + +describe('#removeListener', () => { + test('should remove one listener', () => { + const emitter = new EventEmitter() + const actual = jest.fn() + emitter.on('test', actual) + emitter.on('test', actual) + emitter.emit('test') + emitter.removeListener('test', actual) + emitter.emit('test') + expect(actual).toHaveBeenCalledTimes(3) + }) + + test('cb2 removes cb1 but cb1 should be called', () => { + const emitter = new EventEmitter() + const actual: string[] = [] + const cb1 = (): void => { actual.push('cb1') } + const cb2 = (): void => { + actual.push('cb2') + emitter.removeListener('test', cb1) + } + emitter.on('test', cb2).on('test', cb1) + emitter.emit('test') + expect(actual).toEqual(['cb2', 'cb1']) + emitter.emit('test') + expect(actual).toEqual(['cb2', 'cb1', 'cb2']) + }) + + test('should remove listener added by #once', () => { + const emitter = new EventEmitter() + const actual = jest.fn() + emitter.once('test', actual) + emitter.removeListener('test', actual) + emitter.emit('test') + expect(actual).toHaveBeenCalledTimes(0) + }) + + test('should remove latest listener', () => { + const emitter = new EventEmitter() + const actual: string[] = [] + function pong (): void { actual.push('pong') } + + emitter.on('ping', pong) + emitter.once('ping', pong) + emitter.removeListener('ping', pong) + + emitter.emit('ping') + emitter.emit('ping') + expect(actual).toEqual(['pong', 'pong']) + }) + + test('#removeEventListener', () => { + const emitter = new EventEmitter() + const actual = jest.fn() + emitter.on('test', actual) + emitter.on('test', actual) + emitter.emit('test') + ;(emitter as any).removeEventListener('test', actual) + emitter.emit('test') + expect(actual).toHaveBeenCalledTimes(3) + }) + + test('should no effect if no listeners', () => { + const emitter = new EventEmitter() + emitter.removeListener('test', () => {}) + expect(emitter.listenerCount('test')).toBe(0) + }) +}) + +describe('#rawListeners', () => { + test('should return a copy of the listeners including wrappers', () => { + const emitter = new EventEmitter() + const actual: string[] = [] + emitter.once('test', () => { actual.push('once') }) + + const listeners1 = emitter.rawListeners('test') + const fnWrapper = listeners1[0] + + ;(fnWrapper as any).listener?.() + fnWrapper() + + emitter.on('test', () => { actual.push('on') }) + const listeners2 = emitter.rawListeners('test') + listeners2[0]() + emitter.emit('test') + + expect(actual).toEqual(['once', 'once', 'on', 'on']) + }) +}) + +describe('#dispatchEvent', () => { + test('should emit the event', () => { + const emitter = new EventEmitter() + const actual = jest.fn() + emitter.on('test', actual) + ;(emitter as any).dispatchEvent({ type: 'test' }) + expect(actual).toHaveBeenCalledTimes(1) + }) +}) + +describe('#addEventListener', () => { + test('should add an event listener that can be trigger multiple times', () => { + const emitter = new EventEmitter() + const actual = jest.fn() + ;(emitter as any).addEventListener('test', actual) + emitter.emit('test') + ;(emitter as any).dispatchEvent({ type: 'test' }) + expect(actual).toHaveBeenCalledTimes(2) + }) + + test('should add an event listener that can be trigger once', () => { + const emitter = new EventEmitter() + const actual = jest.fn() + ;(emitter as any).addEventListener('test', actual, { once: true }) + emitter.emit('test') + emitter.emit('test') + expect(actual).toHaveBeenCalledTimes(1) + }) + + test('should add an event listener and ignore capture boolean', () => { + const emitter = new EventEmitter() + const actual = jest.fn() + ;(emitter as any).addEventListener('test', actual, false) + emitter.emit('test') + emitter.emit('test') + expect(actual).toHaveBeenCalledTimes(2) + }) +}) diff --git a/src/EventEmitter.ts b/src/EventEmitter.ts new file mode 100644 index 0000000..fba3813 --- /dev/null +++ b/src/EventEmitter.ts @@ -0,0 +1,154 @@ +import _ from 'lodash' +const captureRejectionSymbol = Symbol.for('nodejs.rejection') + +export class EventEmitter implements NodeJS.EventEmitter, EventTarget { + ;[captureRejectionSymbol]?: (err: Error, eventName: EventName, ...args: [] | any[]) => void + static readonly defaultMaxListeners = 10 + #maxListeners = EventEmitter.defaultMaxListeners + readonly #eventMap: EventMap = new Map() + + emit (eventName: EventName, ...args: [] | any[]): boolean { + const listeners = this.rawListeners(eventName) + if (listeners.length === 0) return false + for (const listener of listeners) { + try { + const promise: any | Promise = listener.apply(null, args) + if (!_.isFunction(promise?.catch)) continue // not promise + promise.catch((err: Error) => { + if (err instanceof Error) this.emit('error', err) + }) + } catch (err) { + this.emit('error', err) + } + } + return true + } + + dispatchEvent (event: DomEvent): boolean { + this.emit(event.type, event) + return true + } + + eventNames (): EventName[] { + return [...this.#eventMap.keys()] + } + + getMaxListeners (): number { + return this.#maxListeners + } + + listenerCount (eventName: EventName, listener?: EventListener): number { + const listeners = this.rawListeners(eventName) + if (listener === undefined) return listeners.length + return _.sumBy(listeners, listener2 => isListenerEqual(listener, listener2) ? 1 : 0) + } + + listeners (eventName: EventName): EventListener[] { + return _.map(this.#eventMap.get(eventName), listener => listener.listener ?? listener) + } + + on (eventName: EventName, listener: EventListener): this { + const listeners = this.#eventMap.get(eventName) ?? [] + if (!this.#eventMap.has(eventName)) this.#eventMap.set(eventName, listeners) + if (this.#maxListeners > 0 && listeners.length >= this.#maxListeners) throw new Error(`Max listeners exceeded for event: ${String(eventName)}`) + + this.emit('newListener', eventName, listener) + listeners.push(listener) + return this + } + + // @ts-expect-error ts(2416) + get addListener (): this['on'] { return this.on } + + addEventListener (eventName: EventName, listener: EventListener, options: boolean | AddEventListenerOptions = {}): this { + if (typeof options === 'boolean') options = { capture: options } + if (options.once === true) return this.once(eventName, listener) + return this.on(eventName, listener) + } + + once (eventName: EventName, listener: T): this { + return this.on(eventName, onceWrapper(this, eventName, listener)) + } + + prependListener (eventName: EventName, listener: EventListener): this { + const listeners = this.#eventMap.get(eventName) ?? [] + if (!this.#eventMap.has(eventName)) this.#eventMap.set(eventName, listeners) + if (this.#maxListeners > 0 && listeners.length >= this.#maxListeners) throw new Error(`Max listeners exceeded for event: ${String(eventName)}`) + + listeners.unshift(listener) + this.emit('newListener', eventName, listener) + return this + } + + prependOnceListener (eventName: EventName, listener: EventListener): this { + return this.prependListener(eventName, onceWrapper(this, eventName, listener)) + } + + removeAllListeners (eventName?: EventName): this { + if (eventName !== undefined) { + this.#eventMap.delete(eventName) + } else { + for (const eventName1 of this.#eventMap.keys()) this.#eventMap.delete(eventName1) + } + return this + } + + removeListener (eventName: EventName, listener: EventListener): this { + const listeners = this.#eventMap.get(eventName) ?? [] + const idx = _.findLastIndex(listeners, listener2 => isListenerEqual(listener, listener2)) + // console.log(`idx = ${idx}, listeners = ${listeners as unknown as string}`) + if (idx >= 0) { + listeners.splice(idx, 1) + this.emit('removeListener', eventName, listener) + } + return this + } + + removeEventListener (eventName: EventName, listener: EventListener, options: EventListenerOptions = {}): void { + this.removeListener(eventName, listener) + } + + // @ts-expect-error ts(2416) + get off (): this['removeListener'] { return this.removeListener } + + setMaxListeners (num: number): this { + this.#maxListeners = num + return this + } + + rawListeners (eventName: EventName): EventListener[] { + return [...(this.#eventMap.get(eventName) ?? [])] + } +} + +type EventListener = EventListenerOrig & { listener?: EventListenerOrig } +type EventListenerOrig = (...args: T) => unknown | Promise +type EventMap = Map +type EventName = string | symbol + +interface DomEvent { + type: string +} + +interface EventListenerOptions { + capture?: boolean +} + +interface AddEventListenerOptions extends EventListenerOptions { + once?: boolean + passive?: boolean + signal?: any +} + +function onceWrapper (emitter: EventEmitter, eventName: EventName, listener: EventListener): EventListener { + const wrapped = (...args: [] | any[]): void => { + emitter.removeListener(eventName, listener) + listener.apply(null, args) + } + wrapped.listener = listener + return wrapped +} + +function isListenerEqual (listener1: EventListener, listener2: EventListener): boolean { + return (listener1.listener ?? listener1) === (listener2.listener ?? listener2) +} diff --git a/src/example/serialport.ts b/src/example/serialport.ts index fbfee89..e31e03c 100644 --- a/src/example/serialport.ts +++ b/src/example/serialport.ts @@ -1,8 +1,10 @@ import { ChameleonUltra } from '../ChameleonUltra' +import ChameleonDebug from '../plugin/Debug' import SerialPortAdapter from '../plugin/SerialPortAdapter' async function main (): Promise { - const ultra = new ChameleonUltra(true) + const ultra = new ChameleonUltra() + await ultra.use(new ChameleonDebug()) await ultra.use(new SerialPortAdapter()) console.log(`version: ${await ultra.cmdGetAppVersion()} (${await ultra.cmdGetGitVersion()})`) diff --git a/src/plugin/Debug.ts b/src/plugin/Debug.ts new file mode 100644 index 0000000..90e256c --- /dev/null +++ b/src/plugin/Debug.ts @@ -0,0 +1,23 @@ +import createDebugger, { type Debugger } from 'debug' +import { type ChameleonPlugin, type PluginInstallContext } from '../ChameleonUltra' + +export default class ChameleonDebug implements ChameleonPlugin { + filter?: ChameleonDebugFilter + debugers = new Map() + name = 'debug' + + async install (context: PluginInstallContext): Promise { + const { ultra } = context + ultra.emitter.on('debug', (namespace: string, formatter: any, ...args: [] | any[]) => { + if (!(this.filter?.(namespace, formatter, ...args) ?? true)) return + const debug = this.debugers.get(namespace) ?? createDebugger(`ultra:${namespace}`) + if (!this.debugers.has(namespace)) this.debugers.set(namespace, debug) + debug(formatter, ...args) + }) + return this + } +} + +;((globalThis as any ?? {}).ChameleonUltraJS ?? {}).ChameleonDebug = ChameleonDebug // eslint-disable-line @typescript-eslint/prefer-optional-chain + +type ChameleonDebugFilter = (namespace: string, formatter: any, ...args: [] | any[]) => boolean diff --git a/src/plugin/SerialPortAdapter.ts b/src/plugin/SerialPortAdapter.ts index 7863292..06ca76c 100644 --- a/src/plugin/SerialPortAdapter.ts +++ b/src/plugin/SerialPortAdapter.ts @@ -1,7 +1,7 @@ import _ from 'lodash' import { Duplex } from 'stream' import { SerialPort } from 'serialport' -import { type ChameleonPlugin, type Logger, type PluginInstallContext } from '../ChameleonUltra' +import { type ChameleonPlugin, type PluginInstallContext, type ChameleonUltra } from '../ChameleonUltra' async function findDevicePath (): Promise { const device = _.find(await SerialPort.list(), { vendorId: '6868', productId: '8686' }) // ChameleonUltra @@ -11,12 +11,15 @@ async function findDevicePath (): Promise { export default class SerialPortAdapter implements ChameleonPlugin { duplex?: SerialPort - logger: Record = {} name = 'adapter' + ultra?: ChameleonUltra + + #debug (formatter: any, ...args: [] | any[]): void { + this.ultra?.emitter.emit('debug', 'serial', formatter, ...args) + } async install (context: AdapterInstallContext, pluginOption: SerialPortOption = {}): Promise { - const { ultra } = context - this.logger.serial = ultra.createDebugger('serial') + const ultra = this.ultra = context.ultra if (!_.isNil(ultra.$adapter)) { await ultra.disconnect(new Error('adapter replaced')) @@ -39,13 +42,13 @@ export default class SerialPortAdapter implements ChameleonPlugin { const tmp = new SerialPort({ baudRate, path }, err => { _.isNil(err) ? resolve(tmp) : reject(err) }) }) this.duplex?.once('close', () => { void ultra.disconnect(new Error('SerialPort closed')) }) - this.logger.serial(`port connected, path = ${path}, baudRate = ${baudRate}`) + this.#debug(`port connected, path = ${path}, baudRate = ${baudRate}`) ultra.port = _.merge(Duplex.toWeb(this.duplex), { isOpen: () => { return this.duplex?.isOpen ?? false }, }) return await next() } catch (err) { - this.logger.serial(err) + this.#debug(err) throw err } }) diff --git a/src/plugin/WebbleAdapter.ts b/src/plugin/WebbleAdapter.ts index 36995a5..8012705 100644 --- a/src/plugin/WebbleAdapter.ts +++ b/src/plugin/WebbleAdapter.ts @@ -3,7 +3,7 @@ import { ReadableStream, type ReadableStreamDefaultController, type UnderlyingSi import { sleep } from '../helper' import { type bluetooth } from 'webbluetooth' import { type Buffer } from '@taichunmin/buffer' -import { type ChameleonPlugin, type Logger, type PluginInstallContext } from '../ChameleonUltra' +import { type ChameleonPlugin, type ChameleonUltra, type PluginInstallContext } from '../ChameleonUltra' const bluetooth1: typeof bluetooth = (globalThis as any)?.navigator?.bluetooth const ReadableStream1: typeof ReadableStream = (globalThis as any)?.ReadableStream ?? ReadableStream @@ -25,18 +25,21 @@ export default class WebbleAdapter implements ChameleonPlugin { Buffer?: typeof Buffer device?: BluetoothDevice isOpen: boolean = false - logger: Record = {} name = 'adapter' recv?: BluetoothRemoteGATTCharacteristic rxSource?: ChameleonWebbleAdapterRxSource send?: BluetoothRemoteGATTCharacteristic serv?: BluetoothRemoteGATTService txSink?: ChameleonWebbleAdapterTxSink + ultra?: ChameleonUltra + + #debug (formatter: any, ...args: [] | any[]): void { + this.ultra?.emitter.emit('debug', 'webble', formatter, ...args) + } async install (context: AdapterInstallContext, pluginOption: any): Promise { const { ultra, Buffer } = context - this.Buffer = Buffer - this.logger.webble = ultra.createDebugger('webble') + ;[this.ultra, this.Buffer] = [ultra, Buffer] if (!_.isNil(ultra.$adapter)) await ultra.disconnect(new Error('adapter replaced')) const adapter: any = {} @@ -57,19 +60,19 @@ export default class WebbleAdapter implements ChameleonPlugin { optionalServices: _.uniq(_.map(BLESERIAL_UUID, 'serv')), }) if (_.isNil(this.device)) throw new Error('no device') - this.logger.webble(`device selected, name = ${this.device.name ?? 'null'}, id = ${this.device.id}`) + this.#debug(`device selected, name = ${this.device.name ?? 'null'}, id = ${this.device.id}`) this.rxSource = new ChameleonWebbleAdapterRxSource(this) this.txSink = new ChameleonWebbleAdapterTxSink(this) for (let i = 0; i < 100; i++) { - this.logger.webble(`gatt connecting, retry = ${i}`) - if (!gattIsConnected()) await this.device.gatt?.connect().catch((err: any) => { this.logger.webble(err.message) }) + this.#debug(`gatt connecting, retry = ${i}`) + if (!gattIsConnected()) await this.device.gatt?.connect().catch((err: any) => { this.#debug(err.message) }) // find serv, send, recv, ctrl // uuid from [bluefy](https://apps.apple.com/app/bluefy-web-ble-browser/id1492822055) is uppercase const primaryServices = _.map(await this.device.gatt?.getPrimaryServices(), serv => _.toLower(serv.uuid)) - this.logger.webble(`primaryServices = ${JSON.stringify(primaryServices)}`) + this.#debug(`primaryServices = ${JSON.stringify(primaryServices)}`) for (const uuids of BLESERIAL_UUID) { try { if (!_.includes(primaryServices, uuids.serv)) continue @@ -85,7 +88,7 @@ export default class WebbleAdapter implements ChameleonPlugin { } if (!_.isNil(this.send) && !_.isNil(this.recv)) { - this.logger.webble(`gatt connected, serv = ${this.serv?.uuid ?? '?'}, recv = ${this.recv?.uuid ?? '?'}, send = ${this.send?.uuid ?? '?'}'`) + this.#debug(`gatt connected, serv = ${this.serv?.uuid ?? '?'}, recv = ${this.recv?.uuid ?? '?'}, send = ${this.send?.uuid ?? '?'}'`) this.isOpen = true break } @@ -103,7 +106,7 @@ export default class WebbleAdapter implements ChameleonPlugin { } return await next() } catch (err) { - this.logger.webble(`Failed to connect: ${err.message as string}`) + this.#debug(`Failed to connect: ${err.message as string}`) throw err } }) @@ -145,13 +148,17 @@ class ChameleonWebbleAdapterRxSource implements UnderlyingSource { #controller?: ReadableStreamDefaultController readonly #adapter: WebbleAdapter + #debug (formatter: any, ...args: [] | any[]): void { + this.#adapter.ultra?.emitter.emit('debug', 'webble', formatter, ...args) + } + constructor (adapter: WebbleAdapter) { this.#adapter = adapter } start (controller: ReadableStreamDefaultController): void { this.#controller = controller } onNotify (event: any): void { const buf = this.#adapter.Buffer?.fromView((event?.target?.value as DataView)) - this.#adapter.logger.webble(`onNotify = ${buf?.toString('hex')}`) + this.#debug(`onNotify = ${buf?.toString('hex')}`) this.#controller?.enqueue(buf) } } @@ -166,6 +173,10 @@ class ChameleonWebbleAdapterTxSink implements UnderlyingSink { this.#Buffer = this.#adapter.Buffer } + #debug (formatter: any, ...args: [] | any[]): void { + this.#adapter.ultra?.emitter.emit('debug', 'webble', formatter, ...args) + } + async write (chunk: Buffer): Promise { if (_.isNil(this.#adapter.send)) throw new Error('this.#adapter.send can not be null') @@ -176,7 +187,7 @@ class ChameleonWebbleAdapterTxSink implements UnderlyingSink { const buf2 = chunk.subarray(i, i + 20) if (_.isNil(buf1) || buf1.length !== buf2.length) buf1 = new this.#Buffer(buf2.length) buf1.set(buf2) - this.#adapter.logger.webble(`bleWrite = ${buf1.toString('hex')}`) + this.#debug(`bleWrite = ${buf1.toString('hex')}`) await this.#adapter.send?.writeValueWithoutResponse(buf1.buffer) } } diff --git a/src/plugin/WebserialAdapter.ts b/src/plugin/WebserialAdapter.ts index cd79818..e6432f7 100644 --- a/src/plugin/WebserialAdapter.ts +++ b/src/plugin/WebserialAdapter.ts @@ -1,22 +1,15 @@ import _ from 'lodash' -import { sleep } from '../helper' -import { type ChameleonPlugin, type Logger, type PluginInstallContext } from '../ChameleonUltra' import { serial, type SerialPort } from 'web-serial-polyfill' +import { sleep } from '../helper' +import { type ChameleonPlugin, type ChameleonUltra, type PluginInstallContext } from '../ChameleonUltra' +import { type EventEmitter } from '../EventEmitter' -type SerialPort1 = SerialPort & { - addEventListener: ( - eventName: string, - listener: (...args: any[]) => void, - opts?: { - once: boolean - } - ) => any -} +type SerialPort1 = SerialPort & EventEmitter const navigator = (globalThis as any)?.navigator ?? {} const serial1: typeof serial = navigator.serial ?? ('usb' in navigator ? serial : null) const WEBSERIAL_FILTERS = [ - { usbVendorId: 0x6868, usbProductId: 0x8686 }, // Chameleon Tiny + { usbVendorId: 0x6868, usbProductId: 0x8686 }, // Chameleon Ultra ] function u16ToHex (num: number): string { @@ -25,13 +18,16 @@ function u16ToHex (num: number): string { export default class WebserialAdapter implements ChameleonPlugin { isOpen: boolean = false - logger: Record = {} name = 'adapter' port?: SerialPort1 + ultra?: ChameleonUltra + + #debug (formatter: any, ...args: [] | any[]): void { + this.ultra?.emitter.emit('debug', 'webserial', formatter, ...args) + } async install (context: AdapterInstallContext, pluginOption: any): Promise { - const { ultra } = context - this.logger.webserial = ultra.createDebugger('webserial') + const ultra = this.ultra = context.ultra if (!_.isNil(ultra.$adapter)) await ultra.disconnect(new Error('adapter replaced')) const adapter: any = {} @@ -52,14 +48,14 @@ export default class WebserialAdapter implements ChameleonPlugin { this.isOpen = true const info = await this.port.getInfo() as { usbVendorId: number, usbProductId: number } - this.logger.webserial(`port selected, usbVendorId = 0x${u16ToHex(info.usbVendorId)}, usbProductId = 0x${u16ToHex(info.usbProductId)}`) + this.#debug(`port selected, usbVendorId = 0x${u16ToHex(info.usbVendorId)}, usbProductId = 0x${u16ToHex(info.usbProductId)}`) this.port.addEventListener('disconnect', () => { void ultra.disconnect(new Error('Webserial disconnect')) }) ultra.port = _.merge(this.port, { isOpen: () => this.isOpen, }) return await next() } catch (err) { - this.logger.webserial(err) + this.#debug(err) throw err } }) diff --git a/tsup.config.ts b/tsup.config.ts index e500192..3aa5cb5 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -39,6 +39,8 @@ export default defineConfig((options): Options[] => [ minify: !options.watch, entry: [ 'src/Crypto1.ts', + 'src/plugin/BufferMockAdapter.ts', + 'src/plugin/Debug.ts', 'src/plugin/SerialPortAdapter.ts', 'src/plugin/WebbleAdapter.ts', 'src/plugin/WebserialAdapter.ts', diff --git a/typedoc.json b/typedoc.json index f8e0a3e..b5a06e4 100644 --- a/typedoc.json +++ b/typedoc.json @@ -1,5 +1,7 @@ { "cleanOutputDir": false, + "customCss": "./typedoc/custom.css", + "darkHighlightTheme": "dark-plus", "exclude": ["**/*.spec.ts", "**/*.test.ts", "**/node_modules/**"], "includeVersion": true, "out": "dist", diff --git a/typedoc/custom.css b/typedoc/custom.css new file mode 100644 index 0000000..7291df9 --- /dev/null +++ b/typedoc/custom.css @@ -0,0 +1,13 @@ +code { + :not(pre)>& { + background-color: #2c3437; + border-radius: 0.4rem; + border: .1rem solid rgba(255, 255, 255, .1); + color: #e9edf0; + font-family: SFMono-Regular, Menlo, Consolas, "Liberation Mono", "Courier New", monospace; + font-size: 90%; + line-height: 1.5rem; + margin: 0; + padding: 1px 4px; + } +} diff --git a/yarn.lock b/yarn.lock index bf10f95..2920cc3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -309,6 +309,13 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + "@esbuild/aix-ppc64@0.19.12": version "0.19.12" resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz#d1bc06aedb6936b3b6d313bf809a5a40387d2b7f" @@ -824,6 +831,11 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + "@jridgewell/resolve-uri@^3.1.0": version "3.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" @@ -839,6 +851,14 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9": version "0.3.19" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811" @@ -1088,10 +1108,10 @@ dependencies: "@sinonjs/commons" "^3.0.0" -"@taichunmin/buffer@^0.13.4": - version "0.13.4" - resolved "https://registry.yarnpkg.com/@taichunmin/buffer/-/buffer-0.13.4.tgz#8c5519a60c10d6eada4c0a3688f6188454cd0bfd" - integrity sha512-uwtPpQf7+S0mV6D4oM39NErrVkLkL0+RcyzKLZEkex3umngMhIQH/tin8eW5jGORwJJzcSeSTx58vMXD/jQRqA== +"@taichunmin/buffer@^0.13.6": + version "0.13.6" + resolved "https://registry.yarnpkg.com/@taichunmin/buffer/-/buffer-0.13.6.tgz#e75a098498adca2c5b51ca85fc53ac5357633f8b" + integrity sha512-3CM5cD1dkWm8l7VFOL0Etz/wQzjHPMTRWQD/QvtSVBBK5uEEvIaQrD/jII+Q+Jy9S6wZZSzadP70vF3Rpuyu0g== dependencies: lodash "^4.17.21" @@ -1100,6 +1120,26 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node-lts/-/node-lts-20.1.3.tgz#8cdf6fa6c593bf5e16e53134ed377627a58a746e" integrity sha512-m3b7EP2U+h5tNSpaBMfcTuHmHn04wrgRPQQrfKt75YIPq6kPs2153/KfPHdqkEWGx5pEBvS6rnvToT+yTtC1iw== +"@tsconfig/node10@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + "@types/babel__core@^7.1.14": version "7.20.2" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.2.tgz#215db4f4a35d710256579784a548907237728756" @@ -1459,12 +1499,17 @@ acorn-jsx@^5.3.2: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== +acorn-walk@^8.1.1: + version "8.3.2" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" + integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== + acorn@^7.1.1: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.11.3, acorn@^8.9.0: +acorn@^8.11.3, acorn@^8.4.1, acorn@^8.9.0: version "8.11.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== @@ -1561,6 +1606,11 @@ are-we-there-yet@^3.0.0: delegates "^1.0.0" readable-stream "^3.6.0" +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -2116,6 +2166,11 @@ create-jest@^29.7.0: jest-util "^29.7.0" prompts "^2.0.1" +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -2238,6 +2293,11 @@ diff-sequences@^29.6.3: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -4151,7 +4211,7 @@ make-dir@^4.0.0: dependencies: semver "^7.5.3" -make-error@1.x: +make-error@1.x, make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== @@ -5550,6 +5610,25 @@ ts-jest@^29.1.4: semver "^7.5.3" yargs-parser "^21.0.1" +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + tsconfig-paths@^3.15.0: version "3.15.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" @@ -5775,6 +5854,11 @@ utility-types@^3.11.0: resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.11.0.tgz#607c40edb4f258915e901ea7995607fdf319424c" integrity sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw== +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + v8-to-istanbul@^9.0.1: version "9.1.0" resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz#1b83ed4e397f58c85c266a570fc2558b5feb9265" @@ -5964,6 +6048,11 @@ yargs@^17.3.1, yargs@^17.6.0, yargs@^17.7.2: y18n "^5.0.5" yargs-parser "^21.1.1" +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"