diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..04ba039 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] + +# Change these settings to your own preference +indent_style = space +indent_size = 2 + +# We recommend you to keep these unchanged +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +node_modules diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..f3d93a6 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,20 @@ +{ + "root": true, + "parserOptions": { + "ecmaVersion": 12, + "sourceType": "module" + }, + "extends": ["eslint:recommended", "plugin:jest/recommended", "prettier"], + "env": { + "es2021": true, + "node": true + }, + "overrides": [ + { + "files": ["tests/**/*"], + "env": { + "jest": true + } + } + ] +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..3cf0098 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,50 @@ +# .gitattributes snippet to force users to use same line endings for project. +# +# Handle line endings automatically for files detected as text +# and leave all files detected as binary untouched. +* text=auto eol=lf + +# +# The above will handle all files NOT found below +# https://help.github.com/articles/dealing-with-line-endings/ +# https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes + + + +# These files are text and should be normalized (Convert crlf => lf) +*.php text +*.css text +*.js text +*.json text +*.htm text +*.html text +*.xml text +*.txt text +*.ini text +*.inc text +*.pl text +*.rb text +*.py text +*.scm text +*.sql text +.htaccess text +*.sh text + +# These files are binary and should be left untouched +# (binary is a macro for -text -diff) +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.mov binary +*.mp4 binary +*.mp3 binary +*.flv binary +*.fla binary +*.swf binary +*.gz binary +*.zip binary +*.7z binary +*.ttf binary +*.pyc binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ad46b30 --- /dev/null +++ b/.gitignore @@ -0,0 +1,61 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..c402386 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "singleQuote": true, + "semi": false, + "printWidth": 150, + "tabWidth": 2, + "arrowParens": "avoid", + "endOfLine": "lf" +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2055039 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Ruslan Kyba + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..bf70559 --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ + + +# @kybarg/ssp + +Node.JS implemenation of Encrypted Smiley ® Secure Protocol (eSSP, SSP) + +**Supported devices:** NV9USB, NV10USB, BV20, BV50, BV100, NV200, NV200 Spectral, SMART Hopper, SMART Payout, NV11, NV22 + +> [!WARNING] +> **NV11** is not fully tested and supported. Maintainer is required. + +## Table of Contents + +1. [Basis usage](#basis-usage) +2. [Methods](#methods) +3. [Commands](docs/commands.md) +4. [Events](docs/events.md) + +## Basis usage + +```js +const sspLib = require('@kybarg/ssp') + +let eSSP = new sspLib({ + id: 0x00, + timeout: 3000, + fixedKey: '0123456701234567', +}) + +eSSP.on('READ_NOTE', result => { + console.log('READ_NOTE', result) +}) + +eSSP.command('GET_SERIAL_NUMBER').then(result => { + console.log('Serial number:', result.info.serial_number) + return +}) +``` + +## Methods + +All methods return Promise + +- `eSSP.open('COM1')` - Connect device +- `eSSP.close()` - Disconnect device +- `eSSP.initEncryption()` - Initializing Diffie-Hellman key exchange and enable encryption +- `eSSP.enable()` - Enable device and start listen events +- `eSSP.disable()` - Disable device and stop listen events +- `eSSP.poll()` - Start/Stop polling the device +- `eSSP.command('COMMAND_NAME')` - Execute command and get answer + + + +## License + +Distributed under the MIT License. See `LICENSE.txt` for more information. + +

(back to top)

diff --git a/docs/commands.md b/docs/commands.md new file mode 100644 index 0000000..370b53e --- /dev/null +++ b/docs/commands.md @@ -0,0 +1,1413 @@ + +[Back to Readme](../README.md) +# Commands +1. [Reset](#RESET) +2. [Set Channel Inhibits](#SET_CHANNEL_INHIBITS) +3. [Display On](#DISPLAY_ON) +4. [Display Off](#DISPLAY_OFF) +5. [Setup Request](#SETUP_REQUEST) +6. [Host Protocol Version](#HOST_PROTOCOL_VERSION) +7. [Poll](#POLL) +8. [Reject Banknote](#REJECT_BANKNOTE) +9. [Disable](#DISABLE) +10. [Enable](#ENABLE) +11. [Get Serial Number](#GET_SERIAL_NUMBER) +12. [Unit Data](#UNIT_DATA) +13. [Channel Value Request](#CHANNEL_VALUE_REQUEST) +14. [Channel Security Data](#CHANNEL_SECURITY_DATA) +15. [Channel Re Teach Data](#CHANNEL_RE_TEACH_DATA) +16. [Sync](#SYNC) +17. [Last Reject Code](#LAST_REJECT_CODE) +18. [Hold](#HOLD) +19. [Get Firmware Version](#GET_FIRMWARE_VERSION) +20. [Get Dataset Version](#GET_DATASET_VERSION) +21. [Get All Levels](#GET_ALL_LEVELS) +22. [Get Bar Code Reader Configuration](#GET_BAR_CODE_READER_CONFIGURATION) +23. [Set Bar Code Configuration](#SET_BAR_CODE_CONFIGURATION) +24. [Get Bar Code Inhibit Status](#GET_BAR_CODE_INHIBIT_STATUS) +25. [Set Bar Code Inhibit Status](#SET_BAR_CODE_INHIBIT_STATUS) +26. [Get Bar Code Data](#GET_BAR_CODE_DATA) +27. [Set Refill Mode](#SET_REFILL_MODE) +28. [Payout Amount](#PAYOUT_AMOUNT) +29. [Set Denomination Level](#SET_DENOMINATION_LEVEL) +30. [Get Denomination Level](#GET_DENOMINATION_LEVEL) +31. [Communication Pass Through](#COMMUNICATION_PASS_THROUGH) +32. [Halt Payout](#HALT_PAYOUT) +33. [Set Denomination Route](#SET_DENOMINATION_ROUTE) +34. [Get Denomination Route](#GET_DENOMINATION_ROUTE) +35. [Float Amount](#FLOAT_AMOUNT) +36. [Get Minimum Payout](#GET_MINIMUM_PAYOUT) +37. [Empty All](#EMPTY_ALL) +38. [Set Coin Mech Inhibits](#SET_COIN_MECH_INHIBITS) +39. [Get Note Positions](#GET_NOTE_POSITIONS) +40. [Payout Note](#PAYOUT_NOTE) +41. [Stack Note](#STACK_NOTE) +42. [Float By Denomination](#FLOAT_BY_DENOMINATION) +43. [Set Value Reporting Type](#SET_VALUE_REPORTING_TYPE) +44. [Payout By Denomination](#PAYOUT_BY_DENOMINATION) +45. [Set Coin Mech Global Inhibit](#SET_COIN_MECH_GLOBAL_INHIBIT) +46. [Set Generator](#SET_GENERATOR) +47. [Set Modulus](#SET_MODULUS) +48. [Request Key Exchange](#REQUEST_KEY_EXCHANGE) +49. [Set Baud Rate](#SET_BAUD_RATE) +50. [Get Build Revision](#GET_BUILD_REVISION) +51. [Set Hopper Options](#SET_HOPPER_OPTIONS) +52. [Get Hopper Options](#GET_HOPPER_OPTIONS) +53. [Smart Empty](#SMART_EMPTY) +54. [Cashbox Payout Operation Data](#CASHBOX_PAYOUT_OPERATION_DATA) +55. [Configure Bezel](#CONFIGURE_BEZEL) +56. [Poll With Ack](#POLL_WITH_ACK) +57. [Event Ack](#EVENT_ACK) +58. [Get Counters](#GET_COUNTERS) +59. [Reset Counters](#RESET_COUNTERS) +60. [Coin Mech Options](#COIN_MECH_OPTIONS) +61. [Disable Payout Device](#DISABLE_PAYOUT_DEVICE) +62. [Enable Payout Device](#ENABLE_PAYOUT_DEVICE) +63. [Set Fixed Encryption Key](#SET_FIXED_ENCRYPTION_KEY) +64. [Reset Fixed Encryption Key](#RESET_FIXED_ENCRYPTION_KEY) +## RESET +| Code dec | Code hex | +| --- | --- | +| 1 | 0x01 | + +### Devices +`NV9USB`, `NV10USB`, `BV20`, `BV50`, `BV100`, `NV200`, `SMART Hopper`, `SMART Payout`, `NV11` + +### Description +Command to instruct the slave to perform a hard reset at any point within its operational status. + +### Example +```javascript +SSP.command('RESET') +``` + +

(back to top)

+ +## SET_CHANNEL_INHIBITS +| Code dec | Code hex | +| --- | --- | +| 2 | 0x02 | + +### Devices +`NV9USB`, `NV10USB`, `BV20`, `BV50`, `BV100`, `NV200`, `NV11` + +### Description +Variable length command, used to control which channels are enabled. The command byte is followed by 2 data bytes, these bytes are combined to create the INHIBIT_REGISTER, each bit represents the state of a channel (LSB= channel 1, 1=enabled, 0=disabled). At power up all channels are inhibited and the validator is disabled. + +### Arguments +| Name | Type | +| --- | --- | +| channels | `number[]` | + +### Example +```javascript +SSP.command('SET_CHANNEL_INHIBITS', {channels:[1,1,1,1,1,0,0,0]}) +``` + +

(back to top)

+ +## DISPLAY_ON +| Code dec | Code hex | +| --- | --- | +| 3 | 0x03 | + +### Devices +`NV9USB`, `NV10USB`, `NV200`, `NV11` + +### Description +Use this command to re-enabled a disabled bezel illumination function (using the Display Off command). The Bezel will only be illuminated when the device is enabled even if this command is sent. + +### Example +```javascript +SSP.command('DISPLAY_ON') +``` + +

(back to top)

+ +## DISPLAY_OFF +| Code dec | Code hex | +| --- | --- | +| 4 | 0x04 | + +### Devices +`NV9USB`, `NV10USB`, `NV200`, `NV11` + +### Description +This command will force the device bezel to not be illuminated even if the device is enabled. + +### Example +```javascript +SSP.command('DISPLAY_OFF') +``` + +

(back to top)

+ +## SETUP_REQUEST +| Code dec | Code hex | +| --- | --- | +| 5 | 0x05 | + +### Devices +`NV9USB`, `NV10USB`, `BV20`, `BV50`, `BV100`, `NV200`, `SMART Hopper`, `NV11` + +### Description +The device responds with an array of data the format of which depends upon the device, the dataset installed and the protocol version set. + +### Example +```javascript +SSP.command('SETUP_REQUEST') +``` + +

(back to top)

+ +## HOST_PROTOCOL_VERSION +| Code dec | Code hex | +| --- | --- | +| 6 | 0x06 | + +### Devices +`NV9USB`, `NV10USB`, `BV20`, `BV50`, `BV100`, `NV200`, `SMART Hopper`, `SMART Payout`, `NV11` + +### Description +Dual byte command, the first byte is the command; the second byte is the version of the protocol that is implemented on the host. So for example, to enable events on BNV to protocol version 6, send 06, 06. The device will respond with OK if the device supports version 6, or FAIL (0xF8) if it does not. + +### Arguments +| Name | Type | +| --- | --- | +| version | `number` | + +### Example +```javascript +SSP.command('HOST_PROTOCOL_VERSION', { version: 6 }) +``` + +

(back to top)

+ +## POLL +| Code dec | Code hex | +| --- | --- | +| 7 | 0x07 | + +### Devices +`NV9USB`, `NV10USB`, `BV20`, `BV50`, `BV100`, `NV200`, `SMART Hopper`, `SMART Payout`, `NV11` + +### Description +The poll command returns the list of events that have occurred within the device since the last poll. The format of the events depends on the protocol version set within the device. Note that more than one event can occur within a poll response so ensure that the full return array is scanned. + +### Example +```javascript +SSP.command('POLL') +``` + +

(back to top)

+ +## REJECT_BANKNOTE +| Code dec | Code hex | +| --- | --- | +| 8 | 0x08 | + +### Devices +`NV9USB`, `NV10USB`, `BV20`, `BV50`, `BV100`, `NV200`, `NV11` + +### Description +A command to reject a note held in escrow in the banknote validator. For devices apart form NV11; if there is no note in escrow to be rejected, the device replies with COMMAND CANNOT BE PROCESSED (0xF5). + +### Example +```javascript +SSP.command('REJECT_BANKNOTE') +``` + +

(back to top)

+ +## DISABLE +| Code dec | Code hex | +| --- | --- | +| 9 | 0x09 | + +### Devices +`NV9USB`, `NV10USB`, `BV20`, `BV50`, `BV100`, `NV200`, `SMART Hopper`, `NV11` + +### Description +The peripheral will switch to its disabled state, it will not execute any more commands or perform any actions until enabled, any poll commands will report disabled. + +### Example +```javascript +SSP.command('DISABLE') +``` + +

(back to top)

+ +## ENABLE +| Code dec | Code hex | +| --- | --- | +| 10 | 0x0a | + +### Devices +`NV9USB`, `NV10USB`, `BV20`, `BV50`, `BV100`, `NV200`, `SMART Hopper`, `NV11` + +### Description +Send this command to enable a disabled device. + +### Example +```javascript +SSP.command('ENABLE') +``` + +

(back to top)

+ +## GET_SERIAL_NUMBER +| Code dec | Code hex | +| --- | --- | +| 12 | 0x0c | + +### Devices +`NV9USB`, `NV10USB`, `BV20`, `BV50`, `BV100`, `NV200`, `SMART Hopper`, `SMART Payout`, `NV11` + +### Description +This command returns a 4-byte big endian array representing the unique factory programmed serial number of the device. + +### Example +```javascript +SSP.command('GET_SERIAL_NUMBER') +``` + +

(back to top)

+ +## UNIT_DATA +| Code dec | Code hex | +| --- | --- | +| 13 | 0x0d | + +### Devices +`NV9USB`, `NV10USB`, `BV20`, `BV50`, `BV100`, `NV200`, `NV11` + +### Description +Returns, Unit type (1 Byte integer), Firmware Version (4 bytes ASCII string), Country Code (3 Bytes ASCII string), Value Multiplier (3 bytes integer), Protocol Version (1 Byte, integer) + +### Example +```javascript +SSP.command('UNIT_DATA') +``` + +

(back to top)

+ +## CHANNEL_VALUE_REQUEST +| Code dec | Code hex | +| --- | --- | +| 14 | 0x0e | + +### Devices +`NV9USB`, `NV10USB`, `BV20`, `BV50`, `BV100`, `NV200`, `NV11` + +### Description +Returns channel value data for a banknote validator. Formatted as: byte 0 - the highest channel used the a value byte representing each of the denomination values. The real value is obtained by multiplying by the value multiplier. If the validator is greater than or equal to protocol version 6 then the channel values response will be given as: Highest Channel, Value Per Channel (0 for expanded values),3 Byte ASCI country code for each channel, 4- byte Full channel Value for each channel. + +### Example +```javascript +SSP.command('CHANNEL_VALUE_REQUEST') +``` + +

(back to top)

+ +## CHANNEL_SECURITY_DATA +| Code dec | Code hex | +| --- | --- | +| 15 | 0x0f | + +### Devices +`NV9USB`, `NV10USB`, `BV20`, `BV50`, `BV100`, `NV200`, `NV11` + +### Description +Command which returns a number of channels byte (the highest channel used) and then 1 to n bytes which give the security of each channel up to the highest one, a zero indicates that the channel is not implemented. (1 = low, 2 = std, 3 = high, 4 = inhibited). + +### Example +```javascript +SSP.command('CHANNEL_SECURITY_DATA') +``` + +

(back to top)

+ +## CHANNEL_RE_TEACH_DATA +| Code dec | Code hex | +| --- | --- | +| 16 | 0x10 | + +### Devices +`NV9USB`, `NV10USB`, `BV20`, `BV50`, `BV100`, `NV200`, `NV11` + +### Description +This is a vestigial command and may be deprecated in future versions. Do not use. If it is supported in a device it will return all zeros. + +### Example +```javascript +SSP.command('CHANNEL_RE_TEACH_DATA') +``` + +

(back to top)

+ +## SYNC +| Code dec | Code hex | +| --- | --- | +| 17 | 0x11 | + +### Devices +`NV9USB`, `NV10USB`, `BV20`, `BV50`, `BV100`, `NV200`, `SMART Hopper`, `SMART Payout`, `NV11` + +### Description +A command to establish communications with a slave device. A Sync command resets the seq bit of the packet so that the slave device expects the next seq bit to be 0. The host then sets its next seq bit to 0 and the seq sequence is synchronised. + +### Example +```javascript +SSP.command('SYNC') +``` + +

(back to top)

+ +## LAST_REJECT_CODE +| Code dec | Code hex | +| --- | --- | +| 23 | 0x17 | + +### Devices +`NV9USB`, `NV10USB`, `BV20`, `BV50`, `BV100`, `NV200`, `NV11` + +### Description +Returns a single byte that indicates the reason for the last banknote reject. The codes are shown in the table below. Specifics of note validation are not shown to protect integrity of manufacturers security. + +### Example +```javascript +SSP.command('LAST_REJECT_CODE') +``` + +

(back to top)

+ +## HOLD +| Code dec | Code hex | +| --- | --- | +| 24 | 0x18 | + +### Devices +`NV9USB`, `NV10USB`, `BV20`, `BV50`, `BV100`, `NV200`, `NV11` + +### Description +This command may be sent to BNV when Note Read has changed from 0 to >0 (valid note seen) if the user does not wish to accept the note with the next command. This command will also reset the 10-second time-out period after which a note held would be rejected automatically, so it should be sent before this time-out if an escrow function is required. If there is no note in escrow to hold, the device will reply with COMMAND CANNOT BE PROCESSED (0xF5) + +### Example +```javascript +SSP.command('HOLD') +``` + +

(back to top)

+ +## GET_FIRMWARE_VERSION +| Code dec | Code hex | +| --- | --- | +| 32 | 0x20 | + +### Devices +`NV9USB`, `NV10USB`, `BV20`, `BV50`, `BV100`, `NV200`, `SMART Hopper`, `NV11` + +### Description +Returns the full firmware version ascii data array for this device. + +### Example +```javascript +SSP.command('GET_FIRMWARE_VERSION') +``` + +

(back to top)

+ +## GET_DATASET_VERSION +| Code dec | Code hex | +| --- | --- | +| 33 | 0x21 | + +### Devices +`NV9USB`, `NV10USB`, `BV20`, `BV50`, `BV100`, `NV200`, `NV11` + +### Description +Returns a string of ascii codes giving the full dataset version of the device. + +### Example +```javascript +SSP.command('GET_DATASET_VERSION') +``` + +

(back to top)

+ +## GET_ALL_LEVELS +| Code dec | Code hex | +| --- | --- | +| 34 | 0x22 | + +### Devices +`SMART Hopper`, `SMART Payout` + +### Description +Use this command to return all the stored levels of denominations in the device (including those at zero level). This gives a faster response than sending each individual denomination level request. + +### Example +```javascript +SSP.command('GET_ALL_LEVELS') +``` + +

(back to top)

+ +## GET_BAR_CODE_READER_CONFIGURATION +| Code dec | Code hex | +| --- | --- | +| 35 | 0x23 | + +### Devices +`NV9USB`, `NV200` + +### Description +Returns the set-up data for the device bar code readers. + +### Example +```javascript +SSP.command('GET_BAR_CODE_READER_CONFIGURATION') +``` + +

(back to top)

+ +## SET_BAR_CODE_CONFIGURATION +| Code dec | Code hex | +| --- | --- | +| 36 | 0x24 | + +### Devices +`NV9USB`, `NV200` + +### Description +This command allows the host to set-up the bar code reader(s) configuration on the device. 3 bytes of data define the configuration. In this example we enable both readers with format interleaved 1 of 5 for 18 characters. + +### Arguments +| Name | Type | +| --- | --- | +| enable | `none\|top\|bottom\|both` | +| numChar | `number // min:6 max:24` | + +### Example +```javascript +SSP.command('SET_BAR_CODE_CONFIGURATION', {enable: 'top', numChar: 6}) +``` + +

(back to top)

+ +## GET_BAR_CODE_INHIBIT_STATUS +| Code dec | Code hex | +| --- | --- | +| 37 | 0x25 | + +### Devices +`NV9USB`, `NV200` + +### Description +Command to return the current bar code/currency inhibit status. + +### Example +```javascript +SSP.command('GET_BAR_CODE_INHIBIT_STATUS') +``` + +

(back to top)

+ +## SET_BAR_CODE_INHIBIT_STATUS +| Code dec | Code hex | +| --- | --- | +| 38 | 0x26 | + +### Devices +`NV9USB`, `NV200` + +### Description +Sets up the bar code inhibit status register. A single data byte representing a bit register is sent. Bit 0 is Currency read enable (0 = enable, 1= disable) Bit 1 is the Bar code enable (0 = enable, 1 = disable). All other bits are not used and set to 1. This example shows a request to a device to have currency enabled, bar code enabled. + +### Arguments +| Name | Type | +| --- | --- | +| currencyRead | `boolean` | +| barCode | `boolean` | + +### Example +```javascript +SSP.command('SET_BAR_CODE_INHIBIT_STATUS',{ currencyRead: true, barCode: true }) +``` + +

(back to top)

+ +## GET_BAR_CODE_DATA +| Code dec | Code hex | +| --- | --- | +| 39 | 0x27 | + +### Devices +`NV9USB`, `NV200` + +### Description +Command to obtain last valid bar code ticket data, send in response to a Bar Code Ticket Validated event. This command will return a variable length data steam, a generic response (OK) followed by a status byte, a bar code data length byte, then a stream of bytes of the ticket data in ASCII. + +### Example +```javascript +SSP.command('GET_BAR_CODE_DATA') +``` + +

(back to top)

+ +## SET_REFILL_MODE +| Code dec | Code hex | +| --- | --- | +| 48 | 0x30 | + +### Devices +`SMART Payout` + +### Description +A command sequence to set or reset the facility for the payout to reject notes that are routed to the payout store but the firmware determines that they are un-suitable for storage. In default mode, they would be rerouted to the stacker. In refill mode they will be rejected from the front of the NV200. + +### Arguments +| Name | Type | +| --- | --- | +| mode | `(on\|of\|get)` | + +### Example +```javascript +SSP.command('SET_REFILL_MODE', { mode: 'on' }) +``` + +

(back to top)

+ +## PAYOUT_AMOUNT +| Code dec | Code hex | +| --- | --- | +| 51 | 0x33 | + +> [!IMPORTANT] +> **Requires encryption** + +### Devices +`SMART Hopper`, `SMART Payout` + +### Description +A command to set the monetary value to be paid by the payout unit. Using protocol version 6, the host also sends a pre-test option byte (TEST_PAYOUT_AMOUT 0x19, PAYOUT_AMOUNT 0x58), which will determine if the command amount is tested or paid out. This is useful for multi-payout systems so that the ability to pay a split down amount can be tested before committing to actual payout. + +### Arguments +| Name | Type | +| --- | --- | +| test | `boolean` | +| amount | `number` | +| country_code | `string` | + +### Example +```javascript +SSP.command('PAYOUT_AMOUNT', { test: true, amount: 500, country_code: 'EUR' }) +``` + +

(back to top)

+ +## SET_DENOMINATION_LEVEL +| Code dec | Code hex | +| --- | --- | +| 52 | 0x34 | + +### Devices +`SMART Hopper` + +### Description +A command to increment the level of coins of a denomination stored in the hopper. The command is formatted with the command byte first, amount of coins to add as a 2-byte little endian, the value of coin as 2-byte little endian and (if using protocol version 6) the country code of the coin as 3 byte ASCII. The level of coins for a denomination can be set to zero by sending a zero level for that value. Note that protocol 6 version commands have been expanded to use a 4-byte coin value. The command data is formatted as byte 0 and byte 1 give the number of coins to add. In protocol version 5, the denomination is then sent as a two byte value. In protocol version greater than 5, the denomination is sent as 4 byte value plus 3 bytes ascii country code. In this example we want to increase the level of .50c coin by 20 using protocol version 5. + +### Arguments +| Name | Type | +| --- | --- | +| value | `number` | +| denomination | `number` | +| country_code | `string` | + +### Example +```javascript +SSP.command('SET_DENOMINATION_LEVEL', { value: 12, denomination: 100, country_code: 'EUR' }) +``` + +

(back to top)

+ +## GET_DENOMINATION_LEVEL +| Code dec | Code hex | +| --- | --- | +| 53 | 0x35 | + +### Devices +`SMART Hopper`, `SMART Payout` + +### Description +This command returns the level of a denomination stored in a payout device as a 2 byte value. In protocol versions greater or equal to 6, the host adds a 3 byte ascii country code to give mulit-currency functionality. Send the requested denomination to find its level. In this case a request to find the amount of 0.10c coins in protocol version 5. + +### Arguments +| Name | Type | +| --- | --- | +| amount | `number` | +| country_code | `string` | + +### Example +```javascript +SSP.command('GET_DENOMINATION_LEVEL', { amount: 500, country_code: 'EUR' }) +``` + +

(back to top)

+ +## COMMUNICATION_PASS_THROUGH +| Code dec | Code hex | +| --- | --- | +| 55 | 0x37 | + +### Devices +`SMART Hopper` + +### Description +Used with SMART Hopper only. This command sets USB pass through mode. SMART Hopper then works only as USB to serial converter to allow direct communication (firmware/dataset update) with devices connected to SMART Hopper UARTS. This command was implemented in firmware versions greater or equal to 6.16. + +### Example +```javascript +SSP.command('COMMUNICATION_PASS_THROUGH') +``` + +

(back to top)

+ +## HALT_PAYOUT +| Code dec | Code hex | +| --- | --- | +| 56 | 0x38 | + +> [!IMPORTANT] +> **Requires encryption** + +### Devices +`SMART Hopper`, `SMART Payout` + +### Description +A command to stop the execution of an existing payout. The device will stop payout at the earliest convenient place and generate a Halted event giving the value paid up to that point. + +### Example +```javascript +SSP.command('HALT_PAYOUT') +``` + +

(back to top)

+ +## SET_DENOMINATION_ROUTE +| Code dec | Code hex | +| --- | --- | +| 59 | 0x3b | + +> [!IMPORTANT] +> **Requires encryption** + +### Devices +`SMART Hopper`, `SMART Payout`, `NV11` + +### Description +This command will configure the denomination to be either routed to the cashbox on detection or stored to be made available for later possible payout. + +### Arguments +| Name | Type | +| --- | --- | +| route | `payout\|cashbox` | +| value | `number` | +| country_code | `string` | +| isHopper | `boolean` | + +### Example +```javascript +SSP.command('SET_DENOMINATION_ROUTE', { route: 'payout', value: 10, country_code: 'EUR', isHopper: false }) +``` + +

(back to top)

+ +## GET_DENOMINATION_ROUTE +| Code dec | Code hex | +| --- | --- | +| 60 | 0x3c | + +> [!IMPORTANT] +> **Requires encryption** + +### Devices +`SMART Hopper`, `SMART Payout`, `NV11` + +### Description +This command allows the host to determine the route of a denomination. Note protocol versions: For protocol versions less than 6 a value only data array is sent. For protocol version greater or equal to 6, a 3 byte country code is also sent to allow multi-currency functionality to the payout. Please note that there exists a difference in the data format between SMART Payout and SMART Hopper for protocol versions less than 6. In these protocol versions the value was determined by a 2 byte array rather than 4 byte array For NV11 devices the host must send the required note value in the same form that the device is set to report by (see Set Value Reporting Type command). + +### Arguments +| Name | Type | +| --- | --- | +| value | `number` | +| country_code | `string` | +| isHopper | `boolean` | + +### Example +```javascript +SSP.command('GET_DENOMINATION_ROUTE', { value: 500, country_code: 'EUR', isHopper: false }) +``` + +

(back to top)

+ +## FLOAT_AMOUNT +| Code dec | Code hex | +| --- | --- | +| 61 | 0x3d | + +> [!IMPORTANT] +> **Requires encryption** + +### Devices +`SMART Hopper`, `SMART Payout` + +### Description +A command to float the hopper unit to leave a requested value of money, with a requested minimum possible payout level. All monies not required to meet float value are routed to cashbox. Using protocol version 6, the host also sends a pre-test option byte (TEST_FLOAT_AMOUT 0x19, FLOAT_AMOUNT 0x58), which will determine if the command amount is tested or floated. This is useful for multi-payout systems so that the ability to pay a split down amount can be tested before committing to actual float. + +### Arguments +| Name | Type | +| --- | --- | +| test | `boolean` | +| min_possible_payout | `number` | +| amount | `number` | +| country_code | `string` | + +### Example +```javascript +SSP.command('FLOAT_AMOUNT', { test: true, min_possible_payout: 50, amount: 10000, country_code: 'EUR' }) +``` + +

(back to top)

+ +## GET_MINIMUM_PAYOUT +| Code dec | Code hex | +| --- | --- | +| 62 | 0x3e | + +### Devices +`SMART Hopper`, `SMART Payout` + +### Description +A command to request the minimum possible payout amount that this device can provide + +### Example +```javascript +SSP.command('GET_MINIMUM_PAYOUT') +``` + +

(back to top)

+ +## EMPTY_ALL +| Code dec | Code hex | +| --- | --- | +| 63 | 0x3f | + +> [!IMPORTANT] +> **Requires encryption** + +### Devices +`SMART Hopper`, `SMART Payout`, `NV11` + +### Description +This command will direct all stored monies to the cash box without reporting any value and reset all the stored counters to zero. See Smart Empty command to record the value emptied. + +### Example +```javascript +SSP.command('EMPTY_ALL') +``` + +

(back to top)

+ +## SET_COIN_MECH_INHIBITS +| Code dec | Code hex | +| --- | --- | +| 64 | 0x40 | + +### Devices +`SMART Hopper` + +### Description +This command is used to enable or disable acceptance of individual coin values from a coin acceptor connected to the hopper. + +### Arguments +| Name | Type | +| --- | --- | +| inhibited | `boolean` | +| amount | `number` | +| country_code | `string` | + +### Example +```javascript +SSP.command('SET_COIN_MECH_INHIBITS', { inhibited: false, amount: 50, country_code: 'EUR' }) +``` + +

(back to top)

+ +## GET_NOTE_POSITIONS +| Code dec | Code hex | +| --- | --- | +| 65 | 0x41 | + +### Devices +`NV11` + +### Description +This command will return the number of notes in the Note Float and the value in each position. The way the value is reported is specified by the Set Reporting Type command. The value can be reported by its value or by the channel number of the bill validator. The first note in the table is the first note that was paid into the Note Float. The Note Float is a LIFO system, so the note that is last in the table is the only one that is available to be paid out or moved into the stacker. + +### Example +```javascript +SSP.command('GET_NOTE_POSITIONS') +``` + +

(back to top)

+ +## PAYOUT_NOTE +| Code dec | Code hex | +| --- | --- | +| 66 | 0x42 | + +### Devices +`NV11` + +### Description +The Note Float will payout the last note that was stored. This is the note that is in the highest position in the table returned by the Get Note Positions Command. If the payout is possible the Note Float will reply with generic response OK. If the payout is not possible the reply will be generic response COMMAND CANNOT BE PROCESSED, followed by an error code shown in the table below + +### Example +```javascript +SSP.command('PAYOUT_NOTE') +``` + +

(back to top)

+ +## STACK_NOTE +| Code dec | Code hex | +| --- | --- | +| 67 | 0x43 | + +### Devices +`NV11` + +### Description +The Note Float will stack the last note that was stored. This is the note that is in the highest position in the table returned by the Get Note Positions Command. If the stack operation is possible the Note Float will reply with generic response OK. If the stack is not possible the reply will be generic response command cannot be processed, followed by an error code as shown in the table. + +### Example +```javascript +SSP.command('STACK_NOTE') +``` + +

(back to top)

+ +## FLOAT_BY_DENOMINATION +| Code dec | Code hex | +| --- | --- | +| 68 | 0x44 | + +> [!IMPORTANT] +> **Requires encryption** + +### Devices +`SMART Hopper`, `SMART Payout` + +### Description +A command to float (leave in device) the requested quantity of individual denominations. The quantities of denominations to leave are sent as a 2 byte little endian array; the money values as 4-byte little endian array and the country code as a 3-byte ASCII array. The host also adds an option byte to the end of the command array (TEST_PAYOUT_AMOUT 0x19 or PAYOUT_AMOUNT 0x58). This will allow a pre-test of the ability to float to the requested levels before actual float executes. + +### Arguments +| Name | Type | +| --- | --- | +| test | `boolean` | +| value | `Object[]` | +| value[].number | `number` | +| value[].denomination | `number` | +| value[].country_code | `string` | + +### Example +```javascript +SSP.command('FLOAT_BY_DENOMINATION', { value: [{ number: 4, denomination: 100, country_code: 'EUR' }, { number: 5, denomination: 10, country_code: 'EUR' }], test: false }) +``` + +

(back to top)

+ +## SET_VALUE_REPORTING_TYPE +| Code dec | Code hex | +| --- | --- | +| 69 | 0x45 | + +### Devices +`NV11` + +### Description +This will set the method of reporting values of notes. There are two options, by a four-byte value of the note or by the channel number of the value from the banknote validator. If the channel number is used then the actual value must be determined using the data from the Validator command Unit Data. The default operation is by 4-byte value. Send 0x00 to set Report by value, 0x01 to set Report By Channel. + +### Arguments +| Name | Type | +| --- | --- | +| reportBy | `value\|channel` | + +### Example +```javascript +SSP.command('SET_VALUE_REPORTING_TYPE', { reportBy: 'channel' }) +``` + +

(back to top)

+ +## PAYOUT_BY_DENOMINATION +| Code dec | Code hex | +| --- | --- | +| 70 | 0x46 | + +> [!IMPORTANT] +> **Requires encryption** + +### Devices +`SMART Hopper`, `SMART Payout` + +### Description +A command to payout the requested quantity of individual denominations. The quantities of denominations to pay are sent as a 2 byte little endian array; the money values as 4-byte little endian array and the country code as a 3-byte ASCII array. The host also adds an option byte to the end of the command array (TEST_PAYOUT_AMOUT 0x19 or PAYOUT_AMOUNT 0x58). This will allow a pre-test of the ability to payout the requested levels before actual payout executes. + +### Arguments +| Name | Type | +| --- | --- | +| test | `boolean` | +| value | `Object[]` | +| value[].number | `number` | +| value[].denomination | `number` | +| value[].country_code | `string` | + +### Example +```javascript +SSP.command('PAYOUT_BY_DENOMINATION', { value: [{ number: 4, denomination: 100, country_code: 'EUR' }, { number: 5, denomination: 10, country_code: 'EUR' }], test: false }) +``` + +

(back to top)

+ +## SET_COIN_MECH_GLOBAL_INHIBIT +| Code dec | Code hex | +| --- | --- | +| 73 | 0x49 | + +### Devices +`SMART Hopper` + +### Description +This command allows the host to enable/disable the attached coin mech in one command rather than by each individual value with previous firmware versions. Send this command and one Mode data byte: Data byte = 0x00 - mech disabled. Date byte = 0x01 - mech enabled. + +### Arguments +| Name | Type | +| --- | --- | +| enable | `boolean` | + +### Example +```javascript +SSP.command('SET_COIN_MECH_GLOBAL_INHIBIT', {enable:true}) +``` + +

(back to top)

+ +## SET_GENERATOR +| Code dec | Code hex | +| --- | --- | +| 74 | 0x4a | + +### Devices +`NV9USB`, `NV10USB`, `BV20`, `BV50`, `BV100`, `NV200`, `SMART Hopper`, `SMART Payout`, `NV11` + +### Description +Eight data bytes are a 64 bit number representing the Generator this must be a 64bit prime number. The slave will reply with OK or PARAMETER_OUT_OF_RANGE if the number is not prime. + +### Arguments +| Name | Type | +| --- | --- | +| key | `number` | + +### Example +```javascript +SSP.command('SET_GENERATOR', { key: 982451653 }) +``` + +

(back to top)

+ +## SET_MODULUS +| Code dec | Code hex | +| --- | --- | +| 75 | 0x4b | + +### Devices +`NV9USB`, `NV10USB`, `BV20`, `BV50`, `BV100`, `NV200`, `SMART Hopper`, `SMART Payout`, `NV11` + +### Description +Eight data bytes are a 64 bit number representing the modulus this must be a 64 bit prime number. The slave will reply with OK or PARAMETER_OUT_OF_RANGE if the number is not prime. + +### Arguments +| Name | Type | +| --- | --- | +| key | `number` | + +### Example +```javascript +SSP.command('SET_GENERATOR', { key: 982451653 }) +``` + +

(back to top)

+ +## REQUEST_KEY_EXCHANGE +| Code dec | Code hex | +| --- | --- | +| 76 | 0x4c | + +### Devices +`NV9USB`, `NV10USB`, `BV20`, `BV50`, `BV100`, `NV200`, `SMART Hopper`, `SMART Payout`, `NV11` + +### Description +The eight data bytes are a 64 bit number representing the Host intermediate key. If the Generator and Modulus have been set the slave will calculate the reply with the generic response and eight data bytes representing the slave intermediate key. The host and slave will then calculate the key. If Generator and Modulus are not set then the slave will reply FAIL. + +### Arguments +| Name | Type | +| --- | --- | +| key | `number` | + +### Example +```javascript +SSP.command('SET_GENERATOR', { key: 982451653 }) +``` + +

(back to top)

+ +## SET_BAUD_RATE +| Code dec | Code hex | +| --- | --- | +| 77 | 0x4d | + +### Devices +`SMART Hopper`, `SMART Payout`, `NV11` + +### Description +This command has two data bytes to allow communication speed to be set on a device. The first byte is the speed to change to (see table below). + +### Arguments +| Name | Type | +| --- | --- | +| baudrate | `9600\|38400\|115200` | +| reset_to_default_on_reset | `boolean` | + +### Example +```javascript +SSP.command('SET_BAUD_RATE', { baudrate: 9600, reset_to_default_on_reset: true }) +``` + +

(back to top)

+ +## GET_BUILD_REVISION +| Code dec | Code hex | +| --- | --- | +| 79 | 0x4f | + +### Devices +`NV200`, `SMART Hopper`, `SMART Payout`, `NV11` + +### Description +A command to return the build revision information of a device. The command returns 3 bytes of information representing the build of the product. Byte 0 is the product type, next two bytes make up the revision number(0-65536). For NV200 and NV9USB, the type byte is 0, for Note Float, byte is 3 and for SMART Payout the byte is 6. + +### Example +```javascript +SSP.command('GET_BUILD_REVISION') +``` + +

(back to top)

+ +## SET_HOPPER_OPTIONS +| Code dec | Code hex | +| --- | --- | +| 80 | 0x50 | + +### Devices +`SMART Hopper` + +### Description +The host can set the following options for the SMART Hopper. These options do not persist in memory and after a reset they will go to their default values. This command is valid only when using protocol version 6 or greater. + +### Arguments +| Name | Type | +| --- | --- | +| payMode | `0\|1` | +| levelCheck | `boolean` | +| motorSpeed | `0\|1` | +| cashBoxPayActive | `boolean` | + +### Example +```javascript +SSP.command('SET_HOPPER_OPTIONS', { payMode: 0, levelCheck: false, motorSpeed: 1, cashBoxPayActive: false }) +``` + +

(back to top)

+ +## GET_HOPPER_OPTIONS +| Code dec | Code hex | +| --- | --- | +| 81 | 0x51 | + +### Devices +`SMART Hopper` + +### Description +This command returns 2 option register bytes described in Set Hopper Options command. + +### Example +```javascript +SSP.command('GET_HOPPER_OPTIONS') +``` + +

(back to top)

+ +## SMART_EMPTY +| Code dec | Code hex | +| --- | --- | +| 82 | 0x52 | + +> [!IMPORTANT] +> **Requires encryption** + +### Devices +`SMART Hopper`, `SMART Payout`, `NV11` + +### Description +Empties payout device of contents, maintaining a count of value emptied. The current total value emptied is given is response to a poll command. All coin counters will be set to 0 after running this command. Use Cashbox Payout Operation Data command to retrieve a breakdown of the denomination routed to the cashbox through this operation. + +### Example +```javascript +SSP.command('SMART_EMPTY') +``` + +

(back to top)

+ +## CASHBOX_PAYOUT_OPERATION_DATA +| Code dec | Code hex | +| --- | --- | +| 83 | 0x53 | + +### Devices +`SMART Hopper`, `SMART Payout`, `NV11` + +### Description +Can be sent at the end of a SMART Empty, float or dispense operation. Returns the amount emptied to cashbox from the payout in the last dispense, float or empty command. The quantity of denominations in the response is sent as a 2 byte little endian array; the note values as 4-byte little endian array and the country code as a 3-byte ASCII array. Each denomination in the dataset will be reported, even if 0 coins of that denomination are emptied. As money is emptied from the device, the value is checked. An additional 4 bytes will be added to the response giving a count of object that could not be validated whilst performing the operation. The response is formatted as follows: byteParameter byte 0The number denominations (n) in this response (max 20) byte 1 to byte 1 + (9*n)The individual denomination level (see description below) byte 1 to byte 1 + (9*n) + 1 to byte 1 to byte 1 + (9*n) + 4 The number of un-validated objects moved. Individual level requests: byte 0 and byte 1 number of coins of this denomination moved to cashbox in operation byte 2 to byte 5 The denomination value byte 6 to byte 8 The ascii denomination country code + +### Example +```javascript +SSP.command('CASHBOX_PAYOUT_OPERATION_DATA') +``` + +

(back to top)

+ +## CONFIGURE_BEZEL +| Code dec | Code hex | +| --- | --- | +| 84 | 0x54 | + +### Devices +`NV200` + +### Description +This command allows the host to configure a supported BNV bezel. If the bezel is not supported the command will return generic response COMMAND NOT KNOWN 0xF2. + +### Arguments +| Name | Type | +| --- | --- | +| RGB | `hex color` | +| volatile | `boolean` | + +### Example +```javascript +SSP.command('CONFIGURE_BEZEL', { RGB: 'FF0000', volatile: false }) +``` + +

(back to top)

+ +## POLL_WITH_ACK +| Code dec | Code hex | +| --- | --- | +| 86 | 0x56 | + +> [!IMPORTANT] +> **Requires encryption** + +### Devices +`NV9USB`, `NV10USB`, `BV20`, `BV50`, `BV100`, `NV200`, `SMART Hopper`, `NV11` + +### Description +A command that behaves in the same way as the Poll command but with this command, the specified events (see table below) will need to be acknowledged by the host using the EVENT ACK command (0x56). The events will repeat until the EVENT ACK command is sent and the BNV will not allow any further note actions until the event has been cleared by the EVENT ACK command. If this command is not supported by the slave device, then generic response 0xF2 will be returned and standard poll command (0x07) will have to be used. + +### Example +```javascript +SSP.command('POLL_WITH_ACK') +``` + +

(back to top)

+ +## EVENT_ACK +| Code dec | Code hex | +| --- | --- | +| 87 | 0x57 | + +> [!IMPORTANT] +> **Requires encryption** + +### Devices +`NV9USB`, `NV10USB`, `BV20`, `BV50`, `BV100`, `NV200`, `SMART Hopper`, `NV11` + +### Description +This command will clear a repeating Poll ACK response and allow further note operations. + +### Example +```javascript +SSP.command('EVENT_ACK') +``` + +

(back to top)

+ +## GET_COUNTERS +| Code dec | Code hex | +| --- | --- | +| 88 | 0x58 | + +### Devices +`NV9USB`, `SMART Payout`, `NV11` + +### Description +A command to return a global note activity counter set for the slave device. The response is formatted as in the table below and the counter values are persistent in memory after a power down- power up cycle. These counters are note set independent and will wrap to zero and begin again if their maximum value is reached. Each counter is made up of 4 bytes of data giving a max value of 4294967295. + +### Example +```javascript +SSP.command('GET_COUNTERS') +``` + +

(back to top)

+ +## RESET_COUNTERS +| Code dec | Code hex | +| --- | --- | +| 89 | 0x59 | + +### Devices +`NV9USB`, `SMART Payout`, `NV11` + +### Description +Resets the note activity counters described in Get Counters command to all zero values. + +### Example +```javascript +SSP.command('RESET_COUNTERS') +``` + +

(back to top)

+ +## COIN_MECH_OPTIONS +| Code dec | Code hex | +| --- | --- | +| 90 | 0x5a | + +### Devices +`SMART Hopper` + +### Description +The host can set the following options for the SMART Hopper. These options do not persist in memory and after a reset they will go to their default values. + +### Arguments +| Name | Type | +| --- | --- | +| ccTalk | `boolean` | + +### Example +```javascript +SSP.command('COIN_MECH_OPTIONS') +``` + +

(back to top)

+ +## DISABLE_PAYOUT_DEVICE +| Code dec | Code hex | +| --- | --- | +| 91 | 0x5b | + +### Devices +`SMART Payout`, `NV11` + +### Description +All accepted notes will be routed to the stacker and payout commands will not be accepted. + +### Example +```javascript +SSP.command('DISABLE_PAYOUT_DEVICE') +``` + +

(back to top)

+ +## ENABLE_PAYOUT_DEVICE +| Code dec | Code hex | +| --- | --- | +| 92 | 0x5c | + +### Devices +`SMART Payout`, `NV11` + +### Description +A command to enable the attached payout device for storing/paying out notes. A successful enable will return OK, If there is a problem the reply will be generic response COMMAND_CANNOT_BE_PROCESSED, followed by an error code. + +### Arguments +| Name | Type | +| --- | --- | +| GIVE_VALUE_ON_STORED | `boolean` | +| NO_HOLD_NOTE_ON_PAYOUT | `boolean` | +| REQUIRE_FULL_STARTUP | `boolean` | +| OPTIMISE_FOR_PAYIN_SPEED | `boolean` | + +### Example +```javascript +SSP.command('ENABLE_PAYOUT_DEVICE', { GIVE_VALUE_ON_STORED: true, NO_HOLD_NOTE_ON_PAYOUT: true }) +``` + +

(back to top)

+ +## SET_FIXED_ENCRYPTION_KEY +| Code dec | Code hex | +| --- | --- | +| 96 | 0x60 | + +> [!IMPORTANT] +> **Requires encryption** + +### Devices +`SMART Hopper`, `SMART Payout`, `NV11` + +### Description +A command to allow the host to change the fixed part of the eSSP key. The eight data bytes are a 64 bit number representing the fixed part of the key. This command must be encrypted. + +### Arguments +| Name | Type | +| --- | --- | +| fixedKey | `string` | + +### Example +```javascript +SSP.command('SET_FIXED_ENCRYPTION_KEY', { fixedKey: '0123456701234567' }) +``` + +

(back to top)

+ +## RESET_FIXED_ENCRYPTION_KEY +| Code dec | Code hex | +| --- | --- | +| 97 | 0x61 | + +### Devices +`SMART Hopper`, `SMART Payout`, `NV11` + +### Description +Resets the fixed encryption key to the device default. The device may have extra security requirements before it will accept this command (e.g. The Hopper must be empty) if these requirements are not met, the device will reply with Command Cannot be Processed. If successful, the device will reply OK, then reset. When it starts up the fixed key will be the default. + +### Example +```javascript +SSP.command('RESET_FIXED_ENCRYPTION_KEY') +``` + +

(back to top)

+ diff --git a/docs/events.md b/docs/events.md new file mode 100644 index 0000000..e4d5a08 --- /dev/null +++ b/docs/events.md @@ -0,0 +1,1204 @@ + +[Back to Readme](../README.md) +# Events +1. [Jam Recovery](#JAM_RECOVERY) +2. [Error During Payout](#ERROR_DURING_PAYOUT) +3. [Smart Emptying](#SMART_EMPTYING) +4. [Smart Emptied](#SMART_EMPTIED) +5. [Channel Disable](#CHANNEL_DISABLE) +6. [Initialising](#INITIALISING) +7. [Coin Mech Error](#COIN_MECH_ERROR) +8. [Emptying](#EMPTYING) +9. [Emptied](#EMPTIED) +10. [Coin Mech Jammed](#COIN_MECH_JAMMED) +11. [Coin Mech Return Pressed](#COIN_MECH_RETURN_PRESSED) +12. [Payout Out Of Service](#PAYOUT_OUT_OF_SERVICE) +13. [Note Float Removed](#NOTE_FLOAT_REMOVED) +14. [Note Float Attached](#NOTE_FLOAT_ATTACHED) +15. [Note Transfered To Stacker](#NOTE_TRANSFERED_TO_STACKER) +16. [Note Paid Into Stacker At Power-up](#NOTE_PAID_INTO_STACKER_AT_POWER-UP) +17. [Note Paid Into Store At Power-up](#NOTE_PAID_INTO_STORE_AT_POWER-UP) +18. [Note Stacking](#NOTE_STACKING) +19. [Note Dispensed At Power-up](#NOTE_DISPENSED_AT_POWER-UP) +20. [Note Held In Bezel](#NOTE_HELD_IN_BEZEL) +21. [Device Full](#DEVICE_FULL) +22. [Bar Code Ticket Acknowledge](#BAR_CODE_TICKET_ACKNOWLEDGE) +23. [Dispensed](#DISPENSED) +24. [Jammed](#JAMMED) +25. [Halted](#HALTED) +26. [Floating](#FLOATING) +27. [Floated](#FLOATED) +28. [Time Out](#TIME_OUT) +29. [Dispensing](#DISPENSING) +30. [Note Stored In Payout](#NOTE_STORED_IN_PAYOUT) +31. [Incomplete Payout](#INCOMPLETE_PAYOUT) +32. [Incomplete Float](#INCOMPLETE_FLOAT) +33. [Cashbox Paid](#CASHBOX_PAID) +34. [Coin Credit](#COIN_CREDIT) +35. [Note Path Open](#NOTE_PATH_OPEN) +36. [Note Cleared From Front](#NOTE_CLEARED_FROM_FRONT) +37. [Note Cleared To Cashbox](#NOTE_CLEARED_TO_CASHBOX) +38. [Cashbox Removed](#CASHBOX_REMOVED) +39. [Cashbox Replaced](#CASHBOX_REPLACED) +40. [Bar Code Ticket Validated](#BAR_CODE_TICKET_VALIDATED) +41. [Fraud Attempt](#FRAUD_ATTEMPT) +42. [Stacker Full](#STACKER_FULL) +43. [Disabled](#DISABLED) +44. [Unsafe Note Jam](#UNSAFE_NOTE_JAM) +45. [Safe Note Jam](#SAFE_NOTE_JAM) +46. [Note Stacked](#NOTE_STACKED) +47. [Note Rejected](#NOTE_REJECTED) +48. [Note Rejecting](#NOTE_REJECTING) +49. [Credit Note](#CREDIT_NOTE) +50. [Read Note](#READ_NOTE) +51. [Ok](#OK) +52. [Slave Reset](#SLAVE_RESET) +53. [Command Not Known](#COMMAND_NOT_KNOWN) +54. [Wrong No Parameters](#WRONG_NO_PARAMETERS) +55. [Parameter Out Of Range](#PARAMETER_OUT_OF_RANGE) +56. [Command Cannot Be Processed](#COMMAND_CANNOT_BE_PROCESSED) +57. [Software Error](#SOFTWARE_ERROR) +58. [Fail](#FAIL) +59. [Key Not Set](#KEY_NOT_SET) + +## JAM_RECOVERY +| Code dec | Code hex | +| --- | --- | +| 176 | 0x76 | + +### Devices +`SMART Payout` + +### Description +The SMART Payout unit is in the process of recovering from a detected jam. This process will typically move five notes to the cash box; this is done to minimise the possibility the unit will go out of service + +### Data +This event has no data associated with it + +

(back to top)

+ +## ERROR_DURING_PAYOUT +| Code dec | Code hex | +| --- | --- | +| 177 | 0x77 | + +### Devices +`SMART Payout` + +### Description +Returned if an error is detected whilst moving a note inside the SMART Payout unit. The cause of error (1 byte) indicates the source of the condition; 0x00 for note not being correctly detected as it is routed to cashbox or for payout, 0x01 if note is jammed in transport. In the case of the incorrect detection, the response to Cashbox Payout Operation Data request would report the note expected to be paid out. + +### Data +#### Protocol version < 7 +| Name | Type | +| --- | --- | +| error | `string` | + +#### Protocol version >= 7 +| Name | Type | +| --- | --- | +| value | `Object[]` | +| value[].value | `number` | +| value[].country_code | `string` | +| error | `string` | + + +

(back to top)

+ +## SMART_EMPTYING +| Code dec | Code hex | +| --- | --- | +| 179 | 0x79 | + +### Devices +`SMART Payout`, `SMART Hopper`, `NV11` + +### Description +The device is in the process of carrying out its Smart Empty command from the host. The value emptied at the poll point is given in the event data. + +### Data +#### Protocol version < 6 +| Name | Type | +| --- | --- | +| value | `number` | + +#### Protocol version >= 6 +| Name | Type | +| --- | --- | +| value | `Object[]` | +| value[].value | `number` | +| value[].country_code | `string` | + + +

(back to top)

+ +## SMART_EMPTIED +| Code dec | Code hex | +| --- | --- | +| 180 | 0x80 | + +### Devices +`SMART Payout`, `SMART Hopper`, `NV11` + +### Description +The device has completed its Smart Empty command. The total amount emptied is given in the event data. + +### Data +#### Protocol version < 6 +| Name | Type | +| --- | --- | +| value | `number` | + +#### Protocol version >= 6 +| Name | Type | +| --- | --- | +| value | `Object[]` | +| value[].value | `number` | +| value[].country_code | `string` | + + +

(back to top)

+ +## CHANNEL_DISABLE +| Code dec | Code hex | +| --- | --- | +| 181 | 0x81 | + +### Devices +`BV20`, `BV50`, `BV100`, `NV9USB`, `NV10USB`, `NV200`, `SMART Payout`, `NV11` + +### Description +The device has had all its note channels inhibited and has become disabled for note insertion. + +### Data +This event has no data associated with it + +

(back to top)

+ +## INITIALISING +| Code dec | Code hex | +| --- | --- | +| 182 | 0x82 | + +### Devices +`BV20`, `BV50`, `BV100`, `NV9USB`, `NV10USB`, `NV200`, `SMART Payout`, `NV11`, `SMART Hopper` + +### Description +This event is given only when using the Poll with ACK command. It is given when the BNV is powered up and setting its sensors and mechanisms to be ready for Note acceptance. When the event response does not contain this event, the BNV is ready to be enabled and used. + +### Data +This event has no data associated with it + +

(back to top)

+ +## COIN_MECH_ERROR +| Code dec | Code hex | +| --- | --- | +| 183 | 0x83 | + +### Devices +`SMART Hopper` + +### Description +The attached coin mechanism has generated an error. Its code is given in the event data. + +### Data +This event has no data associated with it + +

(back to top)

+ +## EMPTYING +| Code dec | Code hex | +| --- | --- | +| 194 | 0x94 | + +### Devices +`SMART Payout`, `SMART Hopper`, `NV11` + +### Description +The device is in the process of emptying its content to the system cashbox in response to an Empty command. + +### Data +This event has no data associated with it + +

(back to top)

+ +## EMPTIED +| Code dec | Code hex | +| --- | --- | +| 195 | 0x95 | + +### Devices +`SMART Payout`, `SMART Hopper`, `NV11` + +### Description +The device has completed its Empty process in response to an Empty command from the host. + +### Data +This event has no data associated with it + +

(back to top)

+ +## COIN_MECH_JAMMED +| Code dec | Code hex | +| --- | --- | +| 196 | 0x96 | + +### Devices +`SMART Hopper` + +### Description +The attached coin mechanism has been detected as having a jam. + +### Data +This event has no data associated with it + +

(back to top)

+ +## COIN_MECH_RETURN_PRESSED +| Code dec | Code hex | +| --- | --- | +| 197 | 0x97 | + +### Devices +`SMART Hopper` + +### Description +The attached coin mechanism has been detected as having is reject or return button pressed. + +### Data +This event has no data associated with it + +

(back to top)

+ +## PAYOUT_OUT_OF_SERVICE +| Code dec | Code hex | +| --- | --- | +| 198 | 0x98 | + +### Devices +`SMART Payout`, `NV11` + +### Description +This event is given if the payout goes out of service during operation. If this event is detected after a poll, the host can send the ENABLE PAYOUT DEVICE command to determine if the payout unit comes back into service. + +### Data +This event has no data associated with it + +

(back to top)

+ +## NOTE_FLOAT_REMOVED +| Code dec | Code hex | +| --- | --- | +| 199 | 0x99 | + +### Devices +`NV11` + +### Description +Reported when a note float unit has been detected as removed from its validator. + +### Data +This event has no data associated with it + +

(back to top)

+ +## NOTE_FLOAT_ATTACHED +| Code dec | Code hex | +| --- | --- | +| 200 | 0x00 | + +### Devices +`NV11` + +### Description +Reported when a note float unit has been detected as removed from its validator. + +### Data +This event has no data associated with it + +

(back to top)

+ +## NOTE_TRANSFERED_TO_STACKER +| Code dec | Code hex | +| --- | --- | +| 201 | 0x01 | + +### Devices +`SMART Payout`, `NV11` + +### Description +Reported when a note has been successfully moved from the payout store into the stacker cashbox. + +### Data +#### Protocol version >= 6 +| Name | Type | +| --- | --- | +| value | `Object[]` | +| value[].value | `number` | +| value[].country_code | `string` | + + +

(back to top)

+ +## NOTE_PAID_INTO_STACKER_AT_POWER-UP +| Code dec | Code hex | +| --- | --- | +| 202 | 0x02 | + +### Devices +`SMART Payout`, `NV11` + +### Description +Reported when a note has been detected as paid into the cashbox stacker as part of the power-up procedure. + +### Data +#### Protocol version >= 8 +| Name | Type | +| --- | --- | +| value | `Object[]` | +| value[].value | `number` | +| value[].country_code | `string` | + + +

(back to top)

+ +## NOTE_PAID_INTO_STORE_AT_POWER-UP +| Code dec | Code hex | +| --- | --- | +| 203 | 0x03 | + +### Devices +`SMART Payout`, `NV11` + +### Description +Reported when a note has been detected as paid into the payout store as part of the power-up procedure. + +### Data +#### Protocol version >= 8 +| Name | Type | +| --- | --- | +| value | `Object[]` | +| value[].value | `number` | +| value[].country_code | `string` | + + +

(back to top)

+ +## NOTE_STACKING +| Code dec | Code hex | +| --- | --- | +| 204 | 0x04 | + +### Devices +`BV20`, `BV50`, `BV100`, `NV9USB`, `NV10USB`, `NV200`, `SMART Payout`, `NV11` + +### Description +The note is being moved from the escrow position to the host exit section of the device. + +### Data +This event has no data associated with it + +

(back to top)

+ +## NOTE_DISPENSED_AT_POWER-UP +| Code dec | Code hex | +| --- | --- | +| 205 | 0x05 | + +### Devices +`NV11` + +### Description +Reported when a note has been dispensed as part of the power-up procedure. + +### Data +#### Protocol version >= 6 +| Name | Type | +| --- | --- | +| value | `Object[]` | +| value[].value | `number` | +| value[].country_code | `string` | + + +

(back to top)

+ +## NOTE_HELD_IN_BEZEL +| Code dec | Code hex | +| --- | --- | +| 206 | 0x06 | + +### Devices +`SMART Payout`, `NV11` + +### Description +Reported when a dispensing note is held in the bezel of the payout device. + +### Data +#### Protocol version >= 8 +| Name | Type | +| --- | --- | +| value | `Object[]` | +| value[].value | `number` | +| value[].country_code | `string` | + + +

(back to top)

+ +## DEVICE_FULL +| Code dec | Code hex | +| --- | --- | +| 207 | 0x07 | + +### Devices +`NV11` + +### Description +This event is reported when the Note Float has reached its limit of stored notes. This event will be reported until a note is paid out or stacked. + +### Data +This event has no data associated with it + +

(back to top)

+ +## BAR_CODE_TICKET_ACKNOWLEDGE +| Code dec | Code hex | +| --- | --- | +| 209 | 0x09 | + +### Devices +`NV200`, `NV201` + +### Description +The bar code ticket has been passed to a safe point in the device stacker. + +### Data +This event has no data associated with it + +

(back to top)

+ +## DISPENSED +| Code dec | Code hex | +| --- | --- | +| 210 | 0x10 | + +### Devices +`SMART Payout`, `SMART Hopper`, `NV11` + +### Description +The device has completed its pay-out request. The final value paid is given in the event data. + +### Data +#### Protocol version < 6 +| Name | Type | +| --- | --- | +| value | `number` | + +#### Protocol version >= 6 +| Name | Type | +| --- | --- | +| value | `Object[]` | +| value[].value | `number` | +| value[].country_code | `string` | + + +

(back to top)

+ +## JAMMED +| Code dec | Code hex | +| --- | --- | +| 213 | 0x13 | + +### Devices +`SMART Payout`, `SMART Hopper`, `NV11` + +### Description +The device has detected that coins are jammed in its mechanism and cannot be removed other than by manual intervention. The value paid at the jam point is given in the event data. + +### Data +#### Protocol version < 6 +| Name | Type | +| --- | --- | +| value | `number` | + +#### Protocol version >= 6 +| Name | Type | +| --- | --- | +| value | `Object[]` | +| value[].value | `number` | +| value[].country_code | `string` | + + +

(back to top)

+ +## HALTED +| Code dec | Code hex | +| --- | --- | +| 214 | 0x14 | + +### Devices +`SMART Payout`, `SMART Hopper`, `NV11` + +### Description +This event is given when the host has requested a halt to the device. The value paid at the point of halting is given in the event data. + +### Data +#### Protocol version < 6 +| Name | Type | +| --- | --- | +| value | `number` | + +#### Protocol version >= 6 +| Name | Type | +| --- | --- | +| value | `Object[]` | +| value[].value | `number` | +| value[].country_code | `string` | + + +

(back to top)

+ +## FLOATING +| Code dec | Code hex | +| --- | --- | +| 215 | 0x15 | + +### Devices +`SMART Payout`, `SMART Hopper` + +### Description +The device is in the process of executing a float command and the value paid to the cashbox at the poll time is given in the event data. + +### Data +#### Protocol version < 6 +| Name | Type | +| --- | --- | +| value | `number` | + +#### Protocol version >= 6 +| Name | Type | +| --- | --- | +| value | `Object[]` | +| value[].value | `number` | +| value[].country_code | `string` | + + +

(back to top)

+ +## FLOATED +| Code dec | Code hex | +| --- | --- | +| 216 | 0x16 | + +### Devices +`SMART Payout`, `SMART Hopper` + +### Description +The device has completed its float command and the final value floated to the cashbox is given in the event data. + +### Data +#### Protocol version < 6 +| Name | Type | +| --- | --- | +| value | `number` | + +#### Protocol version >= 6 +| Name | Type | +| --- | --- | +| value | `Object[]` | +| value[].value | `number` | +| value[].country_code | `string` | + + +

(back to top)

+ +## TIME_OUT +| Code dec | Code hex | +| --- | --- | +| 217 | 0x17 | + +### Devices +`SMART Payout`, `SMART Hopper`, `NV11` + +### Description +The device has been unable to complete a request. The value paid up until the time-out point is given in the event data. + +### Data +#### Protocol version < 6 +| Name | Type | +| --- | --- | +| value | `number` | + +#### Protocol version >= 6 +| Name | Type | +| --- | --- | +| value | `Object[]` | +| value[].value | `number` | +| value[].country_code | `string` | + + +

(back to top)

+ +## DISPENSING +| Code dec | Code hex | +| --- | --- | +| 218 | 0x18 | + +### Devices +`SMART Payout`, `SMART Hopper`, `NV11` + +### Description +The device is in the process of paying out a requested value. The value paid at the poll is given in the vent data. + +### Data +#### Protocol version < 6 +| Name | Type | +| --- | --- | +| value | `number` | + +#### Protocol version >= 6 +| Name | Type | +| --- | --- | +| value | `Object[]` | +| value[].value | `number` | +| value[].country_code | `string` | + + +

(back to top)

+ +## NOTE_STORED_IN_PAYOUT +| Code dec | Code hex | +| --- | --- | +| 219 | 0x19 | + +### Devices +`SMART Payout`, `NV11` + +### Description +The note has been passed into the note store of the payout unit. + +### Data +This event has no data associated with it + +

(back to top)

+ +## INCOMPLETE_PAYOUT +| Code dec | Code hex | +| --- | --- | +| 220 | 0x20 | + +### Devices +`SMART Payout`, `SMART Hopper`, `NV11` + +### Description +The device has detected a discrepancy on power-up that the last payout request was interrupted (possibly due to a power failure). The amounts of the value paid and requested are given in the event data. + +### Data +#### Protocol version < 6 +| Name | Type | +| --- | --- | +| actual | `number` | +| requested | `number` | + +#### Protocol version >= 6 +| Name | Type | +| --- | --- | +| value | `Object[]` | +| value[].actual | `number` | +| value[].requested | `number` | +| value[].country_code | `string` | + + +

(back to top)

+ +## INCOMPLETE_FLOAT +| Code dec | Code hex | +| --- | --- | +| 221 | 0x21 | + +### Devices +`SMART Payout`, `SMART Hopper`, `NV11` + +### Description +The device has detected a discrepancy on power-up that the last float request was interrupted (possibly due to a power failure). The amounts of the value paid and requested are given in the event data. + +### Data +#### Protocol version < 6 +| Name | Type | +| --- | --- | +| actual | `number` | +| requested | `number` | + +#### Protocol version >= 6 +| Name | Type | +| --- | --- | +| value | `Object[]` | +| value[].actual | `number` | +| value[].requested | `number` | +| value[].country_code | `string` | + + +

(back to top)

+ +## CASHBOX_PAID +| Code dec | Code hex | +| --- | --- | +| 222 | 0x22 | + +### Devices +`SMART Hopper` + +### Description +This is given at the end of a payout cycle. It shows the value of stored coins that were routed to the cashbox that were paid into the cashbox during the payout cycle. + +### Data +#### Protocol version < 6 +| Name | Type | +| --- | --- | +| value | `number` | + +#### Protocol version >= 6 +| Name | Type | +| --- | --- | +| value | `Object[]` | +| value[].value | `number` | +| value[].country_code | `string` | + + +

(back to top)

+ +## COIN_CREDIT +| Code dec | Code hex | +| --- | --- | +| 223 | 0x23 | + +### Devices +`SMART Hopper` + +### Description +A coin has been detected as added to the system via the attached coin mechanism. The value of the coin detected is given in the event data. + +### Data +#### Protocol version < 6 +| Name | Type | +| --- | --- | +| value | `number` | + +#### Protocol version >= 6 +| Name | Type | +| --- | --- | +| value | `Object[]` | +| value[].value | `number` | +| value[].country_code | `string` | + + +

(back to top)

+ +## NOTE_PATH_OPEN +| Code dec | Code hex | +| --- | --- | +| 224 | 0x24 | + +### Devices +`NV200` + +### Description +The device has detected that its note transport path has been opened. + +### Data +This event has no data associated with it + +

(back to top)

+ +## NOTE_CLEARED_FROM_FRONT +| Code dec | Code hex | +| --- | --- | +| 225 | 0x25 | + +### Devices +`BV20`, `BV50`, `BV100`, `NV9USB`, `NV10USB`, `NV200`, `SMART Payout`, `NV11` + +### Description +At power-up, a note was detected as being rejected out of the front of the device. The channel value, if known is given in the data byte. + +### Data +#### All +| Name | Type | +| --- | --- | +| channel | `number` | + + +

(back to top)

+ +## NOTE_CLEARED_TO_CASHBOX +| Code dec | Code hex | +| --- | --- | +| 226 | 0x26 | + +### Devices +`BV20`, `BV50`, `BV100`, `NV9USB`, `NV10USB`, `NV200`, `SMART Payout`, `NV11` + +### Description +At power up, a note was detected as being moved into the stacker unit or host exit of the device. The channel number of the note is given in the data byte if known. + +### Data +#### All +| Name | Type | +| --- | --- | +| channel | `number` | + + +

(back to top)

+ +## CASHBOX_REMOVED +| Code dec | Code hex | +| --- | --- | +| 227 | 0x27 | + +### Devices +`BV50`, `BV100`, `NV200`, `SMART Payout`, `NV11` + +### Description +A device with a detectable cashbox has detected that it has been removed. + +### Data +This event has no data associated with it + +

(back to top)

+ +## CASHBOX_REPLACED +| Code dec | Code hex | +| --- | --- | +| 228 | 0x28 | + +### Devices +`BV50`, `BV100`, `NV200`, `SMART Payout`, `NV11` + +### Description +A device with a detectable cashbox has detected that it has been replaced. + +### Data +This event has no data associated with it + +

(back to top)

+ +## BAR_CODE_TICKET_VALIDATED +| Code dec | Code hex | +| --- | --- | +| 229 | 0x29 | + +### Devices +`NV200`, `NV201` + +### Description +A validated barcode ticket has been scanned and is available at the escrow point of the device. + +### Data +This event has no data associated with it + +

(back to top)

+ +## FRAUD_ATTEMPT +| Code dec | Code hex | +| --- | --- | +| 230 | 0x30 | + +### Devices +`BV20`, `BV50`, `BV100`, `NV9USB`, `NV10USB`, `NV200`, `SMART Payout`, `NV11`, `SMART Hopper` + +### Description +The device has detected an attempt to tamper with the normal validation/stacking/payout process. + +### Data +#### Banknote validators +| Name | Type | +| --- | --- | +| channel | `number` | + +#### Protocol version < 6 +| Name | Type | +| --- | --- | +| value | `number` | + +#### Protocol version >= 6 +| Name | Type | +| --- | --- | +| value | `Object[]` | +| value[].value | `number` | +| value[].country_code | `string` | + + +

(back to top)

+ +## STACKER_FULL +| Code dec | Code hex | +| --- | --- | +| 231 | 0x31 | + +### Devices +`BV20`, `BV50`, `BV100`, `NV9USB`, `NV10USB`, `NV200`, `SMART Payout`, `NV11` + +### Description +The banknote stacker unit attached to this device has been detected as at its full limit + +### Data +This event has no data associated with it + +

(back to top)

+ +## DISABLED +| Code dec | Code hex | +| --- | --- | +| 232 | 0x32 | + +### Devices +`All` + +### Description +The device is not active and unavailable for normal validation functions. + +### Data +This event has no data associated with it + +

(back to top)

+ +## UNSAFE_NOTE_JAM +| Code dec | Code hex | +| --- | --- | +| 233 | 0x33 | + +### Devices +`BV20`, `BV50`, `BV100`, `NV9USB`, `NV10USB`, `NV200`, `SMART Payout`, `NV11` + +### Description +The note is stuck in a position where the user could possibly remove it from the front of the device. + +### Data +This event has no data associated with it + +

(back to top)

+ +## SAFE_NOTE_JAM +| Code dec | Code hex | +| --- | --- | +| 234 | 0x34 | + +### Devices +`BV20`, `BV50`, `BV100`, `NV9USB`, `NV10USB`, `NV200`, `SMART Payout`, `NV11` + +### Description +The note is stuck in a position not retrievable from the front of the device (user side) + +### Data +This event has no data associated with it + +

(back to top)

+ +## NOTE_STACKED +| Code dec | Code hex | +| --- | --- | +| 235 | 0x35 | + +### Devices +`BV20`, `BV50`, `BV100`, `NV9USB`, `NV10USB`, `NV200`, `SMART Payout`, `NV11` + +### Description +The note has exited the device on the host side or has been placed within its note stacker. + +### Data +This event has no data associated with it + +

(back to top)

+ +## NOTE_REJECTED +| Code dec | Code hex | +| --- | --- | +| 236 | 0x36 | + +### Devices +`BV20`, `BV50`, `BV100`, `NV9USB`, `NV10USB`, `NV200`, `SMART Payout`, `NV11` + +### Description +The note has been rejected from the validator and is available for the user to retrieve. + +### Data +This event has no data associated with it + +

(back to top)

+ +## NOTE_REJECTING +| Code dec | Code hex | +| --- | --- | +| 237 | 0x37 | + +### Devices +`BV20`, `BV50`, `BV100`, `NV9USB`, `NV10USB`, `NV200`, `SMART Payout`, `NV11` + +### Description +The note is in the process of being rejected from the validator + +### Data +This event has no data associated with it + +

(back to top)

+ +## CREDIT_NOTE +| Code dec | Code hex | +| --- | --- | +| 238 | 0x38 | + +### Devices +`BV20`, `BV50`, `BV100`, `NV9USB`, `NV10USB`, `NV200`, `SMART Payout`, `NV11` + +### Description +A note has passed through the device, past the point of possible recovery and the host can safely issue its credit amount. The byte value is the channel number of the note to credit. + +### Data +#### All +| Name | Type | +| --- | --- | +| channel | `number` | + + +

(back to top)

+ +## READ_NOTE +| Code dec | Code hex | +| --- | --- | +| 239 | 0x39 | + +### Devices +`BV20`, `BV50`, `BV100`, `NV9USB`, `NV10USB`, `NV200`, `SMART Payout`, `NV11` + +### Description +A note is in the process of being scanned by the device (byte value 0) or a valid note has been scanned and is in escrow (byte value gives the channel number) + +### Data +#### All +| Name | Type | +| --- | --- | +| channel | `number` | + + +

(back to top)

+ +## OK +| Code dec | Code hex | +| --- | --- | +| 240 | 0x40 | + +### Description +Returned when a command from the host is understood and has been, or is in the process of, being executed. + +### Data +This event has no data associated with it + +

(back to top)

+ +## SLAVE_RESET +| Code dec | Code hex | +| --- | --- | +| 241 | 0x41 | + +### Devices +`All` + +### Description +The device has undergone a power reset. + +### Data +This event has no data associated with it + +

(back to top)

+ +## COMMAND_NOT_KNOWN +| Code dec | Code hex | +| --- | --- | +| 242 | 0x42 | + +### Description +Returned when an invalid command is received by a peripheral. + +### Data +This event has no data associated with it + +

(back to top)

+ +## WRONG_NO_PARAMETERS +| Code dec | Code hex | +| --- | --- | +| 243 | 0x43 | + +### Description +A command was received by a peripheral, but an incorrect number of parameters were received. + +### Data +This event has no data associated with it + +

(back to top)

+ +## PARAMETER_OUT_OF_RANGE +| Code dec | Code hex | +| --- | --- | +| 244 | 0x44 | + +### Description +One of the parameters sent with a command is out of range. + +### Data +This event has no data associated with it + +

(back to top)

+ +## COMMAND_CANNOT_BE_PROCESSED +| Code dec | Code hex | +| --- | --- | +| 245 | 0x45 | + +### Description +A command sent could not be processed at that time. E.g. sending a dispense command before the last dispense operation has completed. + +### Data +This event has no data associated with it + +

(back to top)

+ +## SOFTWARE_ERROR +| Code dec | Code hex | +| --- | --- | +| 246 | 0x46 | + +### Description +Reported for errors in the execution of software e.g. Divide by zero. This may also be reported if there is a problem resulting from a failed remote firmware upgrade, in this case the firmware upgrade should be redone. + +### Data +This event has no data associated with it + +

(back to top)

+ +## FAIL +| Code dec | Code hex | +| --- | --- | +| 248 | 0x48 | + +### Description +Command failure + +### Data +This event has no data associated with it + +

(back to top)

+ +## KEY_NOT_SET +| Code dec | Code hex | +| --- | --- | +| 250 | 0x50 | + +### Description +The slave is in encrypted communication mode but the encryption keys have not been negotiated. + +### Data +This event has no data associated with it + +

(back to top)

+ diff --git a/example.js b/example.js new file mode 100644 index 0000000..d5f1c1b --- /dev/null +++ b/example.js @@ -0,0 +1,85 @@ +const SSP = require('./lib/index.js') +const channels = [{ value: 0, country_code: 'XXX' }] + +const serialPortConfig = { + baudRate: 9600, // default: 9600 + dataBits: 8, // default: 8 + stopBits: 2, // default: 2 + parity: 'none', // default: 'none' +} + +const eSSP = new SSP({ + id: 0x00, + timeout: 1000, // default: 3000 + encryptAllCommand: true, // default: true + fixedKey: '0123456701234567', // default: '0123456701234567' +}) + +// eSSP.on('DATA_RECEIVED', data => { +// console.log(data) +// }) + +// eSSP.on('DEBUG', data => { +// console.log(data) +// }) + +eSSP.on('OPEN', () => { + console.log('Port opened!') +}) + +eSSP.on('CLOSE', () => { + console.log('Port closed!') +}) + +eSSP.on('READ_NOTE', result => { + console.log('READ_NOTE', result) + console.log(channels[result.channel]) + + if (channels[result.channel].value === 500) { + eSSP.command('REJECT_BANKNOTE') + } +}) + +eSSP.on('NOTE_REJECTED', result => { + console.log('NOTE_REJECTED', result) + + eSSP.command('LAST_REJECT_CODE').then(result => { + console.log(result) + }) +}) + +eSSP + .open('COM9', serialPortConfig) + .then(() => eSSP.command('SYNC')) + .then(() => eSSP.command('HOST_PROTOCOL_VERSION', { version: 6 })) + .then(() => eSSP.initEncryption()) + .then(() => eSSP.command('GET_SERIAL_NUMBER')) + .then(result => { + console.log(eSSP.keys.encryptKey) + console.log('SERIAL NUMBER:', result.info.serial_number) + return + }) + .then(() => eSSP.command('UNIT_DATA')) + .then(() => eSSP.command('SETUP_REQUEST')) + .then(result => { + for (let i = 0; i < result.info.channel_value.length; i++) { + channels[i + 1] = { + value: result.info.expanded_channel_value[i], + country_code: result.info.expanded_channel_country_code[i], + } + } + console.log('channels', channels) + return + }) + .then(() => + eSSP.command('SET_CHANNEL_INHIBITS', { + channels: Array(channels.length).fill(1), + }), + ) + .then(() => eSSP.enable()) + .then(() => { + console.log('GO!!!') + }) + .catch(error => { + console.log(error) + }) diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..2d56db2 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,385 @@ +const { once, EventEmitter } = require('node:events') +const { promisify } = require('node:util') +const { SerialPort } = require('serialport') +const chalk = require('chalk') +const debug = require('debug')('ssp') +const { + argsToByte, + CRC16, + extractPacketData, + generateKeys, + getPacket, + parseData, + createSSPHostEncryptionKey, + validateNodeVersion, +} = require('./utils.js') +const commandList = require('./static/commands.json') +const { SSPParser } = require('./parser/index.js') + +const PORT_OPTIONS = { + baudRate: 9600, + dataBits: 8, + stopBits: 2, + parity: 'none', + highWaterMark: 64 * 1024, +} + +class SSP extends EventEmitter { + constructor(config) { + super() + validateNodeVersion() + + // Initialize event emitter + this.eventEmitter = new EventEmitter() + + // Initialize config + const { fixedKey = '0123456701234567', ...otherConfig } = config + this.config = { + debug: false, + encryptAllCommand: true, + id: 0, + timeout: 1000, + ...otherConfig, + } + + // Initialize keys + this.keys = { + encryptKey: null, + fixedKey, + generator: null, + hostInter: null, + hostRandom: null, + key: null, + modulus: null, + slaveInterKey: null, + } + + // Initialize state + this.state = { + enabled: false, + polling: false, + processing: false, + } + + this.eCount = 0 + this.commandSendAttempts = 0 + this.sequence = 0x80 + + this.protocol_version = null + this.unit_type = null + } + + async open(port, options = {}) { + const serialOptions = { ...PORT_OPTIONS, ...options } + + try { + this.port = new SerialPort({ path: port, ...serialOptions, autoOpen: true }) + + await Promise.race([once(this.port, 'open'), once(this.port, 'close')]) + + this.port.on('open', () => { + this.emit('OPEN') + }) + + this.port.on('close', () => { + this.emit('CLOSE') + }) + + this.port.on('error', error => { + this.emit('ERROR', error) + this.eventEmitter.emit('error', error) + }) + + this.parser = this.port.pipe(new SSPParser()) + this.parser.on('data', buffer => { + this.eventEmitter.emit('DATA', buffer) + }) + + return + } catch (error) { + this.eventEmitter.emit('error', error) + + throw error + } + } + + async close() { + if (this.port !== undefined) { + await promisify(this.port.close)() + } + return + } + + getSequence() { + return this.config.id | this.sequence + } + + /** + * Exchange encryption keys. + * + * @returns {Promise} result - The result of the key exchange. + */ + async initEncryption() { + const newKeys = generateKeys() + + // Reset counter and keys + Object.assign(this.keys, newKeys, { encryptKey: null }) + this.eCount = 0 + + // Define key exchange commands + const commands = [ + { command: 'SET_GENERATOR', args: { key: this.keys.generator } }, + { command: 'SET_MODULUS', args: { key: this.keys.modulus } }, + { command: 'REQUEST_KEY_EXCHANGE', args: { key: this.keys.hostInter } }, + ] + + // Execute key exchange commands sequentially + let result + for (const { command, args } of commands) { + result = await this.command(command, args) + if (!result || !result.success) { + throw result + } + } + + return result + } + + parsePacketData(buffer, command) { + const parsedData = parseData(buffer, command, this.protocol_version, this.unit_type) + + debug(parsedData) + + if (parsedData.success) { + if (command === 'REQUEST_KEY_EXCHANGE') { + try { + const keys = createSSPHostEncryptionKey(Buffer.from(parsedData.info.key), this.keys) + this.keys = { ...this.keys, ...keys } + + debug('AES encrypt key:', chalk.red(`0x${Buffer.from(this.keys.encryptKey).toString('hex')}`)) + debug(this.keys) + } catch (error) { + throw new Error('Key exchange error') + } + } else if (command === 'SETUP_REQUEST') { + this.protocol_version = parsedData.info.protocol_version + this.unit_type = parsedData.info.unit_type + } else if (command === 'UNIT_DATA') { + this.unit_type = parsedData.info.unit_type + } + } else { + if (command === 'HOST_PROTOCOL_VERSION') { + this.protocol_version = undefined + } + } + + return parsedData + } + + /** + * Enable for acepting cash. + * + * @returns {Promise} result - The result of the enable command. + */ + async enable() { + const result = await this.command('ENABLE') + + if (result.status === 'OK') { + this.state.enabled = true + if (!this.state.polling) await this.poll(true) + } + + return result + } + + /** + * Disable for acepting cash. + * + * @returns {Promise} result - The result of the disable command. + */ + async disable() { + if (this.state.polling) await this.poll(false) + + const result = await this.command('DISABLE') + + if (result.status === 'OK') { + this.state.enabled = false + } + + return result + } + + async command(command, args) { + command = command.toUpperCase() + if (commandList[command] === undefined) { + throw new Error('Command not found') + } + + if (this.state.processing) { + throw new Error('Already processing another command') + } + + if (command === 'SYNC') { + this.sequence = 0x80 + } + + this.commandSendAttempts = 0 + + const isEncrypted = this.keys.encryptKey !== null && (commandList[command].encrypted || this.config.encryptAllCommand) + const argBytes = argsToByte(command, args, this.protocol_version) + const sequence = this.getSequence() + const encryptionKey = isEncrypted ? this.keys.encryptKey : null + + const buffer = getPacket(command, argBytes, sequence, encryptionKey, this.eCount) + const bufferPlain = isEncrypted ? getPacket(command, argBytes, sequence, null, this.eCount) : buffer + + const result = await this.sendToDevice(command, buffer, bufferPlain) + + // update sequence after response received + this.sequence = this.sequence === 0x00 ? 0x80 : 0x00 + + if (!result.success) { + throw result + } + + return result + } + + async sendToDevice(command, txBuffer, txBufferPlain) { + // Set processing state + this.state.processing = true + debug('COM <-', chalk.cyan(txBuffer.toString('hex')), chalk.green(command), this.eCount, Date.now()) + + const debugData = { + command, + tx: { + createdAt: Date.now(), + encrypted: txBuffer, + plain: txBufferPlain, + }, + rx: { + createdAt: null, + encrypted: null, + plain: null, + }, + } + + // Define command timeout + const commandTimeout = setTimeout(() => { + this.eventEmitter.emit('error', new Error('TIMEOUT')) + }, this.config.timeout) + + try { + // Send command to device + this.port.write(txBuffer) + this.port.drain() + this.commandSendAttempts += 1 + + // Await data from device + const [rxBuffer] = await once(this.eventEmitter, 'DATA') + debugData.rx.createdAt = Date.now() + debugData.rx.encrypted = rxBuffer + + debug('COM ->', chalk.yellow(rxBuffer.toString('hex')), chalk.green(command), this.eCount, Date.now()) + + // Extract packet data bytes omiting packing data + const DATA = extractPacketData(rxBuffer, this.keys.encryptKey, this.eCount) + debugData.rx.plain = Buffer.from([...rxBuffer.slice(0, 2), DATA.length, ...DATA, ...CRC16([rxBuffer[1], DATA.length, ...DATA])]) + + // Check if sequence flag mismatch + if (txBuffer[1] !== rxBuffer[1]) { + throw new Error('Sequence flag mismatch') + } + + // Increment counter if encrypted command is received + if (this.keys.encryptKey && rxBuffer[3] === 0x7e) { + this.eCount += 1 + } + + // Return parsed packet data + return this.parsePacketData(DATA, command) + } catch (error) { + debugData.rx.createdAt = Date.now() + + console.log(error) + + // Retry sending the same command + // After 20 retries, host assumes that the slave has crashed. + if (this.commandSendAttempts < 20) { + return this.sendToDevice(command, txBuffer, txBufferPlain) + } else { + throw { + success: false, + error: 'Command failed afte 20 retries', + reason: error, + } + } + } finally { + // Unset processing state and clear command fail timeout + this.state.processing = false + clearTimeout(commandTimeout) + + this.emit('DEBUG', debugData) + } + } + + async poll(status = null) { + // If status is true and polling is already in progress, exit early + if (status === true && this.state.polling === true) return + + // Start polling if status is true + if (status === true) { + this.state.polling = true + } + // Stop polling if status is false + else if (status === false) { + this.state.polling = false + clearTimeout(this.pollTimeout) + // Wait until processing is finished + await new Promise(resolve => { + const interval = setInterval(() => { + if (!this.state.processing) { + clearInterval(interval) + resolve() + } + }, 100) + }) + } + + // Poll only if polling is enabled + if (this.state.polling) { + try { + const startTime = Date.now() + const result = await this.command('POLL') + + // Emit events if result contains info + if (result.info) { + const infos = Array.isArray(result.info) ? result.info : [result.info] + infos.forEach(info => this.emit(info.name, info)) + } + + // Calculate execution time and schedule next poll + const endTime = Date.now() + const executionTime = endTime - startTime + this.pollTimeout = setTimeout( + async () => { + try { + await this.poll() + } catch (error) { + this.emit('ERROR', error) + } + }, + Math.max(0, 200 - executionTime), + ) + + return result + } catch (error) { + // Stop polling in case of error + this.state.polling = false + throw error + } + } + } +} + +module.exports = SSP diff --git a/lib/parser/index.js b/lib/parser/index.js new file mode 100644 index 0000000..83b16c3 --- /dev/null +++ b/lib/parser/index.js @@ -0,0 +1,77 @@ +const { Transform } = require('node:stream') + +const SSP_STX = 0x7f + +class SSPParser extends Transform { + constructor(options = {}) { + super(options) + + this.counter = 0 + this.checkStuff = 0 + this.packetLength = 0 + } + + // this is converted from C++ SDK with edits: + // discards falty data + _transform(chunk, encoding, cb) { + for (let ndx = 0; ndx < chunk.length; ndx++) { + const byte = chunk[ndx] + + if (byte == SSP_STX && this.counter == 0) { + // packet start + this.buffer = Buffer.from([byte]) + this.counter++ + } else if (byte == SSP_STX && this.counter == 1) { + // reset if started from stuffed byte + this.reset() + } else { + // if last byte was start byte, and next is not then + // restart the packet + if (this.checkStuff == 1) { + if (byte != SSP_STX) { + this.buffer = Buffer.from([SSP_STX, byte]) + this.counter = 2 + } else { + this.buffer = Buffer.concat([this.buffer, Buffer.from([byte])]) + this.counter++ + } + // reset stuff check flag + this.checkStuff = 0 + } else { + // set flag for stuffed byte check + if (byte == SSP_STX) this.checkStuff = 1 + else { + // add data to packet + this.buffer = Buffer.concat([this.buffer, Buffer.from([byte])]) + this.counter++ + + // get the packet length + if (this.counter === 3) this.packetLength = this.buffer[2] + 5 + } + } + if (this.packetLength === this.buffer.byteLength) { + // reset packet + this.push(this.buffer) + this.reset() + } + } + } + + cb() + } + + reset() { + this.counter = 0 + this.checkStuff = 0 + this.packetLength = 0 + this.buffer = Buffer.alloc(0) + } + + _flush(cb) { + this.push(this.buffer) + this.reset() + cb() + } +} + +module.exports = { SSPParser } diff --git a/lib/parser/index.test.js b/lib/parser/index.test.js new file mode 100644 index 0000000..39116fe --- /dev/null +++ b/lib/parser/index.test.js @@ -0,0 +1,73 @@ +const { SSPParser } = require('./index') + +describe('SSPParser', () => { + let parser + let spy + + beforeEach(() => { + parser = new SSPParser() + spy = jest.fn() + parser.on('data', spy) + }) + + afterEach(() => { + parser.end() + }) + + test('should parse a packet', () => { + parser.write(Buffer.from([127, 0, 1, 240, 32, 10])) + expect(spy).toHaveBeenCalledTimes(1) + expect(spy).toHaveBeenCalledWith(Buffer.from([127, 0, 1, 240, 32, 10])) + }) + + test('should parse a packet with a stuffed byte', () => { + parser.write(Buffer.from([127, 0, 1, 127, 127, 32, 10])) + expect(spy).toHaveBeenCalledTimes(1) + expect(spy).toHaveBeenCalledWith(Buffer.from([127, 0, 1, 127, 32, 10])) + }) + + test('should reset if packet starts with a stuffed byte', () => { + const resetSpy = jest.spyOn(parser, 'reset') + parser.write(Buffer.from([127, 127, 32, 10])) + expect(spy).not.toHaveBeenCalled() + expect(resetSpy).toHaveBeenCalled() + }) + + test('should parse a packet with a stuffed byte at the end', () => { + parser.write(Buffer.from([127, 0, 1, 240, 32, 127, 127])) + expect(spy).toHaveBeenCalledTimes(1) + expect(spy).toHaveBeenCalledWith(Buffer.from([127, 0, 1, 240, 32, 127])) + }) + + test('should restart a packet if last byte was start byte, and next is not', () => { + parser.write(Buffer.from([127, 0, 1, 240, 32, 127, 0, 1, 240, 32, 10])) + expect(spy).toHaveBeenCalledTimes(1) + expect(spy).toHaveBeenCalledWith(Buffer.from([127, 0, 1, 240, 32, 10])) + }) + + test('should parse a packet with a stuffed byte at the end and a new packet', () => { + parser.write(Buffer.from([127, 0, 1, 240, 32, 127, 127, 127, 0, 1, 240, 32, 10])) + expect(spy).toHaveBeenCalledTimes(2) + expect(spy).toHaveBeenCalledWith(Buffer.from([127, 0, 1, 240, 32, 127])) + expect(spy).toHaveBeenCalledWith(Buffer.from([127, 0, 1, 240, 32, 10])) + }) + + test('should parse a packet with a stuffed byte at the end and a new packet with a stuffed byte', () => { + parser.write(Buffer.from([127, 0, 1, 240, 32, 127, 127, 127, 0, 2, 127, 127, 240, 32, 10])) + expect(spy).toHaveBeenCalledTimes(2) + expect(spy).toHaveBeenCalledWith(Buffer.from([127, 0, 1, 240, 32, 127])) + expect(spy).toHaveBeenCalledWith(Buffer.from([127, 0, 2, 127, 240, 32, 10])) + }) + + test('_flush method should push buffer and reset', () => { + const buffer = Buffer.from([127, 0, 1, 127, 127, 32, 10]) + parser.buffer = buffer + + const pushSpy = jest.spyOn(parser, 'push') + const resetSpy = jest.spyOn(parser, 'reset') + + parser.end() + expect(pushSpy).toHaveBeenCalledWith(buffer) + expect(resetSpy).toHaveBeenCalled() + }) +}) diff --git a/lib/static/commands.json b/lib/static/commands.json new file mode 100644 index 0000000..cb09c76 --- /dev/null +++ b/lib/static/commands.json @@ -0,0 +1,557 @@ +{ + "RESET": { + "code": 1, + "encrypted": false, + "args": false, + "device": ["NV9USB", "NV10USB", "BV20", "BV50", "BV100", "NV200", "SMART Hopper", "SMART Payout", "NV11"], + "description": "Command to instruct the slave to perform a hard reset at any point within its operational status." + }, + "SET_CHANNEL_INHIBITS": { + "code": 2, + "encrypted": false, + "args": { + "channels": "number[]" + }, + "device": ["NV9USB", "NV10USB", "BV20", "BV50", "BV100", "NV200", "NV11"], + "description": "Variable length command, used to control which channels are enabled. The command byte is followed by 2 data bytes, these bytes are combined to create the INHIBIT_REGISTER, each bit represents the state of a channel (LSB= channel 1, 1=enabled, 0=disabled). At power up all channels are inhibited and the validator is disabled.", + "example": "SSP.command('SET_CHANNEL_INHIBITS', {channels:[1,1,1,1,1,0,0,0]})" + }, + "DISPLAY_ON": { + "code": 3, + "encrypted": false, + "args": false, + "device": ["NV9USB", "NV10USB", "NV200", "NV11"], + "description": "Use this command to re-enabled a disabled bezel illumination function (using the Display Off command). The Bezel will only be illuminated when the device is enabled even if this command is sent." + }, + "DISPLAY_OFF": { + "code": 4, + "encrypted": false, + "args": false, + "device": ["NV9USB", "NV10USB", "NV200", "NV11"], + "description": "This command will force the device bezel to not be illuminated even if the device is enabled." + }, + "SETUP_REQUEST": { + "code": 5, + "encrypted": false, + "args": false, + "device": ["NV9USB", "NV10USB", "BV20", "BV50", "BV100", "NV200", "SMART Hopper", "NV11"], + "description": "The device responds with an array of data the format of which depends upon the device, the dataset installed and the protocol version set." + }, + "HOST_PROTOCOL_VERSION": { + "code": 6, + "encrypted": false, + "args": { + "version": "number" + }, + "device": ["NV9USB", "NV10USB", "BV20", "BV50", "BV100", "NV200", "SMART Hopper", "SMART Payout", "NV11"], + "description": "Dual byte command, the first byte is the command; the second byte is the version of the protocol that is implemented on the host. So for example, to enable events on BNV to protocol version 6, send 06, 06. The device will respond with OK if the device supports version 6, or FAIL (0xF8) if it does not.", + "example": "SSP.command('HOST_PROTOCOL_VERSION', { version: 6 })" + }, + "POLL": { + "code": 7, + "encrypted": false, + "args": false, + "device": ["NV9USB", "NV10USB", "BV20", "BV50", "BV100", "NV200", "SMART Hopper", "SMART Payout", "NV11"], + "description": "The poll command returns the list of events that have occurred within the device since the last poll. The format of the events depends on the protocol version set within the device. Note that more than one event can occur within a poll response so ensure that the full return array is scanned." + }, + "REJECT_BANKNOTE": { + "code": 8, + "encrypted": false, + "args": false, + "device": ["NV9USB", "NV10USB", "BV20", "BV50", "BV100", "NV200", "NV11"], + "description": "A command to reject a note held in escrow in the banknote validator. For devices apart form NV11; if there is no note in escrow to be rejected, the device replies with COMMAND CANNOT BE PROCESSED (0xF5)." + }, + "DISABLE": { + "code": 9, + "encrypted": false, + "args": false, + "device": ["NV9USB", "NV10USB", "BV20", "BV50", "BV100", "NV200", "SMART Hopper", "NV11"], + "description": "The peripheral will switch to its disabled state, it will not execute any more commands or perform any actions until enabled, any poll commands will report disabled." + }, + "ENABLE": { + "code": 10, + "encrypted": false, + "args": false, + "device": ["NV9USB", "NV10USB", "BV20", "BV50", "BV100", "NV200", "SMART Hopper", "NV11"], + "description": "Send this command to enable a disabled device." + }, + "GET_SERIAL_NUMBER": { + "code": 12, + "encrypted": false, + "args": false, + "device": ["NV9USB", "NV10USB", "BV20", "BV50", "BV100", "NV200", "SMART Hopper", "SMART Payout", "NV11"], + "description": "This command returns a 4-byte big endian array representing the unique factory programmed serial number of the device." + }, + "UNIT_DATA": { + "code": 13, + "encrypted": false, + "args": false, + "device": ["NV9USB", "NV10USB", "BV20", "BV50", "BV100", "NV200", "NV11"], + "description": "Returns, Unit type (1 Byte integer), Firmware Version (4 bytes ASCII string), Country Code (3 Bytes ASCII string), Value Multiplier (3 bytes integer), Protocol Version (1 Byte, integer)" + }, + "CHANNEL_VALUE_REQUEST": { + "code": 14, + "encrypted": false, + "args": false, + "device": ["NV9USB", "NV10USB", "BV20", "BV50", "BV100", "NV200", "NV11"], + "description": "Returns channel value data for a banknote validator. Formatted as: byte 0 - the highest channel used the a value byte representing each of the denomination values. The real value is obtained by multiplying by the value multiplier. If the validator is greater than or equal to protocol version 6 then the channel values response will be given as: Highest Channel, Value Per Channel (0 for expanded values),3 Byte ASCI country code for each channel, 4- byte Full channel Value for each channel." + }, + "CHANNEL_SECURITY_DATA": { + "code": 15, + "encrypted": false, + "args": false, + "device": ["NV9USB", "NV10USB", "BV20", "BV50", "BV100", "NV200", "NV11"], + "description": "Command which returns a number of channels byte (the highest channel used) and then 1 to n bytes which give the security of each channel up to the highest one, a zero indicates that the channel is not implemented. (1 = low, 2 = std, 3 = high, 4 = inhibited)." + }, + "CHANNEL_RE_TEACH_DATA": { + "code": 16, + "encrypted": false, + "args": false, + "device": ["NV9USB", "NV10USB", "BV20", "BV50", "BV100", "NV200", "NV11"], + "description": "This is a vestigial command and may be deprecated in future versions. Do not use. If it is supported in a device it will return all zeros." + }, + "SYNC": { + "code": 17, + "encrypted": false, + "args": false, + "device": ["NV9USB", "NV10USB", "BV20", "BV50", "BV100", "NV200", "SMART Hopper", "SMART Payout", "NV11"], + "description": "A command to establish communications with a slave device. A Sync command resets the seq bit of the packet so that the slave device expects the next seq bit to be 0. The host then sets its next seq bit to 0 and the seq sequence is synchronised." + }, + "LAST_REJECT_CODE": { + "code": 23, + "encrypted": false, + "args": false, + "device": ["NV9USB", "NV10USB", "BV20", "BV50", "BV100", "NV200", "NV11"], + "description": "Returns a single byte that indicates the reason for the last banknote reject. The codes are shown in the table below. Specifics of note validation are not shown to protect integrity of manufacturers security." + }, + "HOLD": { + "code": 24, + "encrypted": false, + "args": false, + "device": ["NV9USB", "NV10USB", "BV20", "BV50", "BV100", "NV200", "NV11"], + "description": "This command may be sent to BNV when Note Read has changed from 0 to >0 (valid note seen) if the user does not wish to accept the note with the next command. This command will also reset the 10-second time-out period after which a note held would be rejected automatically, so it should be sent before this time-out if an escrow function is required. If there is no note in escrow to hold, the device will reply with COMMAND CANNOT BE PROCESSED (0xF5)" + }, + "GET_FIRMWARE_VERSION": { + "code": 32, + "encrypted": false, + "args": false, + "device": ["NV9USB", "NV10USB", "BV20", "BV50", "BV100", "NV200", "SMART Hopper", "NV11"], + "description": "Returns the full firmware version ascii data array for this device." + }, + "GET_DATASET_VERSION": { + "code": 33, + "encrypted": false, + "args": false, + "device": ["NV9USB", "NV10USB", "BV20", "BV50", "BV100", "NV200", "NV11"], + "description": "Returns a string of ascii codes giving the full dataset version of the device." + }, + "GET_ALL_LEVELS": { + "code": 34, + "encrypted": false, + "args": false, + "device": ["SMART Hopper", "SMART Payout"], + "description": "Use this command to return all the stored levels of denominations in the device (including those at zero level). This gives a faster response than sending each individual denomination level request." + }, + "GET_BAR_CODE_READER_CONFIGURATION": { + "code": 35, + "encrypted": false, + "args": false, + "device": ["NV9USB", "NV200"], + "description": "Returns the set-up data for the device bar code readers." + }, + "SET_BAR_CODE_CONFIGURATION": { + "code": 36, + "encrypted": false, + "args": { + "enable": "none|top|bottom|both", + "numChar": "number // min:6 max:24" + }, + "device": ["NV9USB", "NV200"], + "description": "This command allows the host to set-up the bar code reader(s) configuration on the device. 3 bytes of data define the configuration. In this example we enable both readers with format interleaved 1 of 5 for 18 characters.", + "example": "SSP.command('SET_BAR_CODE_CONFIGURATION', {enable: 'top', numChar: 6})" + }, + "GET_BAR_CODE_INHIBIT_STATUS": { + "code": 37, + "encrypted": false, + "args": false, + "device": ["NV9USB", "NV200"], + "description": "Command to return the current bar code/currency inhibit status." + }, + "SET_BAR_CODE_INHIBIT_STATUS": { + "code": 38, + "encrypted": false, + "args": { + "currencyRead": "boolean", + "barCode": "boolean" + }, + "device": ["NV9USB", "NV200"], + "description": "Sets up the bar code inhibit status register. A single data byte representing a bit register is sent. Bit 0 is Currency read enable (0 = enable, 1= disable) Bit 1 is the Bar code enable (0 = enable, 1 = disable). All other bits are not used and set to 1. This example shows a request to a device to have currency enabled, bar code enabled.", + "example": "SSP.command('SET_BAR_CODE_INHIBIT_STATUS',{ currencyRead: true, barCode: true })" + }, + "GET_BAR_CODE_DATA": { + "code": 39, + "encrypted": false, + "args": false, + "device": ["NV9USB", "NV200"], + "description": "Command to obtain last valid bar code ticket data, send in response to a Bar Code Ticket Validated event. This command will return a variable length data steam, a generic response (OK) followed by a status byte, a bar code data length byte, then a stream of bytes of the ticket data in ASCII." + }, + "SET_REFILL_MODE": { + "code": 48, + "encrypted": false, + "args": { + "mode": "(on|of|get)" + }, + "device": ["SMART Payout"], + "description": "A command sequence to set or reset the facility for the payout to reject notes that are routed to the payout store but the firmware determines that they are un-suitable for storage. In default mode, they would be rerouted to the stacker. In refill mode they will be rejected from the front of the NV200.", + "example": "SSP.command('SET_REFILL_MODE', { mode: 'on' })" + }, + "PAYOUT_AMOUNT": { + "code": 51, + "encrypted": true, + "args": { + "test": "boolean", + "amount": "number", + "country_code": "string" + }, + "device": ["SMART Hopper", "SMART Payout"], + "description": "A command to set the monetary value to be paid by the payout unit. Using protocol version 6, the host also sends a pre-test option byte (TEST_PAYOUT_AMOUT 0x19, PAYOUT_AMOUNT 0x58), which will determine if the command amount is tested or paid out. This is useful for multi-payout systems so that the ability to pay a split down amount can be tested before committing to actual payout.", + "example": "SSP.command('PAYOUT_AMOUNT', { test: true, amount: 500, country_code: 'EUR' })" + }, + "SET_DENOMINATION_LEVEL": { + "code": 52, + "encrypted": false, + "args": { + "value": "number", + "denomination": "number", + "country_code": "string" + }, + "device": ["SMART Hopper"], + "description": "A command to increment the level of coins of a denomination stored in the hopper. The command is formatted with the command byte first, amount of coins to add as a 2-byte little endian, the value of coin as 2-byte little endian and (if using protocol version 6) the country code of the coin as 3 byte ASCII. The level of coins for a denomination can be set to zero by sending a zero level for that value. Note that protocol 6 version commands have been expanded to use a 4-byte coin value. The command data is formatted as byte 0 and byte 1 give the number of coins to add. In protocol version 5, the denomination is then sent as a two byte value. In protocol version greater than 5, the denomination is sent as 4 byte value plus 3 bytes ascii country code. In this example we want to increase the level of .50c coin by 20 using protocol version 5.", + "example": "SSP.command('SET_DENOMINATION_LEVEL', { value: 12, denomination: 100, country_code: 'EUR' })" + }, + "GET_DENOMINATION_LEVEL": { + "code": 53, + "encrypted": false, + "args": { + "amount": "number", + "country_code": "string" + }, + "device": ["SMART Hopper", "SMART Payout"], + "description": "This command returns the level of a denomination stored in a payout device as a 2 byte value. In protocol versions greater or equal to 6, the host adds a 3 byte ascii country code to give mulit-currency functionality. Send the requested denomination to find its level. In this case a request to find the amount of 0.10c coins in protocol version 5.", + "example": "SSP.command('GET_DENOMINATION_LEVEL', { amount: 500, country_code: 'EUR' })" + }, + "COMMUNICATION_PASS_THROUGH": { + "code": 55, + "encrypted": false, + "args": false, + "device": ["SMART Hopper"], + "description": "Used with SMART Hopper only. This command sets USB pass through mode. SMART Hopper then works only as USB to serial converter to allow direct communication (firmware/dataset update) with devices connected to SMART Hopper UARTS. This command was implemented in firmware versions greater or equal to 6.16." + }, + "HALT_PAYOUT": { + "code": 56, + "encrypted": true, + "args": false, + "device": ["SMART Hopper", "SMART Payout"], + "description": "A command to stop the execution of an existing payout. The device will stop payout at the earliest convenient place and generate a Halted event giving the value paid up to that point." + }, + "SET_DENOMINATION_ROUTE": { + "code": 59, + "encrypted": true, + "args": { + "route": "payout|cashbox", + "value": "number", + "country_code": "string", + "isHopper": "boolean" + }, + "device": ["SMART Hopper", "SMART Payout", "NV11"], + "description": "This command will configure the denomination to be either routed to the cashbox on detection or stored to be made available for later possible payout.", + "example": "SSP.command('SET_DENOMINATION_ROUTE', { route: 'payout', value: 10, country_code: 'EUR', isHopper: false })" + }, + "GET_DENOMINATION_ROUTE": { + "code": 60, + "encrypted": true, + "args": { + "value": "number", + "country_code": "string", + "isHopper": "boolean" + }, + "device": ["SMART Hopper", "SMART Payout", "NV11"], + "description": "This command allows the host to determine the route of a denomination. Note protocol versions: For protocol versions less than 6 a value only data array is sent. For protocol version greater or equal to 6, a 3 byte country code is also sent to allow multi-currency functionality to the payout. Please note that there exists a difference in the data format between SMART Payout and SMART Hopper for protocol versions less than 6. In these protocol versions the value was determined by a 2 byte array rather than 4 byte array For NV11 devices the host must send the required note value in the same form that the device is set to report by (see Set Value Reporting Type command).", + "example": "SSP.command('GET_DENOMINATION_ROUTE', { value: 500, country_code: 'EUR', isHopper: false })" + }, + "FLOAT_AMOUNT": { + "code": 61, + "encrypted": true, + "args": { + "test": "boolean", + "min_possible_payout": "number", + "amount": "number", + "country_code": "string" + }, + "device": ["SMART Hopper", "SMART Payout"], + "description": "A command to float the hopper unit to leave a requested value of money, with a requested minimum possible payout level. All monies not required to meet float value are routed to cashbox. Using protocol version 6, the host also sends a pre-test option byte (TEST_FLOAT_AMOUT 0x19, FLOAT_AMOUNT 0x58), which will determine if the command amount is tested or floated. This is useful for multi-payout systems so that the ability to pay a split down amount can be tested before committing to actual float.", + "example": "SSP.command('FLOAT_AMOUNT', { test: true, min_possible_payout: 50, amount: 10000, country_code: 'EUR' })" + }, + "GET_MINIMUM_PAYOUT": { + "code": 62, + "encrypted": false, + "args": false, + "device": ["SMART Hopper", "SMART Payout"], + "description": "A command to request the minimum possible payout amount that this device can provide" + }, + "EMPTY_ALL": { + "code": 63, + "encrypted": true, + "args": false, + "device": ["SMART Hopper", "SMART Payout", "NV11"], + "description": "This command will direct all stored monies to the cash box without reporting any value and reset all the stored counters to zero. See Smart Empty command to record the value emptied." + }, + "SET_COIN_MECH_INHIBITS": { + "code": 64, + "encrypted": false, + "args": { + "inhibited": "boolean", + "amount": "number", + "country_code": "string" + }, + "device": ["SMART Hopper"], + "description": "This command is used to enable or disable acceptance of individual coin values from a coin acceptor connected to the hopper.", + "example": "SSP.command('SET_COIN_MECH_INHIBITS', { inhibited: false, amount: 50, country_code: 'EUR' })" + }, + "GET_NOTE_POSITIONS": { + "code": 65, + "encrypted": false, + "args": false, + "device": ["NV11"], + "description": "This command will return the number of notes in the Note Float and the value in each position. The way the value is reported is specified by the Set Reporting Type command. The value can be reported by its value or by the channel number of the bill validator. The first note in the table is the first note that was paid into the Note Float. The Note Float is a LIFO system, so the note that is last in the table is the only one that is available to be paid out or moved into the stacker." + }, + "PAYOUT_NOTE": { + "code": 66, + "encrypted": false, + "args": false, + "device": ["NV11"], + "description": "The Note Float will payout the last note that was stored. This is the note that is in the highest position in the table returned by the Get Note Positions Command. If the payout is possible the Note Float will reply with generic response OK. If the payout is not possible the reply will be generic response COMMAND CANNOT BE PROCESSED, followed by an error code shown in the table below" + }, + "STACK_NOTE": { + "code": 67, + "encrypted": false, + "args": false, + "device": ["NV11"], + "description": "The Note Float will stack the last note that was stored. This is the note that is in the highest position in the table returned by the Get Note Positions Command. If the stack operation is possible the Note Float will reply with generic response OK. If the stack is not possible the reply will be generic response command cannot be processed, followed by an error code as shown in the table." + }, + "FLOAT_BY_DENOMINATION": { + "code": 68, + "encrypted": true, + "args": { + "test": "boolean", + "value": "Object[]", + "value[].number": "number", + "value[].denomination": "number", + "value[].country_code": "string" + }, + "device": ["SMART Hopper", "SMART Payout"], + "description": "A command to float (leave in device) the requested quantity of individual denominations. The quantities of denominations to leave are sent as a 2 byte little endian array; the money values as 4-byte little endian array and the country code as a 3-byte ASCII array. The host also adds an option byte to the end of the command array (TEST_PAYOUT_AMOUT 0x19 or PAYOUT_AMOUNT 0x58). This will allow a pre-test of the ability to float to the requested levels before actual float executes.", + "example": "SSP.command('FLOAT_BY_DENOMINATION', { value: [{ number: 4, denomination: 100, country_code: 'EUR' }, { number: 5, denomination: 10, country_code: 'EUR' }], test: false })" + }, + "SET_VALUE_REPORTING_TYPE": { + "code": 69, + "encrypted": false, + "args": { + "reportBy": "value|channel" + }, + "device": ["NV11"], + "description": "This will set the method of reporting values of notes. There are two options, by a four-byte value of the note or by the channel number of the value from the banknote validator. If the channel number is used then the actual value must be determined using the data from the Validator command Unit Data. The default operation is by 4-byte value. Send 0x00 to set Report by value, 0x01 to set Report By Channel.", + "example": "SSP.command('SET_VALUE_REPORTING_TYPE', { reportBy: 'channel' })" + }, + "PAYOUT_BY_DENOMINATION": { + "code": 70, + "encrypted": true, + "args": { + "test": "boolean", + "value": "Object[]", + "value[].number": "number", + "value[].denomination": "number", + "value[].country_code": "string" + }, + "device": ["SMART Hopper", "SMART Payout"], + "description": "A command to payout the requested quantity of individual denominations. The quantities of denominations to pay are sent as a 2 byte little endian array; the money values as 4-byte little endian array and the country code as a 3-byte ASCII array. The host also adds an option byte to the end of the command array (TEST_PAYOUT_AMOUT 0x19 or PAYOUT_AMOUNT 0x58). This will allow a pre-test of the ability to payout the requested levels before actual payout executes.", + "example": "SSP.command('PAYOUT_BY_DENOMINATION', { value: [{ number: 4, denomination: 100, country_code: 'EUR' }, { number: 5, denomination: 10, country_code: 'EUR' }], test: false })" + }, + "SET_COIN_MECH_GLOBAL_INHIBIT": { + "code": 73, + "encrypted": false, + "args": { + "enable": "boolean" + }, + "device": ["SMART Hopper"], + "description": "This command allows the host to enable/disable the attached coin mech in one command rather than by each individual value with previous firmware versions. Send this command and one Mode data byte: Data byte = 0x00 - mech disabled. Date byte = 0x01 - mech enabled.", + "example": "SSP.command('SET_COIN_MECH_GLOBAL_INHIBIT', {enable:true})" + }, + "SET_GENERATOR": { + "code": 74, + "encrypted": false, + "args": { + "key": "number" + }, + "device": ["NV9USB", "NV10USB", "BV20", "BV50", "BV100", "NV200", "SMART Hopper", "SMART Payout", "NV11"], + "description": "Eight data bytes are a 64 bit number representing the Generator this must be a 64bit prime number. The slave will reply with OK or PARAMETER_OUT_OF_RANGE if the number is not prime.", + "example": "SSP.command('SET_GENERATOR', { key: 982451653 })" + }, + "SET_MODULUS": { + "code": 75, + "encrypted": false, + "args": { + "key": "number" + }, + "device": ["NV9USB", "NV10USB", "BV20", "BV50", "BV100", "NV200", "SMART Hopper", "SMART Payout", "NV11"], + "description": "Eight data bytes are a 64 bit number representing the modulus this must be a 64 bit prime number. The slave will reply with OK or PARAMETER_OUT_OF_RANGE if the number is not prime.", + "example": "SSP.command('SET_GENERATOR', { key: 982451653 })" + }, + "REQUEST_KEY_EXCHANGE": { + "code": 76, + "encrypted": false, + "args": { + "key": "number" + }, + "device": ["NV9USB", "NV10USB", "BV20", "BV50", "BV100", "NV200", "SMART Hopper", "SMART Payout", "NV11"], + "description": "The eight data bytes are a 64 bit number representing the Host intermediate key. If the Generator and Modulus have been set the slave will calculate the reply with the generic response and eight data bytes representing the slave intermediate key. The host and slave will then calculate the key. If Generator and Modulus are not set then the slave will reply FAIL.", + "example": "SSP.command('SET_GENERATOR', { key: 982451653 })" + }, + "SET_BAUD_RATE": { + "code": 77, + "encrypted": false, + "args": { + "baudrate": "9600|38400|115200", + "reset_to_default_on_reset": "boolean" + }, + "device": ["SMART Hopper", "SMART Payout", "NV11"], + "description": "This command has two data bytes to allow communication speed to be set on a device. The first byte is the speed to change to (see table below).", + "example": "SSP.command('SET_BAUD_RATE', { baudrate: 9600, reset_to_default_on_reset: true })" + }, + "GET_BUILD_REVISION": { + "code": 79, + "encrypted": false, + "args": false, + "device": ["NV200", "SMART Hopper", "SMART Payout", "NV11"], + "description": "A command to return the build revision information of a device. The command returns 3 bytes of information representing the build of the product. Byte 0 is the product type, next two bytes make up the revision number(0-65536). For NV200 and NV9USB, the type byte is 0, for Note Float, byte is 3 and for SMART Payout the byte is 6." + }, + "SET_HOPPER_OPTIONS": { + "code": 80, + "encrypted": false, + "args": { + "payMode": "0|1", + "levelCheck": "boolean", + "motorSpeed": "0|1", + "cashBoxPayActive": "boolean" + }, + "device": ["SMART Hopper"], + "description": "The host can set the following options for the SMART Hopper. These options do not persist in memory and after a reset they will go to their default values. This command is valid only when using protocol version 6 or greater.", + "example": "SSP.command('SET_HOPPER_OPTIONS', { payMode: 0, levelCheck: false, motorSpeed: 1, cashBoxPayActive: false })" + }, + "GET_HOPPER_OPTIONS": { + "code": 81, + "encrypted": false, + "args": false, + "device": ["SMART Hopper"], + "description": "This command returns 2 option register bytes described in Set Hopper Options command." + }, + "SMART_EMPTY": { + "code": 82, + "encrypted": true, + "args": false, + "device": ["SMART Hopper", "SMART Payout", "NV11"], + "description": "Empties payout device of contents, maintaining a count of value emptied. The current total value emptied is given is response to a poll command. All coin counters will be set to 0 after running this command. Use Cashbox Payout Operation Data command to retrieve a breakdown of the denomination routed to the cashbox through this operation." + }, + "CASHBOX_PAYOUT_OPERATION_DATA": { + "code": 83, + "encrypted": false, + "args": false, + "device": ["SMART Hopper", "SMART Payout", "NV11"], + "description": "Can be sent at the end of a SMART Empty, float or dispense operation. Returns the amount emptied to cashbox from the payout in the last dispense, float or empty command. The quantity of denominations in the response is sent as a 2 byte little endian array; the note values as 4-byte little endian array and the country code as a 3-byte ASCII array. Each denomination in the dataset will be reported, even if 0 coins of that denomination are emptied. As money is emptied from the device, the value is checked. An additional 4 bytes will be added to the response giving a count of object that could not be validated whilst performing the operation. The response is formatted as follows: byteParameter byte 0The number denominations (n) in this response (max 20) byte 1 to byte 1 + (9*n)The individual denomination level (see description below) byte 1 to byte 1 + (9*n) + 1 to byte 1 to byte 1 + (9*n) + 4 The number of un-validated objects moved. Individual level requests: byte 0 and byte 1 number of coins of this denomination moved to cashbox in operation byte 2 to byte 5 The denomination value byte 6 to byte 8 The ascii denomination country code" + }, + "CONFIGURE_BEZEL": { + "code": 84, + "encrypted": false, + "args": { + "RGB": "hex color", + "volatile": "boolean" + }, + "device": ["NV200"], + "description": "This command allows the host to configure a supported BNV bezel. If the bezel is not supported the command will return generic response COMMAND NOT KNOWN 0xF2.", + "example": "SSP.command('CONFIGURE_BEZEL', { RGB: 'FF0000', volatile: false })" + }, + "POLL_WITH_ACK": { + "code": 86, + "encrypted": true, + "args": false, + "device": ["NV9USB", "NV10USB", "BV20", "BV50", "BV100", "NV200", "SMART Hopper", "NV11"], + "description": "A command that behaves in the same way as the Poll command but with this command, the specified events (see table below) will need to be acknowledged by the host using the EVENT ACK command (0x56). The events will repeat until the EVENT ACK command is sent and the BNV will not allow any further note actions until the event has been cleared by the EVENT ACK command. If this command is not supported by the slave device, then generic response 0xF2 will be returned and standard poll command (0x07) will have to be used." + }, + "EVENT_ACK": { + "code": 87, + "encrypted": true, + "args": false, + "device": ["NV9USB", "NV10USB", "BV20", "BV50", "BV100", "NV200", "SMART Hopper", "NV11"], + "description": "This command will clear a repeating Poll ACK response and allow further note operations." + }, + "GET_COUNTERS": { + "code": 88, + "encrypted": false, + "args": false, + "device": ["NV9USB", "SMART Payout", "NV11"], + "description": "A command to return a global note activity counter set for the slave device. The response is formatted as in the table below and the counter values are persistent in memory after a power down- power up cycle. These counters are note set independent and will wrap to zero and begin again if their maximum value is reached. Each counter is made up of 4 bytes of data giving a max value of 4294967295." + }, + "RESET_COUNTERS": { + "code": 89, + "encrypted": false, + "args": false, + "device": ["NV9USB", "SMART Payout", "NV11"], + "description": "Resets the note activity counters described in Get Counters command to all zero values." + }, + "COIN_MECH_OPTIONS": { + "code": 90, + "encrypted": false, + "args": { + "ccTalk": "boolean" + }, + "device": ["SMART Hopper"], + "description": "The host can set the following options for the SMART Hopper. These options do not persist in memory and after a reset they will go to their default values." + }, + "DISABLE_PAYOUT_DEVICE": { + "code": 91, + "encrypted": false, + "args": false, + "device": ["SMART Payout", "NV11"], + "description": "All accepted notes will be routed to the stacker and payout commands will not be accepted." + }, + "ENABLE_PAYOUT_DEVICE": { + "code": 92, + "encrypted": false, + "args": { + "GIVE_VALUE_ON_STORED": "boolean", + "NO_HOLD_NOTE_ON_PAYOUT": "boolean", + "REQUIRE_FULL_STARTUP": "boolean", + "OPTIMISE_FOR_PAYIN_SPEED": "boolean" + }, + "device": ["SMART Payout", "NV11"], + "description": "A command to enable the attached payout device for storing/paying out notes. A successful enable will return OK, If there is a problem the reply will be generic response COMMAND_CANNOT_BE_PROCESSED, followed by an error code.", + "example": "SSP.command('ENABLE_PAYOUT_DEVICE', { GIVE_VALUE_ON_STORED: true, NO_HOLD_NOTE_ON_PAYOUT: true })" + }, + "SET_FIXED_ENCRYPTION_KEY": { + "code": 96, + "encrypted": true, + "args": { + "fixedKey": "string" + }, + "device": ["SMART Hopper", "SMART Payout", "NV11"], + "description": "A command to allow the host to change the fixed part of the eSSP key. The eight data bytes are a 64 bit number representing the fixed part of the key. This command must be encrypted.", + "example": "SSP.command('SET_FIXED_ENCRYPTION_KEY', { fixedKey: '0123456701234567' })" + }, + "RESET_FIXED_ENCRYPTION_KEY": { + "code": 97, + "encrypted": false, + "args": false, + "device": ["SMART Hopper", "SMART Payout", "NV11"], + "description": "Resets the fixed encryption key to the device default. The device may have extra security requirements before it will accept this command (e.g. The Hopper must be empty) if these requirements are not met, the device will reply with Command Cannot be Processed. If successful, the device will reply OK, then reset. When it starts up the fixed key will be the default." + } +} diff --git a/lib/static/reject_note.json b/lib/static/reject_note.json new file mode 100644 index 0000000..993de1f --- /dev/null +++ b/lib/static/reject_note.json @@ -0,0 +1,122 @@ +{ + "0": { + "name": "NOTE_ACCEPTED", + "description": "The banknote has been accepted. No reject has occured." + }, + "1": { + "name": "LENGTH_FAIL", + "description": "A validation fail: The banknote has been read but it's length registers over the max length parameter." + }, + "2": { + "name": "AVERAGE_FAIL", + "description": "Internal validation failure - banknote not recognised." + }, + "3": { + "name": "COASTLINE_FAIL", + "description": "Internal validation failure - banknote not recognised." + }, + "4": { + "name": "GRAPH_FAIL", + "description": "Internal validation failure - banknote not recognised." + }, + "5": { + "name": "BURIED_FAIL", + "description": "Internal validation failure - banknote not recognised." + }, + "6": { + "name": "CHANNEL_INHIBIT", + "description": "This banknote has been inhibited for acceptance in the dataset configuration." + }, + "7": { + "name": "SECOND_NOTE_DETECTED", + "description": "A second banknote was inserted into the validator while the first one was still being transported through the banknote path." + }, + "8": { + "name": "REJECT_BY_HOST", + "description": "The host system issues a Reject command when this banknote was held in escrow." + }, + "9": { + "name": "CROSS_CHANNEL_DETECTED", + "description": "This bank note was identified as exisiting in two or more seperate channel definitions in the dataset." + }, + "10": { + "name": "REAR_SENSOR_ERROR", + "description": "An inconsistency in a position sensor detection was seen" + }, + "11": { + "name": "NOTE_TOO_LONG", + "description": "The banknote failed dataset length checks." + }, + "12": { + "name": "DISABLED_BY_HOST", + "description": "The bank note was validated on a channel that has been inhibited for acceptance by the host system." + }, + "13": { + "name": "SLOW_MECH", + "description": "The internal mechanism was detected as moving too slowly for correct validation." + }, + "14": { + "name": "STRIM_ATTEMPT", + "description": "The internal mechanism was detected as moving too slowly for correct validation." + }, + "15": { + "name": "FRAUD_CHANNEL", + "description": "Obselete response." + }, + "16": { + "name": "NO_NOTES_DETECTED", + "description": "A banknote detection was initiated but no banknotes were seen at the validation section." + }, + "17": { + "name": "PEAK_DETECT_FAIL", + "description": "Internal validation fail. Banknote not recognised." + }, + "18": { + "name": "TWISTED_NOTE_REJECT", + "description": "Internal validation fail. Banknote not recognised." + }, + "19": { + "name": "ESCROW_TIME-OUT", + "description": "A banknote held in escrow was rejected due to the host not communicating within the timeout period." + }, + "20": { + "name": "BAR_CODE_SCAN_FAIL", + "description": "Internal validation fail. Banknote not recognised." + }, + "21": { + "name": "NO_CAM_ACTIVATE", + "description": "A banknote did not reach the internal note path for validation during transport." + }, + "22": { + "name": "SLOT_FAIL_1", + "description": "Internal validation fail. Banknote not recognised." + }, + "23": { + "name": "SLOT_FAIL_2", + "description": "Internal validation fail. Banknote not recognised." + }, + "24": { + "name": "LENS_OVERSAMPLE", + "description": "The banknote was transported faster than the system could sample the note." + }, + "25": { + "name": "WIDTH_DETECTION_FAIL", + "description": "The banknote failed a measurement test." + }, + "26": { + "name": "SHORT_NOTE_DETECT", + "description": "The banknote measured length fell outside of the validation parameter for minimum length." + }, + "27": { + "name": "PAYOUT_NOTE", + "description": "The reject code cammand was issued after a note was payed out using a note payout device." + }, + "28": { + "name": "DOUBLE_NOTE_DETECTED", + "description": "Mote than one banknote was detected as overlayed during note entry." + }, + "29": { + "name": "UNABLE_TO_STACK", + "description": "The bank was unable to reach it's correct stacking position during transport" + } +} diff --git a/lib/static/status_desc.json b/lib/static/status_desc.json new file mode 100644 index 0000000..53e8d3c --- /dev/null +++ b/lib/static/status_desc.json @@ -0,0 +1,502 @@ +{ + "176": { + "name": "JAM_RECOVERY", + "description": "The SMART Payout unit is in the process of recovering from a detected jam. This process will typically move five notes to the cash box; this is done to minimise the possibility the unit will go out of service", + "devices": ["SMART Payout"] + }, + "177": { + "name": "ERROR_DURING_PAYOUT", + "description": "Returned if an error is detected whilst moving a note inside the SMART Payout unit. The cause of error (1 byte) indicates the source of the condition; 0x00 for note not being correctly detected as it is routed to cashbox or for payout, 0x01 if note is jammed in transport. In the case of the incorrect detection, the response to Cashbox Payout Operation Data request would report the note expected to be paid out.", + "devices": ["SMART Payout"], + "data": { + "Protocol version < 7": { + "error": "string" + }, + "Protocol version >= 7": { + "value": "Object[]", + "value[].value": "number", + "value[].country_code": "string", + "error": "string" + } + } + }, + "179": { + "name": "SMART_EMPTYING", + "description": "The device is in the process of carrying out its Smart Empty command from the host. The value emptied at the poll point is given in the event data.", + "devices": ["SMART Payout", "SMART Hopper", "NV11"], + "data": { + "Protocol version < 6": { + "value": "number" + }, + "Protocol version >= 6": { + "value": "Object[]", + "value[].value": "number", + "value[].country_code": "string" + } + } + }, + "180": { + "name": "SMART_EMPTIED", + "description": "The device has completed its Smart Empty command. The total amount emptied is given in the event data.", + "devices": ["SMART Payout", "SMART Hopper", "NV11"], + "data": { + "Protocol version < 6": { + "value": "number" + }, + "Protocol version >= 6": { + "value": "Object[]", + "value[].value": "number", + "value[].country_code": "string" + } + } + }, + "181": { + "name": "CHANNEL_DISABLE", + "description": "The device has had all its note channels inhibited and has become disabled for note insertion.", + "devices": ["BV20", "BV50", "BV100", "NV9USB", "NV10USB", "NV200", "SMART Payout", "NV11"] + }, + "182": { + "name": "INITIALISING", + "description": "This event is given only when using the Poll with ACK command. It is given when the BNV is powered up and setting its sensors and mechanisms to be ready for Note acceptance. When the event response does not contain this event, the BNV is ready to be enabled and used.", + "devices": ["BV20", "BV50", "BV100", "NV9USB", "NV10USB", "NV200", "SMART Payout", "NV11", "SMART Hopper"] + }, + "183": { + "name": "COIN_MECH_ERROR", + "description": "The attached coin mechanism has generated an error. Its code is given in the event data.", + "devices": ["SMART Hopper"] + }, + "194": { + "name": "EMPTYING", + "description": "The device is in the process of emptying its content to the system cashbox in response to an Empty command.", + "devices": ["SMART Payout", "SMART Hopper", "NV11"] + }, + "195": { + "name": "EMPTIED", + "description": "The device has completed its Empty process in response to an Empty command from the host.", + "devices": ["SMART Payout", "SMART Hopper", "NV11"] + }, + "196": { + "name": "COIN_MECH_JAMMED", + "description": "The attached coin mechanism has been detected as having a jam.", + "devices": ["SMART Hopper"] + }, + "197": { + "name": "COIN_MECH_RETURN_PRESSED", + "description": "The attached coin mechanism has been detected as having is reject or return button pressed.", + "devices": ["SMART Hopper"] + }, + "198": { + "name": "PAYOUT_OUT_OF_SERVICE", + "description": "This event is given if the payout goes out of service during operation. If this event is detected after a poll, the host can send the ENABLE PAYOUT DEVICE command to determine if the payout unit comes back into service.", + "devices": ["SMART Payout", "NV11"] + }, + "199": { + "name": "NOTE_FLOAT_REMOVED", + "description": "Reported when a note float unit has been detected as removed from its validator.", + "devices": ["NV11"] + }, + "200": { + "name": "NOTE_FLOAT_ATTACHED", + "description": "Reported when a note float unit has been detected as removed from its validator.", + "devices": ["NV11"] + }, + "201": { + "name": "NOTE_TRANSFERED_TO_STACKER", + "description": "Reported when a note has been successfully moved from the payout store into the stacker cashbox.", + "devices": ["SMART Payout", "NV11"], + "data": { + "Protocol version >= 6": { + "value": "Object[]", + "value[].value": "number", + "value[].country_code": "string" + } + } + }, + "202": { + "name": "NOTE_PAID_INTO_STACKER_AT_POWER-UP", + "description": "Reported when a note has been detected as paid into the cashbox stacker as part of the power-up procedure.", + "devices": ["SMART Payout", "NV11"], + "data": { + "Protocol version >= 8": { + "value": "Object[]", + "value[].value": "number", + "value[].country_code": "string" + } + } + }, + "203": { + "name": "NOTE_PAID_INTO_STORE_AT_POWER-UP", + "description": "Reported when a note has been detected as paid into the payout store as part of the power-up procedure.", + "devices": ["SMART Payout", "NV11"], + "data": { + "Protocol version >= 8": { + "value": "Object[]", + "value[].value": "number", + "value[].country_code": "string" + } + } + }, + "204": { + "name": "NOTE_STACKING", + "description": "The note is being moved from the escrow position to the host exit section of the device.", + "devices": ["BV20", "BV50", "BV100", "NV9USB", "NV10USB", "NV200", "SMART Payout", "NV11"] + }, + "205": { + "name": "NOTE_DISPENSED_AT_POWER-UP", + "description": "Reported when a note has been dispensed as part of the power-up procedure.", + "devices": ["NV11"], + "data": { + "Protocol version >= 6": { + "value": "Object[]", + "value[].value": "number", + "value[].country_code": "string" + } + } + }, + "206": { + "name": "NOTE_HELD_IN_BEZEL", + "description": "Reported when a dispensing note is held in the bezel of the payout device.", + "devices": ["SMART Payout", "NV11"], + "data": { + "Protocol version >= 8": { + "value": "Object[]", + "value[].value": "number", + "value[].country_code": "string" + } + } + }, + "207": { + "name": "DEVICE_FULL", + "description": "This event is reported when the Note Float has reached its limit of stored notes. This event will be reported until a note is paid out or stacked.", + "devices": ["NV11"] + }, + "209": { + "name": "BAR_CODE_TICKET_ACKNOWLEDGE", + "description": "The bar code ticket has been passed to a safe point in the device stacker.", + "devices": ["NV200", "NV201"] + }, + "210": { + "name": "DISPENSED", + "description": "The device has completed its pay-out request. The final value paid is given in the event data.", + "devices": ["SMART Payout", "SMART Hopper", "NV11"], + "data": { + "Protocol version < 6": { + "value": "number" + }, + "Protocol version >= 6": { + "value": "Object[]", + "value[].value": "number", + "value[].country_code": "string" + } + } + }, + "213": { + "name": "JAMMED", + "description": "The device has detected that coins are jammed in its mechanism and cannot be removed other than by manual intervention. The value paid at the jam point is given in the event data.", + "devices": ["SMART Payout", "SMART Hopper", "NV11"], + "data": { + "Protocol version < 6": { + "value": "number" + }, + "Protocol version >= 6": { + "value": "Object[]", + "value[].value": "number", + "value[].country_code": "string" + } + } + }, + "214": { + "name": "HALTED", + "description": "This event is given when the host has requested a halt to the device. The value paid at the point of halting is given in the event data.", + "devices": ["SMART Payout", "SMART Hopper", "NV11"], + "data": { + "Protocol version < 6": { + "value": "number" + }, + "Protocol version >= 6": { + "value": "Object[]", + "value[].value": "number", + "value[].country_code": "string" + } + } + }, + "215": { + "name": "FLOATING", + "description": "The device is in the process of executing a float command and the value paid to the cashbox at the poll time is given in the event data.", + "devices": ["SMART Payout", "SMART Hopper"], + "data": { + "Protocol version < 6": { + "value": "number" + }, + "Protocol version >= 6": { + "value": "Object[]", + "value[].value": "number", + "value[].country_code": "string" + } + } + }, + "216": { + "name": "FLOATED", + "description": "The device has completed its float command and the final value floated to the cashbox is given in the event data.", + "devices": ["SMART Payout", "SMART Hopper"], + "data": { + "Protocol version < 6": { + "value": "number" + }, + "Protocol version >= 6": { + "value": "Object[]", + "value[].value": "number", + "value[].country_code": "string" + } + } + }, + "217": { + "name": "TIME_OUT", + "description": "The device has been unable to complete a request. The value paid up until the time-out point is given in the event data.", + "devices": ["SMART Payout", "SMART Hopper", "NV11"], + "data": { + "Protocol version < 6": { + "value": "number" + }, + "Protocol version >= 6": { + "value": "Object[]", + "value[].value": "number", + "value[].country_code": "string" + } + } + }, + "218": { + "name": "DISPENSING", + "description": "The device is in the process of paying out a requested value. The value paid at the poll is given in the vent data.", + "devices": ["SMART Payout", "SMART Hopper", "NV11"], + "data": { + "Protocol version < 6": { + "value": "number" + }, + "Protocol version >= 6": { + "value": "Object[]", + "value[].value": "number", + "value[].country_code": "string" + } + } + }, + "219": { + "name": "NOTE_STORED_IN_PAYOUT", + "description": "The note has been passed into the note store of the payout unit.", + "devices": ["SMART Payout", "NV11"] + }, + "220": { + "name": "INCOMPLETE_PAYOUT", + "description": "The device has detected a discrepancy on power-up that the last payout request was interrupted (possibly due to a power failure). The amounts of the value paid and requested are given in the event data.", + "devices": ["SMART Payout", "SMART Hopper", "NV11"], + "data": { + "Protocol version < 6": { + "actual": "number", + "requested": "number" + }, + "Protocol version >= 6": { + "value": "Object[]", + "value[].actual": "number", + "value[].requested": "number", + "value[].country_code": "string" + } + } + }, + "221": { + "name": "INCOMPLETE_FLOAT", + "description": "The device has detected a discrepancy on power-up that the last float request was interrupted (possibly due to a power failure). The amounts of the value paid and requested are given in the event data.", + "devices": ["SMART Payout", "SMART Hopper", "NV11"], + "data": { + "Protocol version < 6": { + "actual": "number", + "requested": "number" + }, + "Protocol version >= 6": { + "value": "Object[]", + "value[].actual": "number", + "value[].requested": "number", + "value[].country_code": "string" + } + } + }, + "222": { + "name": "CASHBOX_PAID", + "description": "This is given at the end of a payout cycle. It shows the value of stored coins that were routed to the cashbox that were paid into the cashbox during the payout cycle.", + "devices": ["SMART Hopper"], + "data": { + "Protocol version < 6": { + "value": "number" + }, + "Protocol version >= 6": { + "value": "Object[]", + "value[].value": "number", + "value[].country_code": "string" + } + } + }, + "223": { + "name": "COIN_CREDIT", + "description": "A coin has been detected as added to the system via the attached coin mechanism. The value of the coin detected is given in the event data.", + "devices": ["SMART Hopper"], + "data": { + "Protocol version < 6": { + "value": "number" + }, + "Protocol version >= 6": { + "value": "Object[]", + "value[].value": "number", + "value[].country_code": "string" + } + } + }, + "224": { + "name": "NOTE_PATH_OPEN", + "description": "The device has detected that its note transport path has been opened.", + "devices": ["NV200"] + }, + "225": { + "name": "NOTE_CLEARED_FROM_FRONT", + "description": "At power-up, a note was detected as being rejected out of the front of the device. The channel value, if known is given in the data byte.", + "devices": ["BV20", "BV50", "BV100", "NV9USB", "NV10USB", "NV200", "SMART Payout", "NV11"], + "data": { + "All": { + "channel": "number" + } + } + }, + "226": { + "name": "NOTE_CLEARED_TO_CASHBOX", + "description": "At power up, a note was detected as being moved into the stacker unit or host exit of the device. The channel number of the note is given in the data byte if known.", + "devices": ["BV20", "BV50", "BV100", "NV9USB", "NV10USB", "NV200", "SMART Payout", "NV11"], + "data": { + "All": { + "channel": "number" + } + } + }, + "227": { + "name": "CASHBOX_REMOVED", + "description": "A device with a detectable cashbox has detected that it has been removed.", + "devices": ["BV50", "BV100", "NV200", "SMART Payout", "NV11"] + }, + "228": { + "name": "CASHBOX_REPLACED", + "description": "A device with a detectable cashbox has detected that it has been replaced.", + "devices": ["BV50", "BV100", "NV200", "SMART Payout", "NV11"] + }, + "229": { + "name": "BAR_CODE_TICKET_VALIDATED", + "description": "A validated barcode ticket has been scanned and is available at the escrow point of the device.", + "devices": ["NV200", "NV201"] + }, + "230": { + "name": "FRAUD_ATTEMPT", + "description": "The device has detected an attempt to tamper with the normal validation/stacking/payout process.", + "devices": ["BV20", "BV50", "BV100", "NV9USB", "NV10USB", "NV200", "SMART Payout", "NV11", "SMART Hopper"], + "data": { + "Banknote validators": { + "channel": "number" + }, + "Protocol version < 6": { + "value": "number" + }, + "Protocol version >= 6": { + "value": "Object[]", + "value[].value": "number", + "value[].country_code": "string" + } + } + }, + "231": { + "name": "STACKER_FULL", + "description": "The banknote stacker unit attached to this device has been detected as at its full limit", + "devices": ["BV20", "BV50", "BV100", "NV9USB", "NV10USB", "NV200", "SMART Payout", "NV11"] + }, + "232": { + "name": "DISABLED", + "description": "The device is not active and unavailable for normal validation functions.", + "devices": ["All"] + }, + "233": { + "name": "UNSAFE_NOTE_JAM", + "description": "The note is stuck in a position where the user could possibly remove it from the front of the device.", + "devices": ["BV20", "BV50", "BV100", "NV9USB", "NV10USB", "NV200", "SMART Payout", "NV11"] + }, + "234": { + "name": "SAFE_NOTE_JAM", + "description": "The note is stuck in a position not retrievable from the front of the device (user side)", + "devices": ["BV20", "BV50", "BV100", "NV9USB", "NV10USB", "NV200", "SMART Payout", "NV11"] + }, + "235": { + "name": "NOTE_STACKED", + "description": "The note has exited the device on the host side or has been placed within its note stacker.", + "devices": ["BV20", "BV50", "BV100", "NV9USB", "NV10USB", "NV200", "SMART Payout", "NV11"] + }, + "236": { + "name": "NOTE_REJECTED", + "description": "The note has been rejected from the validator and is available for the user to retrieve.", + "devices": ["BV20", "BV50", "BV100", "NV9USB", "NV10USB", "NV200", "SMART Payout", "NV11"] + }, + "237": { + "name": "NOTE_REJECTING", + "description": "The note is in the process of being rejected from the validator", + "devices": ["BV20", "BV50", "BV100", "NV9USB", "NV10USB", "NV200", "SMART Payout", "NV11"] + }, + "238": { + "name": "CREDIT_NOTE", + "description": "A note has passed through the device, past the point of possible recovery and the host can safely issue its credit amount. The byte value is the channel number of the note to credit.", + "devices": ["BV20", "BV50", "BV100", "NV9USB", "NV10USB", "NV200", "SMART Payout", "NV11"], + "data": { + "All": { + "channel": "number" + } + } + }, + "239": { + "name": "READ_NOTE", + "description": "A note is in the process of being scanned by the device (byte value 0) or a valid note has been scanned and is in escrow (byte value gives the channel number)", + "devices": ["BV20", "BV50", "BV100", "NV9USB", "NV10USB", "NV200", "SMART Payout", "NV11"], + "data": { + "All": { + "channel": "number" + } + } + }, + "240": { + "name": "OK", + "description": "Returned when a command from the host is understood and has been, or is in the process of, being executed." + }, + "241": { + "name": "SLAVE_RESET", + "description": "The device has undergone a power reset.", + "devices": ["All"] + }, + "242": { + "name": "COMMAND_NOT_KNOWN", + "description": "Returned when an invalid command is received by a peripheral." + }, + "243": { + "name": "WRONG_NO_PARAMETERS", + "description": "A command was received by a peripheral, but an incorrect number of parameters were received." + }, + "244": { + "name": "PARAMETER_OUT_OF_RANGE", + "description": "One of the parameters sent with a command is out of range." + }, + "245": { + "name": "COMMAND_CANNOT_BE_PROCESSED", + "description": "A command sent could not be processed at that time. E.g. sending a dispense command before the last dispense operation has completed." + }, + "246": { + "name": "SOFTWARE_ERROR", + "description": "Reported for errors in the execution of software e.g. Divide by zero. This may also be reported if there is a problem resulting from a failed remote firmware upgrade, in this case the firmware upgrade should be redone." + }, + "248": { + "name": "FAIL", + "description": "Command failure" + }, + "250": { + "name": "KEY_NOT_SET", + "description": "The slave is in encrypted communication mode but the encryption keys have not been negotiated." + } +} diff --git a/lib/static/unit_type.json b/lib/static/unit_type.json new file mode 100644 index 0000000..3613118 --- /dev/null +++ b/lib/static/unit_type.json @@ -0,0 +1,11 @@ +{ + "0": "Banknote validator", + "3": "Smart Hopper", + "6": "SMART payout fitted", + "7": "Note Float fitted", + "8": "Addon Printer", + "11": "Stand Alone Printer", + "13": "TEBS", + "14": "TEBS with SMART Payout", + "15": "TEBS with SMART Ticket" +} diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000..a431456 --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,1065 @@ +const crypto = require('node:crypto') +const { satisfies } = require('semver') +const chalk = require('chalk') +const debug = require('debug')('ssp') +const statusDesc = require('./static/status_desc.json') +const unitType = require('./static/unit_type.json') +const rejectNote = require('./static/reject_note.json') +const commandList = require('./static/commands.json') +const { engines } = require('../package.json') + +const STX = 0x7f +const STEX = 0x7e + +/** + * Returns the absolute value of a BigInt. + * + * @param {BigInt} n - The BigInt for which to calculate the absolute value. + * @returns {BigInt} - The absolute value of the input BigInt. + */ +const absBigInt = n => (n < 0n ? -n : n) + +/** + * Encrypts data using AES encryption with ECB mode. + * + * @param {Buffer} key - The key for encryption. + * @param {Buffer} data - The data to be encrypted. + * @returns {Buffer} - The encrypted data. + * @throws {Error} - Throws an error if key or data is not provided. + */ +function encrypt(key, data) { + if (!key || !Buffer.isBuffer(key)) { + throw new Error('Key must be a Buffer') + } + + if (!data || !Buffer.isBuffer(data)) { + throw new Error('Data must be a Buffer') + } + + // Create cipher using ECB mode (Electronic Codebook) + const cipher = crypto.createCipheriv('aes-128-ecb', key, null) + + // Enable automatic padding + cipher.setAutoPadding(false) + + // Encrypt the data + const encryptedData = Buffer.concat([cipher.update(data), cipher.final()]) + + return encryptedData +} + +/** + * Decrypts data using AES decryption with ECB mode. + * + * @param {Buffer} key - The key for decryption. + * @param {Buffer} data - The data to be decrypted. + * @returns {Buffer} - The decrypted data. + * @throws {Error} - Throws an error if key or data is not provided. + */ +function decrypt(key, data) { + if (!key || !Buffer.isBuffer(key)) { + throw new Error('Key must be a Buffer') + } + + if (!data || !Buffer.isBuffer(data)) { + throw new Error('Data must be a Buffer') + } + + // Create decipher using ECB mode (Electronic Codebook) + const decipher = crypto.createDecipheriv('aes-128-ecb', key, null) + + // Enable automatic padding + decipher.setAutoPadding(false) + + // Decrypt the data + const decryptedData = Buffer.concat([decipher.update(data), decipher.final()]) + + return decryptedData +} + +/** + * Reads bytes from a Buffer starting from the specified index with the given length. + * + * @param {Buffer} buffer - The Buffer from which to read bytes. + * @param {number} startIndex - The starting index from which to begin reading. + * @param {number} length - The number of bytes to read. + * @returns {Buffer} - A new Buffer containing the extracted bytes. + * @throws {Error} - Throws an error if the input is not a Buffer, if the start index is invalid, + * or if the length exceeds the buffer size. + */ +function readBytesFromBuffer(buffer, startIndex, length) { + if (!buffer || !Buffer.isBuffer(buffer)) { + throw new Error('Input must be a Buffer object') + } + + // Ensure the provided index is within the buffer's bounds + if (startIndex < 0 || startIndex >= buffer.length) { + throw new Error('Invalid start index') + } + + // Ensure the requested length doesn't exceed the buffer's remaining size + if (length < 0 || startIndex + length > buffer.length) { + throw new Error('Invalid length or exceeds buffer size') + } + + // Use subarray to extract the specified range of bytes + return buffer.subarray(startIndex, startIndex + length) +} + +function randomInt(min, max) { + return Math.floor(Math.random() * (max - min)) + min +} + +/** + * Calculate CRC16 checksum for the given source data. + * @param {number[] | Buffer} source - The source data represented as an array of numbers or a Buffer object. + * @returns {Buffer} A Buffer containing the CRC16 checksum bytes. + */ +function CRC16(source) { + const CRC_SSP_SEED = 0xffff + const CRC_SSP_POLY = 0x8005 + let crc = CRC_SSP_SEED + + for (let i = 0; i < source.length; ++i) { + crc ^= source[i] << 8 + for (let j = 0; j < 8; ++j) { + crc = crc & 0x8000 ? (crc << 1) ^ CRC_SSP_POLY : crc << 1 + } + } + + // Ensure CRC is within 16-bit range + crc &= 0xffff + + const crcBuffer = Buffer.alloc(2) + crcBuffer.writeUInt16LE(crc, 0) + return crcBuffer +} + +/** + * Creates a Buffer representing the given unsigned 64-bit integer in little-endian format. + * + * @param {bigint} number - The unsigned 64-bit integer. + * @throws {Error} - If the input is not an unsigned 64-bit integer or is out of range. + * @returns {Buffer} - Buffer representing the unsigned 64-bit integer in little-endian format. + */ +function uInt64LE(number) { + if (!Number.isInteger(Number(number)) || number < 0 || number > 18446744073709551615n) { + throw new Error('Input must be an unsigned 64-bit integer') + } + + const buffer = Buffer.alloc(8) + buffer.writeBigUInt64LE(BigInt(number)) + return buffer +} + +/** + * Creates a Buffer representing the given unsigned 32-bit integer in little-endian format. + * + * @param {number} number - The unsigned 32-bit integer. + * @throws {Error} - If the input is not an unsigned 32-bit integer or is out of range. + * @returns {Buffer} - Buffer representing the unsigned 32-bit integer in little-endian format. + */ +function uInt32LE(number) { + if (!Number.isInteger(number) || number < 0 || number > 4294967295) { + throw new Error('Input must be an unsigned 32-bit integer') + } + + const buffer = Buffer.alloc(4) + buffer.writeUInt32LE(number) + return buffer +} + +/** + * Creates a Buffer representing the given unsigned 16-bit integer in little-endian format. + * + * @param {number} number - The unsigned 16-bit integer. + * @throws {Error} - If the input is not an unsigned 16-bit integer or is out of range. + * @returns {Buffer} - Buffer representing the unsigned 16-bit integer in little-endian format. + */ +function uInt16LE(number) { + if (!Number.isInteger(number) || number < 0 || number > 65535) { + throw new Error('Input must be an unsigned 16-bit integer') + } + + const buffer = Buffer.alloc(2) + buffer.writeUInt16LE(number) + return buffer +} + +/** + * Creates a Buffer from given arguments. + * + * @param {string} command - Comand name. + * @param {object} args - Object containing requried arguments. + * @returns {Buffer} - Buffer of converted argumants. + */ +function argsToByte(command, args, protocolVersion) { + if (args !== undefined) { + switch (command) { + case 'SET_GENERATOR': + case 'SET_MODULUS': + case 'REQUEST_KEY_EXCHANGE': + return uInt64LE(args.key) + case 'SET_DENOMINATION_ROUTE': { + const routeBuffer = Buffer.from([args.route === 'payout' ? 0 : 1]) + const valueBuffer32 = uInt32LE(args.value) + + if (protocolVersion >= 6) { + const countryCodeBuffer = Buffer.from(args.country_code, 'ascii') + return Buffer.concat([routeBuffer, valueBuffer32, countryCodeBuffer]) + } + + const valueHopperBuffer = args.isHopper ? uInt16LE(args.value) : valueBuffer32 + return Buffer.concat([routeBuffer, valueHopperBuffer]) + } + case 'SET_CHANNEL_INHIBITS': + return uInt16LE(args.channels.reduce((acc, bit, index) => acc | (bit << index), 0)) + case 'SET_COIN_MECH_GLOBAL_INHIBIT': + return Buffer.from([args.enable ? 1 : 0]) + case 'SET_HOPPER_OPTIONS': { + let res = 0 + if (args.payMode) res += 1 + if (args.levelCheck) res += 2 + if (args.motorSpeed) res += 4 + if (args.cashBoxPayActive) res += 8 + return uInt16LE(res) + } + case 'GET_DENOMINATION_ROUTE': { + const valueBuffer32 = uInt32LE(args.value) + if (protocolVersion >= 6) { + const countryCodeBuffer = Buffer.from(args.country_code, 'ascii') + return Buffer.concat([valueBuffer32, countryCodeBuffer]) + } + return args.isHopper ? uInt16LE(args.value) : valueBuffer32 + } + case 'SET_DENOMINATION_LEVEL': { + const valueBuffer = uInt16LE(args.value) + + if (protocolVersion >= 6) { + const countryCodeBuffer = Buffer.from(args.country_code, 'ascii') + const denominationBuffer32 = uInt32LE(args.denomination) + return Buffer.concat([valueBuffer, denominationBuffer32, countryCodeBuffer]) + } + + const denominationBuffer = uInt16LE(args.denomination) + return Buffer.concat([valueBuffer, denominationBuffer]) + } + case 'SET_REFILL_MODE': { + let result = Buffer.alloc(0) + switch (args.mode) { + case 'on': + result = Buffer.from([0x05, 0x81, 0x10, 0x11, 0x01]) + break + case 'off': + result = Buffer.from([0x05, 0x81, 0x10, 0x11, 0x00]) + break + case 'get': + result = Buffer.from([0x05, 0x81, 0x10, 0x01]) + break + } + return result + } + case 'HOST_PROTOCOL_VERSION': + return Buffer.from([args.version]) + case 'SET_BAR_CODE_CONFIGURATION': { + const enable = { none: 0, top: 1, bottom: 2, both: 3 } + const number = Math.min(Math.max(args.numChar || 6, 6), 24) + return Buffer.from([enable[args.enable || 'none'], 0x01, number]) + } + case 'SET_BAR_CODE_INHIBIT_STATUS': { + let byte = 0xff + if (!args.currencyRead) byte &= 0xfe + if (!args.barCode) byte &= 0xfd + return Buffer.from([byte]) + } + case 'PAYOUT_AMOUNT': { + const amountBuffer = uInt32LE(args.amount) + + if (protocolVersion >= 6) { + const countryCodeBuffer = Buffer.from(args.country_code, 'ascii') + const testBuffer = Buffer.from([args.test ? 0x19 : 0x58]) + return Buffer.concat([amountBuffer, countryCodeBuffer, testBuffer]) + } + + return amountBuffer + } + case 'GET_DENOMINATION_LEVEL': { + const amountBuffer = uInt32LE(args.amount) + + if (protocolVersion >= 6) { + const countryCodeBuffer = Buffer.from(args.country_code, 'ascii') + return Buffer.concat([amountBuffer, countryCodeBuffer]) + } + + return amountBuffer + } + case 'FLOAT_AMOUNT': { + const minBuffer = uInt16LE(args.min_possible_payout) + const amountBuffer = uInt32LE(args.amount) + + if (protocolVersion >= 6) { + const countryCodeBuffer = Buffer.from(args.country_code, 'ascii') + const testBuffer = Buffer.from([args.test ? 0x19 : 0x58]) + return Buffer.concat([minBuffer, amountBuffer, countryCodeBuffer, testBuffer]) + } + + return Buffer.concat([minBuffer, amountBuffer]) + } + case 'SET_COIN_MECH_INHIBITS': { + const inhibitBuffer = Buffer.from([args.inhibited ? 0x00 : 0x01]) + const amountBuffer = uInt16LE(args.amount) + + if (protocolVersion >= 6) { + const countryCodeBuffer = Buffer.from(args.country_code, 'ascii') + return Buffer.concat([inhibitBuffer, amountBuffer, countryCodeBuffer]) + } + + return Buffer.concat([inhibitBuffer, amountBuffer]) + } + case 'FLOAT_BY_DENOMINATION': + case 'PAYOUT_BY_DENOMINATION': { + const tmpBufferArray = [Buffer.from([args.value.length])] + const testBuffer = Buffer.from([args.test ? 0x19 : 0x58]) + + for (let i = 0; i < args.value.length; i++) { + const countBuffer = uInt16LE(args.value[i].number) + const denominationBuffer = uInt32LE(args.value[i].denomination) + const countryCodeBuffer = Buffer.from(args.value[i].country_code, 'ascii') + tmpBufferArray.push(countBuffer, denominationBuffer, countryCodeBuffer) + } + + tmpBufferArray.push(testBuffer) + + return Buffer.concat(tmpBufferArray) + } + case 'SET_VALUE_REPORTING_TYPE': + return Buffer.from([args.reportBy === 'channel' ? 0x01 : 0x00]) + case 'SET_BAUD_RATE': { + let byte = 9600 + switch (args.baudrate) { + case 9600: + byte = 0 + break + case 38400: + byte = 1 + break + case 115200: + byte = 2 + break + } + return Buffer.from([byte, args.reset_to_default_on_reset ? 0 : 1]) + } + case 'CONFIGURE_BEZEL': + return Buffer.concat([Buffer.from(args.RGB, 'hex'), Buffer.from([args.volatile ? 0 : 1])]) + case 'ENABLE_PAYOUT_DEVICE': { + let byte = 0 + byte += args.GIVE_VALUE_ON_STORED || args.REQUIRE_FULL_STARTUP ? 1 : 0 + byte += args.NO_HOLD_NOTE_ON_PAYOUT || args.OPTIMISE_FOR_PAYIN_SPEED ? 2 : 0 + return Buffer.from([byte]) + } + case 'SET_FIXED_ENCRYPTION_KEY': + return Buffer.from(args.fixedKey, 'hex').swap64() + case 'COIN_MECH_OPTIONS': + return Buffer.from([args.ccTalk ? 1 : 0]) + default: + return Buffer.alloc(0) + } + } + return Buffer.alloc(0) +} + +function parseData(data, currentCommand, protocolVersion, deviceUnitType) { + const result = { + success: data[0] === 0xf0, + status: statusDesc[data[0]] !== undefined ? statusDesc[data[0]].name : 'UNDEFINED', + command: currentCommand, + info: {}, + } + + if (result.success) { + data = Buffer.from(data).subarray(1) + + if (currentCommand === 'REQUEST_KEY_EXCHANGE') { + result.info.key = Array.from(data) + } else if (currentCommand === 'SETUP_REQUEST') { + // Common for all device types + const unit_type = unitType[data[0]] + const firmware_version = (parseInt(readBytesFromBuffer(data, 1, 4).toString()) / 100).toFixed(2) + const country_code = readBytesFromBuffer(data, 5, 3).toString() + const isSmartHopper = data[0] === 3 + + if (isSmartHopper) { + // Smart Hopper specific + const protocol_version = data.readUInt8(8) + const number_of_coin_values = data.readUInt8(9) + const coin_values = Array.from({ length: number_of_coin_values }, (_, i) => data.readUIntLE(10 + i * 2, 2)) + + Object.assign(result.info, { + unit_type, + firmware_version, + country_code, + protocol_version, + number_of_coin_values, + coin_values, + }) + + if (protocol_version >= 6) { + const country_codes_for_values = Array.from({ length: number_of_coin_values }, (_, i) => + readBytesFromBuffer(data, 10 + number_of_coin_values * 2 + i * 3, 3).toString(), + ) + Object.assign(result.info, { country_codes_for_values }) + } + } else { + // Other devices + const n = data.readUInt8(11) + const value_multiplier = data.readUIntBE(8, 3) + + Object.assign(result.info, { + channel_security: Array.from(data.slice(12 + n, 12 + n * 2)), + channel_value: Array.from(readBytesFromBuffer(data, 12, n).map(value => value * value_multiplier)), + country_code, + firmware_version, + number_of_channels: n, + protocol_version: data.readUInt8(15 + n * 2), + real_value_multiplier: data.readUIntBE(12 + n * 2, 3), + unit_type, + value_multiplier, + }) + + if (result.info.protocol_version >= 6) { + Object.assign(result.info, { + expanded_channel_country_code: readBytesFromBuffer(data, 16 + n * 2, n * 3) + .toString() + .match(/.{3}/g), + expanded_channel_value: Array.from({ length: n }, (_, i) => readBytesFromBuffer(data, 16 + n * 5, n * 4).readUInt32LE(i * 4)), + }) + } + } + } else if (currentCommand === 'GET_SERIAL_NUMBER') { + result.info.serial_number = Buffer.from(data.slice(0, 4)).readUInt32BE() + } else if (currentCommand === 'UNIT_DATA') { + Object.assign(result.info, { + unit_type: unitType[data[0]], + firmware_version: (parseInt(readBytesFromBuffer(data, 1, 4).toString()) / 100).toFixed(2), + country_code: readBytesFromBuffer(data, 5, 3).toString(), + value_multiplier: data.readUIntBE(8, 3), + protocol_version: data.readUInt8(11), + }) + } else if (currentCommand === 'CHANNEL_VALUE_REQUEST') { + const count = data[0] + + if (protocolVersion >= 6) { + Object.assign(result.info, { + channel: Array.from(data.subarray(1, count + 1)), + country_code: Array.from({ length: count }, (_, i) => readBytesFromBuffer(data, count + 1 + i * 3, 3).toString()), + value: Array.from({ length: count }, (_, i) => data.readUIntLE(count + 1 + count * 3 + i * 4, 4)), + }) + } else { + result.info.channel = Array.from(data.subarray(1, count + 1)) + } + } else if (currentCommand === 'CHANNEL_SECURITY_DATA') { + const level = { + 0: 'not_implemented', + 1: 'low', + 2: 'std', + 3: 'high', + 4: 'inhibited', + } + result.info.channel = {} + for (let i = 1; i <= data[0]; i++) { + result.info.channel[i] = level[data[i]] + } + } else if (currentCommand === 'CHANNEL_RE_TEACH_DATA') { + result.info.source = Array.from(data) + } else if (currentCommand === 'LAST_REJECT_CODE') { + result.info.code = data[0] + result.info.name = rejectNote[data[0]].name + result.info.description = rejectNote[data[0]].description + } else if (currentCommand === 'GET_FIRMWARE_VERSION' || currentCommand === 'GET_DATASET_VERSION') { + result.info.version = Buffer.from(data).toString() + } else if (currentCommand === 'GET_ALL_LEVELS') { + result.info.counter = {} + for (let i = 0; i < data[0]; i++) { + const tmp = data.slice(i * 9 + 1, i * 9 + 10) + result.info.counter[i + 1] = { + denomination_level: Buffer.from(tmp.slice(0, 2)).readUInt16LE(), + value: Buffer.from(tmp.slice(2, 6)).readUInt32LE(), + country_code: Buffer.from(tmp.slice(6, 9)).toString(), + } + } + } else if (currentCommand === 'GET_BAR_CODE_READER_CONFIGURATION') { + const status = { + 0: { 0: 'none', 1: 'Top reader fitted', 2: 'Bottom reader fitted', 3: 'both fitted' }, + 1: { 0: 'none', 1: 'top', 2: 'bottom', 3: 'both' }, + 2: { 1: 'Interleaved 2 of 5' }, + } + result.info = { + bar_code_hardware_status: status[0][data[0]], + readers_enabled: status[1][data[1]], + bar_code_format: status[2][data[2]], + number_of_characters: data[3], + } + } else if (currentCommand === 'GET_BAR_CODE_INHIBIT_STATUS') { + result.info.currency_read_enable = data[0].toString(2).slice(7, 8) === '0' + result.info.bar_code_enable = data[0].toString(2).slice(6, 7) === '0' + } else if (currentCommand === 'GET_BAR_CODE_DATA') { + const status = { 0: 'no_valid_data', 1: 'ticket_in_escrow', 2: 'ticket_stacked', 3: 'ticket_rejected' } + result.info.status = status[data[0]] + result.info.data = Buffer.from(data.slice(2, data[1] + 2)).toString() + } else if (currentCommand === 'GET_DENOMINATION_LEVEL') { + result.info.level = Buffer.from(data).readUInt16LE() + } else if (currentCommand === 'GET_DENOMINATION_ROUTE') { + const res = { + 0: { code: 0, value: 'Recycled and used for payouts' }, + 1: { code: 1, value: 'Detected denomination is routed to system cashbox' }, + } + result.info = res[data[0]] + } else if (currentCommand === 'GET_MINIMUM_PAYOUT') { + result.info.value = Buffer.from(data).readUInt32LE() + } else if (currentCommand === 'GET_NOTE_POSITIONS') { + const count = data[0] + data = data.slice(1) + result.info.slot = {} + + if (data.length === count) { + for (let i = 0; i < count; i++) { + result.info.slot[i + 1] = { channel: data[i] } + } + } else { + for (let i = 0; i < count; i++) { + result.info.slot[i + 1] = { value: data.readUInt32LE(i * 4) } + } + } + } else if (currentCommand === 'GET_BUILD_REVISION') { + const count = data.length / 3 + result.info.device = {} + for (let i = 0; i < count; i++) { + result.info.device[i] = { + unitType: unitType[data[i * 3]], + revision: Buffer.from(data.slice(i * 3 + 1, i * 3 + 3)).readUInt16LE(), + } + } + } else if (currentCommand === 'GET_COUNTERS') { + result.info.stacked = Buffer.from(data.slice(1, 5)).readUInt32LE() + result.info.stored = Buffer.from(data.slice(5, 9)).readUInt32LE() + result.info.dispensed = Buffer.from(data.slice(9, 13)).readUInt32LE() + result.info.transferred_from_store_to_stacker = Buffer.from(data.slice(13, 17)).readUInt32LE() + result.info.rejected = Buffer.from(data.slice(17, 21)).readUInt32LE() + } else if (currentCommand === 'GET_HOPPER_OPTIONS') { + const value = data.readUInt16LE(0) + + Object.assign(result.info, { + payMode: (value & 0x01) !== 0, + levelCheck: (value & 0x02) !== 0, + motorSpeed: (value & 0x04) !== 0, + cashBoxPayAcive: (value & 0x08) !== 0, + }) + } else if (currentCommand === 'POLL' || currentCommand === 'POLL_WITH_ACK') { + data = Buffer.from(data) + result.info = [] + + let k = 0 + while (k < data.length) { + const code = data[k] + + if (!statusDesc[code]) { + k += 1 + continue + } + + const info = { + code, + name: statusDesc[code]?.name, + description: statusDesc[code]?.description, + } + + switch (info.name) { + case 'SLAVE_RESET': + case 'NOTE_REJECTING': + case 'NOTE_REJECTED': + case 'NOTE_STACKING': + case 'NOTE_STACKED': + case 'SAFE_NOTE_JAM': + case 'UNSAFE_NOTE_JAM': + case 'DISABLED': + case 'STACKER_FULL': + case 'CASHBOX_REMOVED': + case 'CASHBOX_REPLACED': + case 'BAR_CODE_TICKET_VALIDATED': + case 'BAR_CODE_TICKET_ACKNOWLEDGE': + case 'NOTE_PATH_OPEN': + case 'CHANNEL_DISABLE': + case 'INITIALISING': + case 'COIN_MECH_JAMMED': + case 'COIN_MECH_RETURN_PRESSED': + case 'EMPTYING': + case 'EMPTIED': + case 'COIN_MECH_ERROR': + case 'NOTE_STORED_IN_PAYOUT': + case 'PAYOUT_OUT_OF_SERVICE': + case 'JAM_RECOVERY': + case 'NOTE_FLOAT_REMOVED': + case 'NOTE_FLOAT_ATTACHED': + case 'DEVICE_FULL': + k += 1 + break + + case 'READ_NOTE': + case 'CREDIT_NOTE': + case 'NOTE_CLEARED_FROM_FRONT': + case 'NOTE_CLEARED_TO_CASHBOX': + info.channel = data.readUInt8(k + 1) + k += 2 + break + + case 'FRAUD_ATTEMPT': { + const smartDevice = [unitType[3], unitType[6]].includes(deviceUnitType) + + if (protocolVersion >= 6 && smartDevice) { + const length = data[k + 1] + info.value = Array.from({ length }, (_, i) => ({ + value: data.readUInt32LE(k + 2 + i * 7), + country_code: readBytesFromBuffer(data, k + 6 + i * 7, 3).toString(), + })) + + k += 2 + length * 7 + } else if (smartDevice) { + info.value = data.readUInt32LE(k + 1) + k += 5 + } else { + info.channel = data.readUInt8(k + 1) + k += 2 + } + break + } + + case 'DISPENSING': + case 'DISPENSED': + case 'JAMMED': + case 'HALTED': + case 'FLOATING': + case 'FLOATED': + case 'TIME_OUT': + case 'CASHBOX_PAID': + case 'COIN_CREDIT': + case 'SMART_EMPTYING': + case 'SMART_EMPTIED': + if (protocolVersion >= 6) { + const length = data[k + 1] + info.value = Array.from({ length }, (_, i) => ({ + value: data.readUInt32LE(k + 2 + i * 7), + country_code: readBytesFromBuffer(data, k + 6 + i * 7, 3).toString(), + })) + + k += 2 + length * 7 + } else { + info.value = data.readUInt32LE(k + 1) + k += 5 + } + break + + case 'INCOMPLETE_PAYOUT': + case 'INCOMPLETE_FLOAT': + if (protocolVersion >= 6) { + const length = data[k + 1] + info.value = Array.from({ length }, (_, i) => ({ + actual: data.readUInt32LE(k + 2 + i * 11), + requested: data.readUInt32LE(k + 6 + i * 11), + country_code: readBytesFromBuffer(data, k + 10 + i * 11, 3).toString(), + })) + + k += 2 + length * 11 + } else { + info.actual = data.readUInt32LE(k + 1) + info.requested = data.readUInt32LE(k + 5) + k += 9 + } + break + + case 'ERROR_DURING_PAYOUT': { + const errors = { + 0x00: 'Note not being correctly detected as it is routed', + 0x01: 'Note jammed in transport', + } + + if (protocolVersion >= 7) { + const length = data[k + 1] + info.value = Array.from({ length }, (_, i) => ({ + value: data.readUInt32LE(k + 2 + i * 7), + country_code: readBytesFromBuffer(data, k + 6 + i * 7, 3).toString(), + })) + + info.error = errors[data.readUInt8(k + 2 + length * 7)] + + k += 3 + length * 7 + } else { + info.error = errors[data.readUInt8(k + 1)] + k += 2 + } + break + } + + case 'NOTE_TRANSFERED_TO_STACKER': + case 'NOTE_DISPENSED_AT_POWER-UP': { + if (protocolVersion >= 6) { + info.value = { + value: data.readUInt32LE(k + 1), + country_code: readBytesFromBuffer(data, k + 5, 3).toString(), + } + + k += 8 + } else { + k += 1 + } + break + } + + case 'NOTE_HELD_IN_BEZEL': + case 'NOTE_PAID_INTO_STACKER_AT_POWER-UP': + case 'NOTE_PAID_INTO_STORE_AT_POWER-UP': + if (protocolVersion >= 8) { + info.value = { + value: data.readUInt32LE(k + 1), + country_code: readBytesFromBuffer(data, k + 5, 3).toString(), + } + + k += 8 + } else { + k += 1 + } + break + } + + result.info.push(info) + } + } else if (currentCommand === 'CASHBOX_PAYOUT_OPERATION_DATA') { + result.info = { data: [] } + for (let i = 0; i < data[0]; i++) { + result.info.data[i] = { + quantity: Buffer.from(data.slice(i * 9 + 1, i * 9 + 3)).readUInt16LE(), + value: Buffer.from(data.slice(i * 9 + 3, i * 9 + 7)).readUInt32LE(), + country_code: Buffer.from(data.slice(i * 9 + 7, i * 9 + 10)).toString(), + } + } + } else if (currentCommand === 'SET_REFILL_MODE' && data.length === 1) { + result.info = { + enabled: data[0] === 0x01, + } + } + } else { + if (result.status === 'COMMAND_CANNOT_BE_PROCESSED' && currentCommand === 'ENABLE_PAYOUT_DEVICE') { + result.info.errorCode = data[1] + switch (data[1]) { + case 1: + result.info.error = 'No device connected' + break + case 2: + result.info.error = 'Invalid currency detected' + break + case 3: + result.info.error = 'Device busy' + break + case 4: + result.info.error = 'Empty only (Note float only)' + break + case 5: + result.info.error = 'Device error' + break + default: + result.info.error = 'Unknown error' + break + } + } else if ( + result.status === 'COMMAND_CANNOT_BE_PROCESSED' && + (currentCommand === 'PAYOUT_BY_DENOMINATION' || currentCommand === 'FLOAT_AMOUNT' || currentCommand === 'PAYOUT_AMOUNT') + ) { + result.info.errorCode = data[1] + switch (data[1]) { + case 0: + result.info.error = 'Not enough value in device' + break + case 1: + result.info.error = 'Cannot pay exact amount' + break + case 3: + result.info.error = 'Device busy' + break + case 4: + result.info.error = 'Device disabled' + break + default: + result.info.error = 'Unknown error' + break + } + } else if ( + result.status === 'COMMAND_CANNOT_BE_PROCESSED' && + (currentCommand === 'SET_VALUE_REPORTING_TYPE' || currentCommand === 'GET_DENOMINATION_ROUTE' || currentCommand === 'SET_DENOMINATION_ROUTE') + ) { + result.info.errorCode = data[1] + switch (data[1]) { + case 1: + result.info.error = 'No payout connected' + break + case 2: + result.info.error = 'Invalid currency detected' + break + case 3: + result.info.error = 'Payout device error' + break + default: + result.info.error = 'Unknown error' + break + } + } else if (result.status === 'COMMAND_CANNOT_BE_PROCESSED' && currentCommand === 'FLOAT_BY_DENOMINATION') { + result.info.errorCode = data[1] + switch (data[1]) { + case 0: + result.info.error = 'Not enough value in device' + break + case 1: + result.info.error = 'Cannot pay exact amount' + break + case 3: + result.info.error = 'Device busy' + break + case 4: + result.info.error = 'Device disabled' + break + default: + result.info.error = 'Unknown error' + break + } + } else if (result.status === 'COMMAND_CANNOT_BE_PROCESSED' && (currentCommand === 'STACK_NOTE' || currentCommand === 'PAYOUT_NOTE')) { + result.info.errorCode = data[1] + switch (data[1]) { + case 1: + result.info.error = 'Note float unit not connected' + break + case 2: + result.info.error = 'Note float empty' + break + case 3: + result.info.error = 'Note float busy' + break + case 4: + result.info.error = 'Note float disabled' + break + default: + result.info.error = 'Unknown error' + break + } + } else if (result.status === 'COMMAND_CANNOT_BE_PROCESSED' && currentCommand === 'GET_NOTE_POSITIONS') { + result.info.errorCode = data[1] + if (data[1] === 2) { + result.info.error = 'Invalid currency' + } + } + } + + return result +} + +/** + * Stuff bytes in a buffer: if a byte of value 0x7f is found, adds another byte with the value 0x7f after it. + * @param {Buffer} inputBuffer - The input buffer to stuff. + * @returns {Buffer} - The stuffed buffer. + */ +function stuffBuffer(inputBuffer) { + const outputBuffer = Buffer.allocUnsafe(inputBuffer.length * 2) // Allocate buffer with double the size + let j = 0 + + for (let i = 0; i < inputBuffer.length; i++) { + const byte = inputBuffer[i] + outputBuffer[j++] = byte // Copy the original byte to the output buffer + + // If the byte has a value of 0x7f, add another byte with the value 0x7f after it + if (byte === 0x7f) { + outputBuffer[j++] = 0x7f + } + } + + return outputBuffer.subarray(0, j) // Truncate the buffer to the actual size +} + +/** + * Validates if the current Node.js version satisfies the required version. + * @throws {Error} Throws an error if the current Node.js version does not satisfy the required version. + */ +function validateNodeVersion() { + if (!satisfies(process.version, engines.node)) { + throw new Error(`Node.js version must be ${engines.node}`) + } +} + +/** + * Extracts data from a packet buffer. + * + * @param {Buffer} buffer - The packet buffer to extract data from. + * @param {Buffer|null} encryptKey - The encryption key. + * @param {number} count - Current count value. + * @returns {Buffer} - Extracted data. + * @throws {Error} - Throws an error if the packet format is invalid, CRC check fails, or decryption encounters an error. + */ +function extractPacketData(buffer, encryptKey, count) { + if (buffer[0] !== STX) { + throw new Error('Unknown response') + } + + buffer = buffer.subarray(1) + const dataLength = buffer[1] + const packetData = buffer.subarray(2, dataLength + 2) + const crcData = buffer.subarray(0, dataLength + 2) + const CRC = CRC16(crcData) + + const crcCheck = buffer.subarray(-2) + if (!CRC.equals(crcCheck)) { + throw new Error('Wrong CRC16') + } + + let extractedData = packetData + + if (encryptKey !== null && packetData[0] === STEX) { + const decryptedData = decrypt(encryptKey, Buffer.from(packetData.subarray(1))) + + debug('Decrypted:', chalk.red(Buffer.from(decryptedData).toString('hex'))) + + const eLength = decryptedData[0] + const eCount = Buffer.from(decryptedData.subarray(1, 5)).readUInt32LE() + extractedData = decryptedData.subarray(5, eLength + 5) + + if (eCount !== count + 1) { + throw new Error('Encrypted counter mismatch') + } + } + + return extractedData +} + +/** + * Generates cryptographic keys for a secure communication protocol. + * @returns {Object} An object containing the generated keys: + * - generator: The generator value for the keys. + * - modulus: The modulus value for the keys. + * - hostRandom: A random value generated for the host. + * - hostInter: Intermediate value computed for the host. + * @throws {Error} Throws an error if either generator or modulus is zero. + */ +function generateKeys() { + // Generate a prime number for the generator and modulus with specified bit length. + let generator = crypto.generatePrimeSync(16, { bigint: true, safe: true }) + let modulus = crypto.generatePrimeSync(16, { bigint: true, safe: true }) + + // Check if either generator or modulus is zero, and throw an error if so. + if (generator === 0n || modulus === 0n) { + throw new Error('GENERATOR and MODULUS should be > 0') + } + + // If generator is less than modulus, swap their values. + if (generator < modulus) { + ;[modulus, generator] = [generator, modulus] + } + + // Generate a random number for the host and calculate hostInter using modular exponentiation. + let hostRandom = BigInt(crypto.randomBytes(2).readUInt16LE()) % 2147483648n + let hostInter = generator ** hostRandom % modulus + + // Return the generated keys as an object. + return { + generator, + modulus, + hostRandom, + hostInter, + } +} + +/** + * Constructs a packet based on the provided parameters. + * @param {string} command - The command name. + * @param {Buffer} argBytes - The buffer containing arguments. + * @param {number} sequence - The sequence number. + * @param {Buffer|null} encryptKey - The encryption key. Defaults to null if not provided. + * @param {number} eCount - The encryption count. + * @returns {Buffer} - The constructed packet. + */ +function getPacket(command, argBytes, sequence, encryptKey = null, eCount) { + if (commandList[command].args && argBytes.length === 0) { + throw new Error('Args missings') + } + + if (commandList[command].encrypted && encryptKey === null) { + throw new Error('Command requires ecnryption') + } + + const SEQ_SLAVE_ID = sequence + let DATA = [commandList[command].code, ...argBytes] + + // Encrypted packet + if (encryptKey !== null) { + const eCOUNT = Buffer.alloc(4) + eCOUNT.writeUInt32LE(eCount, 0) + + /** + + * Random data to make the length of the length + count + data + packing + CRCL + CRCH to be a multiple of 16 bytes + * 7 = DATA.length (1 byte) + eCOUNT (4 bytes) + eCRCL (1 byte) + eCRCH (1 byte) + */ + // const ePACKING = crypto.randomBytes(Math.ceil((DATA.length + 7) / 16) * 16 - (DATA.length + 7)) + const ePACKING = crypto.randomBytes((16 - ((DATA.length + 7) % 16)) % 16) + + // data to calculate CRC on + const crcPacket = [DATA.length, ...eCOUNT, ...DATA, ...ePACKING] + const ENCRYPTED_DATA = encrypt(encryptKey, Buffer.from([...crcPacket, ...CRC16(crcPacket)])) + + DATA = [STEX, ...ENCRYPTED_DATA] + } + + // data to calculate CRC on + const crcPacket = [SEQ_SLAVE_ID, DATA.length, ...DATA] + const PACKET = [...crcPacket, ...CRC16(crcPacket)] + + const STUFFED_PACKET = Buffer.concat([Buffer.from([STX]), stuffBuffer(PACKET)]) + + return STUFFED_PACKET +} + +/** + * Creates a Secure Session Protocol (SSP) host encryption key. + * @param {Buffer} buffer - The buffer containing slave inter key. + * @param {object} keys - An object containing fixedKey, hostRandom, and modulus. + * @param {string} keys.fixedKey - The fixed key in hexadecimal format. + * @param {bigint} keys.hostRandom - The host random value. + * @param {bigint} keys.modulus - The modulus value. + * @returns {object} An object containing slaveInterKey, key, and encryptKey. + */ +function createSSPHostEncryptionKey(buffer, keys) { + const { fixedKey, hostRandom, modulus } = keys + const slaveInterKey = Buffer.from(buffer).readBigInt64LE() + const key = slaveInterKey ** hostRandom % modulus + const encryptKey = Buffer.concat([Buffer.from(fixedKey, 'hex').swap64(), uInt64LE(key)]) + + return { + slaveInterKey, + key, + encryptKey, + } +} + +module.exports = { + absBigInt, + argsToByte, + CRC16, + createSSPHostEncryptionKey, + decrypt, + encrypt, + extractPacketData, + generateKeys, + getPacket, + parseData, + randomInt, + readBytesFromBuffer, + stuffBuffer, + uInt16LE, + uInt32LE, + uInt64LE, + validateNodeVersion, +} diff --git a/lib/utils.parseData.test.js b/lib/utils.parseData.test.js new file mode 100644 index 0000000..59316e1 --- /dev/null +++ b/lib/utils.parseData.test.js @@ -0,0 +1,4362 @@ +const { parseData } = require('./utils') + +describe('parseData', () => { + describe('GENERIC responses', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'SYNC', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SYNC', + info: {}, + status: 'OK', + success: true, + }) + }) + + test('UNDEFINED STATUS', () => { + const data = [0xff] + const result = parseData(data, 'SYNC', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SYNC', + info: {}, + status: 'UNDEFINED', + success: false, + }) + }) + + test('COMMAND_NOT_KNOWN', () => { + const data = [0xf2] + const result = parseData(data, 'SYNC', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SYNC', + info: {}, + status: 'COMMAND_NOT_KNOWN', + success: false, + }) + }) + + test('WRONG_NO_PARAMETERS', () => { + const data = [0xf3] + const result = parseData(data, 'SYNC', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SYNC', + info: {}, + status: 'WRONG_NO_PARAMETERS', + success: false, + }) + }) + + test('PARAMETER_OUT_OF_RANGE', () => { + const data = [0xf4] + const result = parseData(data, 'SYNC', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SYNC', + info: {}, + status: 'PARAMETER_OUT_OF_RANGE', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED', () => { + const data = [0xf5] + const result = parseData(data, 'SYNC', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SYNC', + info: {}, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('SOFTWARE_ERROR', () => { + const data = [0xf6] + const result = parseData(data, 'SYNC', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SYNC', + info: {}, + status: 'SOFTWARE_ERROR', + success: false, + }) + }) + + test('FAIL', () => { + const data = [0xf8] + const result = parseData(data, 'SYNC', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SYNC', + info: {}, + status: 'FAIL', + success: false, + }) + }) + + test('KEY_NOT_SET', () => { + const data = [0xfa] + const result = parseData(data, 'SYNC', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SYNC', + info: {}, + status: 'KEY_NOT_SET', + success: false, + }) + }) + }) + + describe('RESET_FIXED_ENCRYPTION_KEY', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'RESET_FIXED_ENCRYPTION_KEY', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'RESET_FIXED_ENCRYPTION_KEY', + info: {}, + status: 'OK', + success: true, + }) + }) + }) + + describe('SET_FIXED_ENCRYPTION_KEY', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'SET_FIXED_ENCRYPTION_KEY', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_FIXED_ENCRYPTION_KEY', + info: {}, + status: 'OK', + success: true, + }) + }) + }) + + describe('ENABLE_PAYOUT_DEVICE', () => { + test('No device connected', () => { + const data = [0xf5, 0x01] + const result = parseData(data, 'ENABLE_PAYOUT_DEVICE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'ENABLE_PAYOUT_DEVICE', + info: { + error: 'No device connected', + errorCode: 1, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('Invalid currency detected', () => { + const data = [0xf5, 0x02] + const result = parseData(data, 'ENABLE_PAYOUT_DEVICE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'ENABLE_PAYOUT_DEVICE', + info: { + error: 'Invalid currency detected', + errorCode: 2, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('Device busy', () => { + const data = [0xf5, 0x03] + const result = parseData(data, 'ENABLE_PAYOUT_DEVICE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'ENABLE_PAYOUT_DEVICE', + info: { + error: 'Device busy', + errorCode: 3, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('Empty only (Note float only)', () => { + const data = [0xf5, 0x04] + const result = parseData(data, 'ENABLE_PAYOUT_DEVICE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'ENABLE_PAYOUT_DEVICE', + info: { + error: 'Empty only (Note float only)', + errorCode: 4, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('Device error', () => { + const data = [0xf5, 0x05] + const result = parseData(data, 'ENABLE_PAYOUT_DEVICE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'ENABLE_PAYOUT_DEVICE', + info: { + error: 'Device error', + errorCode: 5, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('Unknown error', () => { + const data = [0xf5, 0xff] + const result = parseData(data, 'ENABLE_PAYOUT_DEVICE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'ENABLE_PAYOUT_DEVICE', + info: { + error: 'Unknown error', + errorCode: 255, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + }) + + describe('DISABLE_PAYOUT_DEVICE', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'DISABLE_PAYOUT_DEVICE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'DISABLE_PAYOUT_DEVICE', + info: {}, + status: 'OK', + success: true, + }) + }) + }) + + describe('COIN_MECH_OPTONS', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'COIN_MECH_OPTONS', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'COIN_MECH_OPTONS', + info: {}, + status: 'OK', + success: true, + }) + }) + }) + + describe('RESET_COUNTERS', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'RESET_COUNTERS', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'RESET_COUNTERS', + info: {}, + status: 'OK', + success: true, + }) + }) + }) + + describe('GET_COUNTERS', () => { + test('OK', () => { + const data = [ + 0xf0, 0x05, 0x2c, 0x01, 0x00, 0x00, 0xd2, 0x00, 0x00, 0x00, 0xb4, 0x00, 0x00, 0x00, 0x68, 0x01, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, + ] + const result = parseData(data, 'GET_COUNTERS', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_COUNTERS', + info: { + dispensed: 180, + rejected: 25, + stacked: 300, + stored: 210, + transferred_from_store_to_stacker: 360, + }, + status: 'OK', + success: true, + }) + }) + }) + + describe('EVENT_ACK', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'EVENT_ACK', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'EVENT_ACK', + info: {}, + status: 'OK', + success: true, + }) + }) + }) + + describe('POLL_WITH_ACK', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'POLL_WITH_ACK', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL_WITH_ACK', + info: [], + status: 'OK', + success: true, + }) + }) + }) + + describe('CONFIGURE_BEZEL', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'CONFIGURE_BEZEL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'CONFIGURE_BEZEL', + info: {}, + status: 'OK', + success: true, + }) + }) + + test('COMMAND_NOT_KNOWN', () => { + const data = [0xf2] + const result = parseData(data, 'CONFIGURE_BEZEL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'CONFIGURE_BEZEL', + info: {}, + status: 'COMMAND_NOT_KNOWN', + success: false, + }) + }) + }) + + describe('CASHBOX_PAYOUT_OPERATION_DATA', () => { + test('OK', () => { + const data = [ + 240, 7, 0, 0, 100, 0, 0, 0, 85, 83, 68, 0, 0, 200, 0, 0, 0, 85, 83, 68, 1, 0, 244, 1, 0, 0, 85, 83, 68, 0, 0, 232, 3, 0, 0, 85, 83, 68, 0, 0, + 208, 7, 0, 0, 85, 83, 68, 0, 0, 136, 19, 0, 0, 85, 83, 68, 0, 0, 16, 39, 0, 0, 85, 83, 68, 0, 0, 0, 0, + ] + const result = parseData(data, 'CASHBOX_PAYOUT_OPERATION_DATA', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'CASHBOX_PAYOUT_OPERATION_DATA', + info: { + data: [ + { + country_code: 'USD', + quantity: 0, + value: 100, + }, + { + country_code: 'USD', + quantity: 0, + value: 200, + }, + { + country_code: 'USD', + quantity: 1, + value: 500, + }, + { + country_code: 'USD', + quantity: 0, + value: 1000, + }, + { + country_code: 'USD', + quantity: 0, + value: 2000, + }, + { + country_code: 'USD', + quantity: 0, + value: 5000, + }, + { + country_code: 'USD', + quantity: 0, + value: 10000, + }, + ], + }, + status: 'OK', + success: true, + }) + }) + }) + + describe('SMART_EMPTY', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'SMART_EMPTY', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SMART_EMPTY', + info: {}, + status: 'OK', + success: true, + }) + }) + }) + + describe('GET_HOPPER_OPTIONS', () => { + test('OK', () => { + const data = [0xf0, 0x04, 0x00] + const result = parseData(data, 'GET_HOPPER_OPTIONS', 6, 'Smart Hopper') + + expect(result).toEqual({ + command: 'GET_HOPPER_OPTIONS', + info: { + cashBoxPayAcive: false, + levelCheck: false, + motorSpeed: true, + payMode: false, + }, + status: 'OK', + success: true, + }) + }) + }) + + describe('GET_BUILD_REVISION', () => { + test('NV200', () => { + const data = [0xf0, 0x00, 0x14, 0x00, 0x06, 0x15, 0x00] + const result = parseData(data, 'GET_BUILD_REVISION', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_BUILD_REVISION', + info: { + device: { + 0: { + revision: 20, + unitType: 'Banknote validator', + }, + 1: { + revision: 21, + unitType: 'SMART payout fitted', + }, + }, + }, + status: 'OK', + success: true, + }) + }) + + test('NV11', () => { + const data = [0xf0, 0x00, 0x00, 0x00, 0x03, 0x08, 0x00] + const result = parseData(data, 'GET_BUILD_REVISION', 6, 'Note Float fitted') + + expect(result).toEqual({ + command: 'GET_BUILD_REVISION', + info: { + device: { + 0: { + revision: 0, + unitType: 'Banknote validator', + }, + 1: { + revision: 8, + unitType: 'Smart Hopper', + }, + }, + }, + status: 'OK', + success: true, + }) + }) + }) + + describe('SET_BAUD_RATE', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'SET_BAUD_RATE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_BAUD_RATE', + info: {}, + status: 'OK', + success: true, + }) + }) + + test('COMMAND_NOT_KNOWN', () => { + const data = [0xf2] + const result = parseData(data, 'SET_BAUD_RATE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_BAUD_RATE', + info: {}, + status: 'COMMAND_NOT_KNOWN', + success: false, + }) + }) + }) + + describe('REQUEST_KEY_EXCHANGE', () => { + test('OK', () => { + const data = [0xf0, 0xcb, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + const result = parseData(data, 'REQUEST_KEY_EXCHANGE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'REQUEST_KEY_EXCHANGE', + info: { + key: [203, 225, 0, 0, 0, 0, 0, 0], + }, + status: 'OK', + success: true, + }) + }) + + test('FAIL', () => { + const data = [0xf8] + const result = parseData(data, 'REQUEST_KEY_EXCHANGE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'REQUEST_KEY_EXCHANGE', + info: {}, + status: 'FAIL', + success: false, + }) + }) + }) + + describe('SET_MODULUS', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'SET_MODULUS', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_MODULUS', + info: {}, + status: 'OK', + success: true, + }) + }) + + test('PARAMETER_OUT_OF_RANGE', () => { + const data = [0xf4] + const result = parseData(data, 'SET_MODULUS', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_MODULUS', + info: {}, + status: 'PARAMETER_OUT_OF_RANGE', + success: false, + }) + }) + }) + + describe('SET_GENERATOR', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'SET_GENERATOR', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_GENERATOR', + info: {}, + status: 'OK', + success: true, + }) + }) + + test('PARAMETER_OUT_OF_RANGE', () => { + const data = [0xf4] + const result = parseData(data, 'SET_GENERATOR', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_GENERATOR', + info: {}, + status: 'PARAMETER_OUT_OF_RANGE', + success: false, + }) + }) + }) + + describe('SET_COIN_MECH_GLOBAL_INHIBIT', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'SET_COIN_MECH_GLOBAL_INHIBIT', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_COIN_MECH_GLOBAL_INHIBIT', + info: {}, + status: 'OK', + success: true, + }) + }) + }) + + describe('PAYOUT_BY_DENOMINATION', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'PAYOUT_BY_DENOMINATION', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'PAYOUT_BY_DENOMINATION', + info: {}, + status: 'OK', + success: true, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Not enough value in device', () => { + const data = [0xf5, 0x00] + const result = parseData(data, 'PAYOUT_BY_DENOMINATION', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'PAYOUT_BY_DENOMINATION', + info: { + error: 'Not enough value in device', + errorCode: 0, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Cannot pay exact amount', () => { + const data = [0xf5, 0x01] + const result = parseData(data, 'PAYOUT_BY_DENOMINATION', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'PAYOUT_BY_DENOMINATION', + info: { + error: 'Cannot pay exact amount', + errorCode: 1, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Device busy', () => { + const data = [0xf5, 0x03] + const result = parseData(data, 'PAYOUT_BY_DENOMINATION', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'PAYOUT_BY_DENOMINATION', + info: { + error: 'Device busy', + errorCode: 3, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Device disabled', () => { + const data = [0xf5, 0x04] + const result = parseData(data, 'PAYOUT_BY_DENOMINATION', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'PAYOUT_BY_DENOMINATION', + info: { + error: 'Device disabled', + errorCode: 4, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Unknown error', () => { + const data = [0xf5, 0x02] + const result = parseData(data, 'PAYOUT_BY_DENOMINATION', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'PAYOUT_BY_DENOMINATION', + info: { + error: 'Unknown error', + errorCode: 2, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + }) + + describe('SET_VALUE_REPORTING_TYPE', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'SET_VALUE_REPORTING_TYPE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_VALUE_REPORTING_TYPE', + info: {}, + status: 'OK', + success: true, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: No payout connected', () => { + const data = [0xf5, 0x01] + const result = parseData(data, 'SET_VALUE_REPORTING_TYPE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_VALUE_REPORTING_TYPE', + info: { + error: 'No payout connected', + errorCode: 1, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Invalid currency detected', () => { + const data = [0xf5, 0x02] + const result = parseData(data, 'SET_VALUE_REPORTING_TYPE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_VALUE_REPORTING_TYPE', + info: { + error: 'Invalid currency detected', + errorCode: 2, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Payout device error', () => { + const data = [0xf5, 0x03] + const result = parseData(data, 'SET_VALUE_REPORTING_TYPE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_VALUE_REPORTING_TYPE', + info: { + error: 'Payout device error', + errorCode: 3, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + }) + + describe('FLOAT_BY_DENOMINATION', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'FLOAT_BY_DENOMINATION', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'FLOAT_BY_DENOMINATION', + info: {}, + status: 'OK', + success: true, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Not enough value in device', () => { + const data = [0xf5, 0x00] + const result = parseData(data, 'FLOAT_BY_DENOMINATION', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'FLOAT_BY_DENOMINATION', + info: { + error: 'Not enough value in device', + errorCode: 0, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Cannot pay exact amount', () => { + const data = [0xf5, 0x01] + const result = parseData(data, 'FLOAT_BY_DENOMINATION', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'FLOAT_BY_DENOMINATION', + info: { + error: 'Cannot pay exact amount', + errorCode: 1, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Device busy', () => { + const data = [0xf5, 0x03] + const result = parseData(data, 'FLOAT_BY_DENOMINATION', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'FLOAT_BY_DENOMINATION', + info: { + error: 'Device busy', + errorCode: 3, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Device disabled', () => { + const data = [0xf5, 0x04] + const result = parseData(data, 'FLOAT_BY_DENOMINATION', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'FLOAT_BY_DENOMINATION', + info: { + error: 'Device disabled', + errorCode: 4, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Unknown error', () => { + const data = [0xf5, 0x02] + const result = parseData(data, 'FLOAT_BY_DENOMINATION', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'FLOAT_BY_DENOMINATION', + info: { + error: 'Unknown error', + errorCode: 2, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + }) + + describe('STACK_NOTE', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'STACK_NOTE', 6, 'Note Float fitted') + + expect(result).toEqual({ + command: 'STACK_NOTE', + info: {}, + status: 'OK', + success: true, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Note float unit not connected', () => { + const data = [0xf5, 0x01] + const result = parseData(data, 'STACK_NOTE', 6, 'Note Float fitted') + + expect(result).toEqual({ + command: 'STACK_NOTE', + info: { + error: 'Note float unit not connected', + errorCode: 1, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Note float empty', () => { + const data = [0xf5, 0x02] + const result = parseData(data, 'STACK_NOTE', 6, 'Note Float fitted') + + expect(result).toEqual({ + command: 'STACK_NOTE', + info: { + error: 'Note float empty', + errorCode: 2, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Note float busy', () => { + const data = [0xf5, 0x03] + const result = parseData(data, 'STACK_NOTE', 6, 'Note Float fitted') + + expect(result).toEqual({ + command: 'STACK_NOTE', + info: { + error: 'Note float busy', + errorCode: 3, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Note float disabled', () => { + const data = [0xf5, 0x04] + const result = parseData(data, 'STACK_NOTE', 6, 'Note Float fitted') + + expect(result).toEqual({ + command: 'STACK_NOTE', + info: { + error: 'Note float disabled', + errorCode: 4, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Unknown error', () => { + const data = [0xf5, 0xff] + const result = parseData(data, 'STACK_NOTE', 6, 'Note Float fitted') + + expect(result).toEqual({ + command: 'STACK_NOTE', + info: { + error: 'Unknown error', + errorCode: 255, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + }) + + describe('PAYOUT_NOTE', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'PAYOUT_NOTE', 6, 'Note Float fitted') + + expect(result).toEqual({ + command: 'PAYOUT_NOTE', + info: {}, + status: 'OK', + success: true, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Note float unit not connected', () => { + const data = [0xf5, 0x01] + const result = parseData(data, 'PAYOUT_NOTE', 6, 'Note Float fitted') + + expect(result).toEqual({ + command: 'PAYOUT_NOTE', + info: { + error: 'Note float unit not connected', + errorCode: 1, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Note float empty', () => { + const data = [0xf5, 0x02] + const result = parseData(data, 'PAYOUT_NOTE', 6, 'Note Float fitted') + + expect(result).toEqual({ + command: 'PAYOUT_NOTE', + info: { + error: 'Note float empty', + errorCode: 2, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Note float busy', () => { + const data = [0xf5, 0x03] + const result = parseData(data, 'PAYOUT_NOTE', 6, 'Note Float fitted') + + expect(result).toEqual({ + command: 'PAYOUT_NOTE', + info: { + error: 'Note float busy', + errorCode: 3, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Note float disabled', () => { + const data = [0xf5, 0x04] + const result = parseData(data, 'PAYOUT_NOTE', 6, 'Note Float fitted') + + expect(result).toEqual({ + command: 'PAYOUT_NOTE', + info: { + error: 'Note float disabled', + errorCode: 4, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + }) + + describe('GET_NOTE_POSITIONS', () => { + test('OK: Report by value', () => { + const data = [0xf0, 0x02, 0xf4, 0x01, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00] + const result = parseData(data, 'GET_NOTE_POSITIONS', 6, 'Note Float fitted') + + expect(result).toEqual({ + command: 'GET_NOTE_POSITIONS', + info: { + slot: { + 1: { + value: 500, + }, + 2: { + value: 1000, + }, + }, + }, + status: 'OK', + success: true, + }) + }) + + test('OK: Report by channel', () => { + const data = [0xf0, 0x02, 0x01, 0x02] + const result = parseData(data, 'GET_NOTE_POSITIONS', 6, 'Note Float fitted') + + expect(result).toEqual({ + command: 'GET_NOTE_POSITIONS', + info: { + slot: { + 1: { + channel: 1, + }, + 2: { + channel: 2, + }, + }, + }, + status: 'OK', + success: true, + }) + }) + + test('ERROR: 1', () => { + const data = [0xf5, 0x01] + + const result = parseData(data, 'GET_NOTE_POSITIONS', 6, 'Note Float fitted') + expect(result).toEqual({ + command: 'GET_NOTE_POSITIONS', + info: { + errorCode: 1, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('ERROR: 2', () => { + const data = [0xf5, 0x02] + + const result = parseData(data, 'GET_NOTE_POSITIONS', 6, 'Note Float fitted') + expect(result).toEqual({ + command: 'GET_NOTE_POSITIONS', + info: { + error: 'Invalid currency', + errorCode: 2, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + }) + + describe('SET_COIN_MECH_INHIBITS', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'SET_COIN_MECH_INHIBITS', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_COIN_MECH_INHIBITS', + info: {}, + status: 'OK', + success: true, + }) + }) + }) + + describe('EMPTY_ALL response', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'EMPTY_ALL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'EMPTY_ALL', + info: {}, + status: 'OK', + success: true, + }) + }) + }) + + describe('GET_MINIMUM_PAYOUT', () => { + test('OK', () => { + const data = [0xf0, 0xc8, 0x00, 0x00, 0x00] + const result = parseData(data, 'GET_MINIMUM_PAYOUT', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_MINIMUM_PAYOUT', + info: { + value: 200, + }, + status: 'OK', + success: true, + }) + }) + }) + + describe('FLOAT_AMOUNT', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'FLOAT_AMOUNT', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'FLOAT_AMOUNT', + info: {}, + status: 'OK', + success: true, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Not enough value in device', () => { + const data = [0xf5, 0x00] + const result = parseData(data, 'FLOAT_AMOUNT', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'FLOAT_AMOUNT', + info: { + error: 'Not enough value in device', + errorCode: 0, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Cannot pay exact amount', () => { + const data = [0xf5, 0x01] + const result = parseData(data, 'FLOAT_AMOUNT', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'FLOAT_AMOUNT', + info: { + error: 'Cannot pay exact amount', + errorCode: 1, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Unknown error', () => { + const data = [0xf5, 0x02] + const result = parseData(data, 'FLOAT_AMOUNT', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'FLOAT_AMOUNT', + info: { + error: 'Unknown error', + errorCode: 2, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Device busy', () => { + const data = [0xf5, 0x03] + const result = parseData(data, 'FLOAT_AMOUNT', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'FLOAT_AMOUNT', + info: { + error: 'Device busy', + errorCode: 3, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Device disabled', () => { + const data = [0xf5, 0x04] + const result = parseData(data, 'FLOAT_AMOUNT', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'FLOAT_AMOUNT', + info: { + error: 'Device disabled', + errorCode: 4, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + }) + + describe('GET_DENOMINATION_ROUTE', () => { + test('OK: Recycled and used for payouts', () => { + const data = [0xf0, 0x00] + const result = parseData(data, 'GET_DENOMINATION_ROUTE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_DENOMINATION_ROUTE', + info: { + code: 0, + value: 'Recycled and used for payouts', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: Detected denomination is routed to system cashbox', () => { + const data = [0xf0, 0x01] + const result = parseData(data, 'GET_DENOMINATION_ROUTE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_DENOMINATION_ROUTE', + info: { + code: 1, + value: 'Detected denomination is routed to system cashbox', + }, + status: 'OK', + success: true, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: No payout connected', () => { + const data = [0xf5, 0x01] + const result = parseData(data, 'GET_DENOMINATION_ROUTE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_DENOMINATION_ROUTE', + info: { + errorCode: 1, + error: 'No payout connected', + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Invalid currency detected', () => { + const data = [0xf5, 0x02] + const result = parseData(data, 'GET_DENOMINATION_ROUTE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_DENOMINATION_ROUTE', + info: { + errorCode: 2, + error: 'Invalid currency detected', + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Payout device error', () => { + const data = [0xf5, 0x03] + const result = parseData(data, 'GET_DENOMINATION_ROUTE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_DENOMINATION_ROUTE', + info: { + errorCode: 3, + error: 'Payout device error', + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Unknown error', () => { + const data = [0xf5, 0xff] + const result = parseData(data, 'GET_DENOMINATION_ROUTE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_DENOMINATION_ROUTE', + info: { + errorCode: 255, + error: 'Unknown error', + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('PARAMETER_OUT_OF_RANGE', () => { + const data = [0xf4] + const result = parseData(data, 'GET_DENOMINATION_ROUTE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_DENOMINATION_ROUTE', + info: {}, + status: 'PARAMETER_OUT_OF_RANGE', + success: false, + }) + }) + }) + + describe('SET_DENOMINATION_ROUTE', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'SET_DENOMINATION_ROUTE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_DENOMINATION_ROUTE', + info: {}, + status: 'OK', + success: true, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: No payout connected', () => { + const data = [0xf5, 0x01] + const result = parseData(data, 'SET_DENOMINATION_ROUTE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_DENOMINATION_ROUTE', + info: { + errorCode: 1, + error: 'No payout connected', + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Invalid currency detected', () => { + const data = [0xf5, 0x02] + const result = parseData(data, 'SET_DENOMINATION_ROUTE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_DENOMINATION_ROUTE', + info: { + errorCode: 2, + error: 'Invalid currency detected', + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Payout device error', () => { + const data = [0xf5, 0x03] + const result = parseData(data, 'SET_DENOMINATION_ROUTE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_DENOMINATION_ROUTE', + info: { + errorCode: 3, + error: 'Payout device error', + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('PARAMETER_OUT_OF_RANGE', () => { + const data = [0xf4] + const result = parseData(data, 'SET_DENOMINATION_ROUTE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_DENOMINATION_ROUTE', + info: {}, + status: 'PARAMETER_OUT_OF_RANGE', + success: false, + }) + }) + }) + + describe('HALT_PAYOUT', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'HALT_PAYOUT', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'HALT_PAYOUT', + info: {}, + status: 'OK', + success: true, + }) + }) + }) + + describe('COMMUNICATION_PASS_THROUGH', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'COMMUNICATION_PASS_THROUGH', 6, 'Smart Hopper') + + expect(result).toEqual({ + command: 'COMMUNICATION_PASS_THROUGH', + info: {}, + status: 'OK', + success: true, + }) + }) + }) + + describe('GET_DENOMINATION_LEVEL', () => { + test('OK', () => { + const data = [0xf0, 0xc8, 0x00] + const result = parseData(data, 'GET_DENOMINATION_LEVEL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_DENOMINATION_LEVEL', + info: { + level: 200, + }, + status: 'OK', + success: true, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED', () => { + const data = [0xf5] + const result = parseData(data, 'GET_DENOMINATION_LEVEL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_DENOMINATION_LEVEL', + info: {}, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + }) + + describe('SET_DENOMINATION_LEVEL', () => { + test('OK', () => { + const data = [0xf0, 0xc8, 0x00] + const result = parseData(data, 'SET_DENOMINATION_LEVEL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_DENOMINATION_LEVEL', + info: {}, + status: 'OK', + success: true, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED', () => { + const data = [0xf5] + const result = parseData(data, 'SET_DENOMINATION_LEVEL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_DENOMINATION_LEVEL', + info: {}, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + }) + + describe('PAYOUT_AMOUNT', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'PAYOUT_AMOUNT', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'PAYOUT_AMOUNT', + info: {}, + status: 'OK', + success: true, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Not enough value in device', () => { + const data = [0xf5, 0x00] + const result = parseData(data, 'PAYOUT_AMOUNT', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'PAYOUT_AMOUNT', + info: { + error: 'Not enough value in device', + errorCode: 0, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Cannot pay exact amount', () => { + const data = [0xf5, 0x01] + const result = parseData(data, 'PAYOUT_AMOUNT', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'PAYOUT_AMOUNT', + info: { + error: 'Cannot pay exact amount', + errorCode: 1, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Unknown error', () => { + const data = [0xf5, 0x02] + const result = parseData(data, 'PAYOUT_AMOUNT', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'PAYOUT_AMOUNT', + info: { + error: 'Unknown error', + errorCode: 2, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Device busy', () => { + const data = [0xf5, 0x03] + const result = parseData(data, 'PAYOUT_AMOUNT', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'PAYOUT_AMOUNT', + info: { + error: 'Device busy', + errorCode: 3, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED: Device disabled', () => { + const data = [0xf5, 0x04] + const result = parseData(data, 'PAYOUT_AMOUNT', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'PAYOUT_AMOUNT', + info: { + error: 'Device disabled', + errorCode: 4, + }, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + }) + + describe('SET_REFILL_MODE', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'SET_REFILL_MODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_REFILL_MODE', + info: {}, + status: 'OK', + success: true, + }) + }) + + test('OK: read mode', () => { + const data = [0xf0, 0x01] + const result = parseData(data, 'SET_REFILL_MODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_REFILL_MODE', + info: { + enabled: true, + }, + status: 'OK', + success: true, + }) + }) + }) + + describe('GET_BAR_CODE_DATA', () => { + test('OK: ticket_in_escrow', () => { + const data = [0xf0, 0x01, 0x06, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36] + const result = parseData(data, 'GET_BAR_CODE_DATA', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_BAR_CODE_DATA', + info: { + data: '123456', + status: 'ticket_in_escrow', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: ticket_stacked', () => { + const data = [0xf0, 0x02, 0x06, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36] + const result = parseData(data, 'GET_BAR_CODE_DATA', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_BAR_CODE_DATA', + info: { + data: '123456', + status: 'ticket_stacked', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: ticket_rejected', () => { + const data = [0xf0, 0x03, 0x06, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36] + const result = parseData(data, 'GET_BAR_CODE_DATA', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_BAR_CODE_DATA', + info: { + data: '123456', + status: 'ticket_rejected', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: no_valid_data', () => { + const data = [0xf0, 0x00, 0x00] + const result = parseData(data, 'GET_BAR_CODE_DATA', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_BAR_CODE_DATA', + info: { + data: '', + status: 'no_valid_data', + }, + status: 'OK', + success: true, + }) + }) + }) + + describe('SET_BAR_CODE_INHIBIT_STATUS', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'SET_BAR_CODE_INHIBIT_STATUS', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_BAR_CODE_INHIBIT_STATUS', + info: {}, + status: 'OK', + success: true, + }) + }) + }) + + describe('GET_BAR_CODE_INHIBIT_STATUS', () => { + test('OK: enabled', () => { + const data = [0xf0, 0xfc] + const result = parseData(data, 'GET_BAR_CODE_INHIBIT_STATUS', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_BAR_CODE_INHIBIT_STATUS', + info: { + bar_code_enable: true, + currency_read_enable: true, + }, + status: 'OK', + success: true, + }) + }) + + test('OK: disabled', () => { + const data = [0xf0, 0xff] + const result = parseData(data, 'GET_BAR_CODE_INHIBIT_STATUS', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_BAR_CODE_INHIBIT_STATUS', + info: { + bar_code_enable: false, + currency_read_enable: false, + }, + status: 'OK', + success: true, + }) + }) + }) + + describe('SET_BAR_CODE_CONFIGURATION', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'SET_BAR_CODE_CONFIGURATION', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_BAR_CODE_CONFIGURATION', + info: {}, + status: 'OK', + success: true, + }) + }) + }) + + describe('GET_BAR_CODE_READER_CONFIGURATION', () => { + test('OK: none', () => { + const data = [0xf0, 0x00, 0x00, 0x01, 0x06] + const result = parseData(data, 'GET_BAR_CODE_READER_CONFIGURATION', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_BAR_CODE_READER_CONFIGURATION', + info: { + bar_code_format: 'Interleaved 2 of 5', + bar_code_hardware_status: 'none', + number_of_characters: 6, + readers_enabled: 'none', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: top', () => { + const data = [0xf0, 0x01, 0x01, 0x01, 0x06] + const result = parseData(data, 'GET_BAR_CODE_READER_CONFIGURATION', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_BAR_CODE_READER_CONFIGURATION', + info: { + bar_code_format: 'Interleaved 2 of 5', + bar_code_hardware_status: 'Top reader fitted', + number_of_characters: 6, + readers_enabled: 'top', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: bottom', () => { + const data = [0xf0, 0x02, 0x02, 0x01, 0x06] + const result = parseData(data, 'GET_BAR_CODE_READER_CONFIGURATION', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_BAR_CODE_READER_CONFIGURATION', + info: { + bar_code_format: 'Interleaved 2 of 5', + bar_code_hardware_status: 'Bottom reader fitted', + number_of_characters: 6, + readers_enabled: 'bottom', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: both', () => { + const data = [0xf0, 0x03, 0x03, 0x01, 0x06] + const result = parseData(data, 'GET_BAR_CODE_READER_CONFIGURATION', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_BAR_CODE_READER_CONFIGURATION', + info: { + bar_code_format: 'Interleaved 2 of 5', + bar_code_hardware_status: 'both fitted', + number_of_characters: 6, + readers_enabled: 'both', + }, + status: 'OK', + success: true, + }) + }) + }) + + describe('GET_ALL_LEVELS', () => { + test('OK', () => { + const data = [ + 0xf0, 0x04, 0x64, 0x00, 0x14, 0x00, 0x00, 0x00, 0x45, 0x55, 0x52, 0x41, 0x00, 0x32, 0x00, 0x00, 0x00, 0x45, 0x55, 0x52, 0x00, 0x00, 0x64, + 0x00, 0x00, 0x00, 0x45, 0x55, 0x52, 0x0c, 0x00, 0xc8, 0x00, 0x00, 0x00, 0x45, 0x55, 0x52, 0x84, 0xd0, + ] + const result = parseData(data, 'GET_ALL_LEVELS', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_ALL_LEVELS', + info: { + counter: { + 1: { + country_code: 'EUR', + denomination_level: 100, + value: 20, + }, + 2: { + country_code: 'EUR', + denomination_level: 65, + value: 50, + }, + 3: { + country_code: 'EUR', + denomination_level: 0, + value: 100, + }, + 4: { + country_code: 'EUR', + denomination_level: 12, + value: 200, + }, + }, + }, + status: 'OK', + success: true, + }) + }) + }) + + describe('GET_DATASET_VERSION', () => { + test('OK', () => { + const data = [0xf0, 0x45, 0x55, 0x52, 0x30, 0x31, 0x36, 0x31, 0x30] + const result = parseData(data, 'GET_DATASET_VERSION', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_DATASET_VERSION', + info: { + version: 'EUR01610', + }, + status: 'OK', + success: true, + }) + }) + }) + + describe('GET_FIRMWARE_VERSION', () => { + test('OK', () => { + const data = [0xf0, 0x4e, 0x56, 0x30, 0x32, 0x30, 0x30, 0x34, 0x31, 0x34, 0x31, 0x34, 0x39, 0x38, 0x30, 0x30, 0x30] + const result = parseData(data, 'GET_FIRMWARE_VERSION', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_FIRMWARE_VERSION', + info: { + version: 'NV02004141498000', + }, + status: 'OK', + success: true, + }) + }) + }) + + describe('HOLD', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'HOLD', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'HOLD', + info: {}, + status: 'OK', + success: true, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED', () => { + const data = [0xf5] + const result = parseData(data, 'HOLD', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'HOLD', + info: {}, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + }) + + describe('LAST_REJECT_CODE', () => { + test('OK: NOTE_ACCEPTED', () => { + const data = [0xf0, 0x0] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 0, + description: 'The banknote has been accepted. No reject has occured.', + name: 'NOTE_ACCEPTED', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: LENGTH_FAIL', () => { + const data = [0xf0, 0x1] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 1, + description: "A validation fail: The banknote has been read but it's length registers over the max length parameter.", + name: 'LENGTH_FAIL', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: AVERAGE_FAIL', () => { + const data = [0xf0, 0x2] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 2, + description: 'Internal validation failure - banknote not recognised.', + name: 'AVERAGE_FAIL', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: COASTLINE_FAIL', () => { + const data = [0xf0, 0x3] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 3, + description: 'Internal validation failure - banknote not recognised.', + name: 'COASTLINE_FAIL', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: GRAPH_FAIL', () => { + const data = [0xf0, 0x4] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 4, + description: 'Internal validation failure - banknote not recognised.', + name: 'GRAPH_FAIL', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: BURIED_FAIL', () => { + const data = [0xf0, 0x5] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 5, + description: 'Internal validation failure - banknote not recognised.', + name: 'BURIED_FAIL', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: CHANNEL_INHIBIT', () => { + const data = [0xf0, 0x6] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 6, + description: 'This banknote has been inhibited for acceptance in the dataset configuration.', + name: 'CHANNEL_INHIBIT', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: SECOND_NOTE_DETECTED', () => { + const data = [0xf0, 0x7] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 7, + description: 'A second banknote was inserted into the validator while the first one was still being transported through the banknote path.', + name: 'SECOND_NOTE_DETECTED', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: REJECT_BY_HOST', () => { + const data = [0xf0, 0x8] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 8, + description: 'The host system issues a Reject command when this banknote was held in escrow.', + name: 'REJECT_BY_HOST', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: CROSS_CHANNEL_DETECTED', () => { + const data = [0xf0, 0x9] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 9, + description: 'This bank note was identified as exisiting in two or more seperate channel definitions in the dataset.', + name: 'CROSS_CHANNEL_DETECTED', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: REAR_SENSOR_ERROR', () => { + const data = [0xf0, 0xa] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 10, + description: 'An inconsistency in a position sensor detection was seen', + name: 'REAR_SENSOR_ERROR', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: NOTE_TOO_LONG', () => { + const data = [0xf0, 0xb] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 11, + description: 'The banknote failed dataset length checks.', + name: 'NOTE_TOO_LONG', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: DISABLED_BY_HOST', () => { + const data = [0xf0, 0xc] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 12, + description: 'The bank note was validated on a channel that has been inhibited for acceptance by the host system.', + name: 'DISABLED_BY_HOST', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: SLOW_MECH', () => { + const data = [0xf0, 0xd] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 13, + description: 'The internal mechanism was detected as moving too slowly for correct validation.', + name: 'SLOW_MECH', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: STRIM_ATTEMPT', () => { + const data = [0xf0, 0xe] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 14, + description: 'The internal mechanism was detected as moving too slowly for correct validation.', + name: 'STRIM_ATTEMPT', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: FRAUD_CHANNEL', () => { + const data = [0xf0, 0xf] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 15, + description: 'Obselete response.', + name: 'FRAUD_CHANNEL', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: NO_NOTES_DETECTED', () => { + const data = [0xf0, 0x10] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 16, + description: 'A banknote detection was initiated but no banknotes were seen at the validation section.', + name: 'NO_NOTES_DETECTED', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: PEAK_DETECT_FAIL', () => { + const data = [0xf0, 0x11] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 17, + description: 'Internal validation fail. Banknote not recognised.', + name: 'PEAK_DETECT_FAIL', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: TWISTED_NOTE_REJECT', () => { + const data = [0xf0, 0x12] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 18, + description: 'Internal validation fail. Banknote not recognised.', + name: 'TWISTED_NOTE_REJECT', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: ESCROW_TIME-OUT', () => { + const data = [0xf0, 0x13] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 19, + description: 'A banknote held in escrow was rejected due to the host not communicating within the timeout period.', + name: 'ESCROW_TIME-OUT', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: BAR_CODE_SCAN_FAIL', () => { + const data = [0xf0, 0x14] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 20, + description: 'Internal validation fail. Banknote not recognised.', + name: 'BAR_CODE_SCAN_FAIL', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: NO_CAM_ACTIVATE', () => { + const data = [0xf0, 0x15] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 21, + description: 'A banknote did not reach the internal note path for validation during transport.', + name: 'NO_CAM_ACTIVATE', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: SLOT_FAIL_1', () => { + const data = [0xf0, 0x16] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 22, + description: 'Internal validation fail. Banknote not recognised.', + name: 'SLOT_FAIL_1', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: SLOT_FAIL_2', () => { + const data = [0xf0, 0x17] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 23, + description: 'Internal validation fail. Banknote not recognised.', + name: 'SLOT_FAIL_2', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: LENS_OVERSAMPLE', () => { + const data = [0xf0, 0x18] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 24, + description: 'The banknote was transported faster than the system could sample the note.', + name: 'LENS_OVERSAMPLE', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: WIDTH_DETECTION_FAIL', () => { + const data = [0xf0, 0x19] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 25, + description: 'The banknote failed a measurement test.', + name: 'WIDTH_DETECTION_FAIL', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: SHORT_NOTE_DETECT', () => { + const data = [0xf0, 0x1a] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 26, + description: 'The banknote measured length fell outside of the validation parameter for minimum length.', + name: 'SHORT_NOTE_DETECT', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: PAYOUT_NOTE', () => { + const data = [0xf0, 0x1b] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 27, + description: 'The reject code cammand was issued after a note was payed out using a note payout device.', + name: 'PAYOUT_NOTE', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: DOUBLE_NOTE_DETECTED', () => { + const data = [0xf0, 0x1c] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 28, + description: 'Mote than one banknote was detected as overlayed during note entry.', + name: 'DOUBLE_NOTE_DETECTED', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: UNABLE_TO_STACK', () => { + const data = [0xf0, 0x1d] + const result = parseData(data, 'LAST_REJECT_CODE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'LAST_REJECT_CODE', + info: { + code: 29, + description: "The bank was unable to reach it's correct stacking position during transport", + name: 'UNABLE_TO_STACK', + }, + status: 'OK', + success: true, + }) + }) + }) + + describe('SYNC', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'SYNC', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SYNC', + info: {}, + status: 'OK', + success: true, + }) + }) + }) + + describe('CHANNEL_RE_TEACH_DATA', () => { + test('OK', () => { + const data = [0xf0, 0x00, 0x00, 0x00] + const result = parseData(data, 'CHANNEL_RE_TEACH_DATA', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'CHANNEL_RE_TEACH_DATA', + info: { + source: [0, 0, 0], + }, + status: 'OK', + success: true, + }) + }) + + test('COMMAND_NOT_KNOWN', () => { + const data = [0xf2] + const result = parseData(data, 'CHANNEL_RE_TEACH_DATA', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'CHANNEL_RE_TEACH_DATA', + info: {}, + status: 'COMMAND_NOT_KNOWN', + success: false, + }) + }) + }) + + describe('CHANNEL_SECURITY_DATA', () => { + test('OK', () => { + const data = [0xf0, 0x07, 0x02, 0x02, 0x00, 0x02, 0x00, 0x02, 0x02] + const result = parseData(data, 'CHANNEL_SECURITY_DATA', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'CHANNEL_SECURITY_DATA', + info: { + channel: { + 1: 'std', + 2: 'std', + 3: 'not_implemented', + 4: 'std', + 5: 'not_implemented', + 6: 'std', + 7: 'std', + }, + }, + status: 'OK', + success: true, + }) + }) + }) + + describe('CHANNEL_VALUE_REQUEST', () => { + test('OK: protocol < 6', () => { + const data = [0xf0, 0x07, 0x05, 0x0a, 0x00, 0x14, 0x00, 0x32, 0x64] + const result = parseData(data, 'CHANNEL_VALUE_REQUEST', 5, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'CHANNEL_VALUE_REQUEST', + info: { + channel: [5, 10, 0, 20, 0, 50, 100], + }, + status: 'OK', + success: true, + }) + }) + + test('OK: protocol >= 6', () => { + const data = [ + 240, 7, 1, 2, 5, 10, 20, 50, 100, 85, 83, 68, 85, 83, 68, 85, 83, 68, 85, 83, 68, 85, 83, 68, 85, 83, 68, 85, 83, 68, 1, 0, 0, 0, 2, 0, 0, 0, + 5, 0, 0, 0, 10, 0, 0, 0, 20, 0, 0, 0, 50, 0, 0, 0, 100, 0, 0, 0, + ] + const result = parseData(data, 'CHANNEL_VALUE_REQUEST', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'CHANNEL_VALUE_REQUEST', + info: { + channel: [1, 2, 5, 10, 20, 50, 100], + country_code: ['USD', 'USD', 'USD', 'USD', 'USD', 'USD', 'USD'], + value: [1, 2, 5, 10, 20, 50, 100], + }, + status: 'OK', + success: true, + }) + }) + }) + + describe('UNIT_DATA', () => { + test('OK', () => { + const data = [240, 6, 48, 52, 53, 57, 85, 83, 68, 0, 0, 1, 6] + const result = parseData(data, 'UNIT_DATA', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'UNIT_DATA', + info: { + unit_type: 'SMART payout fitted', + firmware_version: '4.59', + country_code: 'USD', + value_multiplier: 1, + protocol_version: 6, + }, + status: 'OK', + success: true, + }) + }) + }) + + describe('GET_SERIAL_NUMBER', () => { + test('OK', () => { + const data = [240, 0, 74, 120, 180] + const result = parseData(data, 'GET_SERIAL_NUMBER', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'GET_SERIAL_NUMBER', + info: { + serial_number: 4880564, + }, + status: 'OK', + success: true, + }) + }) + }) + + describe('ENABLE', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'ENABLE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'ENABLE', + info: {}, + status: 'OK', + success: true, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED', () => { + const data = [0xf5] + const result = parseData(data, 'ENABLE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'ENABLE', + info: {}, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + }) + + describe('DISABLE', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'DISABLE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'DISABLE', + info: {}, + status: 'OK', + success: true, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED', () => { + const data = [0xf5] + const result = parseData(data, 'DISABLE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'DISABLE', + info: {}, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + }) + + describe('REJECT_BANKNOTE', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'REJECT_BANKNOTE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'REJECT_BANKNOTE', + info: {}, + status: 'OK', + success: true, + }) + }) + + test('COMMAND_CANNOT_BE_PROCESSED', () => { + const data = [0xf5] + const result = parseData(data, 'REJECT_BANKNOTE', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'REJECT_BANKNOTE', + info: {}, + status: 'COMMAND_CANNOT_BE_PROCESSED', + success: false, + }) + }) + }) + + describe('POLL', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [], + status: 'OK', + success: true, + }) + }) + + test('SLAVE_RESET', () => { + const data = [0xf0, 0xf1] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 241, + description: 'The device has undergone a power reset.', + name: 'SLAVE_RESET', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('READ_NOTE', () => { + const data = [0xf0, 0xef, 0x01] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + channel: 1, + code: 239, + description: + 'A note is in the process of being scanned by the device (byte value 0) or a valid note has been scanned and is in escrow (byte value gives the channel number)', + name: 'READ_NOTE', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('CREDIT_NOTE', () => { + const data = [0xf0, 0xee, 0x01] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + channel: 1, + code: 238, + description: + 'A note has passed through the device, past the point of possible recovery and the host can safely issue its credit amount. The byte value is the channel number of the note to credit.', + name: 'CREDIT_NOTE', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('NOTE_REJECTING', () => { + const data = [0xf0, 0xed] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 237, + description: 'The note is in the process of being rejected from the validator', + name: 'NOTE_REJECTING', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('NOTE_REJECTED', () => { + const data = [0xf0, 0xec] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 236, + description: 'The note has been rejected from the validator and is available for the user to retrieve.', + name: 'NOTE_REJECTED', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('NOTE_STACKING', () => { + const data = [0xf0, 0xcc] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 204, + description: 'The note is being moved from the escrow position to the host exit section of the device.', + name: 'NOTE_STACKING', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('NOTE_STACKED', () => { + const data = [0xf0, 0xeb] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 235, + description: 'The note has exited the device on the host side or has been placed within its note stacker.', + name: 'NOTE_STACKED', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('SAFE_NOTE_JAM', () => { + const data = [0xf0, 0xea] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 234, + description: 'The note is stuck in a position not retrievable from the front of the device (user side)', + name: 'SAFE_NOTE_JAM', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('UNSAFE_NOTE_JAM', () => { + const data = [0xf0, 0xe9] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 233, + description: 'The note is stuck in a position where the user could possibly remove it from the front of the device.', + name: 'UNSAFE_NOTE_JAM', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('DISABLED', () => { + const data = [0xf0, 0xe8] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 232, + description: 'The device is not active and unavailable for normal validation functions.', + name: 'DISABLED', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('FRAUD_ATTEMPT: Banknote validator', () => { + const data = [0xf0, 0xe6, 0x01] + const result = parseData(data, 'POLL', 4, 'Banknote validator') + + expect(result).toEqual({ + command: 'POLL', + + info: [ + { + channel: 1, + code: 230, + description: 'The device has detected an attempt to tamper with the normal validation/stacking/payout process.', + name: 'FRAUD_ATTEMPT', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('FRAUD_ATTEMPT: Smart protocol < 6', () => { + const data = [0xf0, 0xe6, 0x01, 0x00, 0x00, 0x00] + const result = parseData(data, 'POLL', 4, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + + info: [ + { + code: 230, + description: 'The device has detected an attempt to tamper with the normal validation/stacking/payout process.', + name: 'FRAUD_ATTEMPT', + value: 1, + }, + ], + status: 'OK', + success: true, + }) + }) + + test('FRAUD_ATTEMPT: Smart protocol >= 6', () => { + const data = [0xf0, 0xe6, 0x02, 0x02, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44, 0x01, 0x00, 0x00, 0x00, 0x55, 0x41, 0x48] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + + info: [ + { + code: 230, + description: 'The device has detected an attempt to tamper with the normal validation/stacking/payout process.', + name: 'FRAUD_ATTEMPT', + value: [ + { country_code: 'USD', value: 2 }, + { country_code: 'UAH', value: 1 }, + ], + }, + ], + status: 'OK', + success: true, + }) + }) + + test('STACKER_FULL', () => { + const data = [0xf0, 0xe7] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 231, + description: 'The banknote stacker unit attached to this device has been detected as at its full limit', + name: 'STACKER_FULL', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('NOTE_CLEARED_FROM_FRONT', () => { + const data = [0xf0, 0xe1, 0x01] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + channel: 1, + code: 225, + description: + 'At power-up, a note was detected as being rejected out of the front of the device. The channel value, if known is given in the data byte.', + name: 'NOTE_CLEARED_FROM_FRONT', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('NOTE_CLEARED_TO_CASHBOX', () => { + const data = [0xf0, 0xe2, 0x01] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + channel: 1, + code: 226, + description: + 'At power up, a note was detected as being moved into the stacker unit or host exit of the device. The channel number of the note is given in the data byte if known.', + name: 'NOTE_CLEARED_TO_CASHBOX', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('CASHBOX_REMOVED', () => { + const data = [0xf0, 0xe3] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 227, + description: 'A device with a detectable cashbox has detected that it has been removed.', + name: 'CASHBOX_REMOVED', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('CASHBOX_REPLACED', () => { + const data = [0xf0, 0xe4] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 228, + description: 'A device with a detectable cashbox has detected that it has been replaced.', + name: 'CASHBOX_REPLACED', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('BAR_CODE_TICKET_VALIDATED', () => { + const data = [0xf0, 0xe5] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 229, + description: 'A validated barcode ticket has been scanned and is available at the escrow point of the device.', + name: 'BAR_CODE_TICKET_VALIDATED', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('BAR_CODE_TICKET_ACKNOWLEDGE', () => { + const data = [0xf0, 0xd1] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 209, + description: 'The bar code ticket has been passed to a safe point in the device stacker.', + name: 'BAR_CODE_TICKET_ACKNOWLEDGE', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('NOTE_PATH_OPEN', () => { + const data = [0xf0, 0xe0] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 224, + description: 'The device has detected that its note transport path has been opened.', + name: 'NOTE_PATH_OPEN', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('CHANNEL_DISABLE', () => { + const data = [0xf0, 0xb5] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 181, + description: 'The device has had all its note channels inhibited and has become disabled for note insertion.', + name: 'CHANNEL_DISABLE', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('INITIALISING', () => { + const data = [0xf0, 0xb6] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 182, + description: + 'This event is given only when using the Poll with ACK command. It is given when the BNV is powered up and setting its sensors and mechanisms to be ready for Note acceptance. When the event response does not contain this event, the BNV is ready to be enabled and used.', + name: 'INITIALISING', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('DISPENSING: protocol < 6', () => { + const data = [0xf0, 0xda, 0x01, 0x00, 0x00, 0x00] + const result = parseData(data, 'POLL', 5, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 218, + description: 'The device is in the process of paying out a requested value. The value paid at the poll is given in the vent data.', + name: 'DISPENSING', + value: 1, + }, + ], + status: 'OK', + success: true, + }) + }) + + test('DISPENSING: protocol >= 6', () => { + const data = [0xf0, 0xda, 0x02, 0x01, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44, 0x05, 0x00, 0x00, 0x00, 0x55, 0x41, 0x48] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 218, + description: 'The device is in the process of paying out a requested value. The value paid at the poll is given in the vent data.', + name: 'DISPENSING', + value: [ + { + country_code: 'USD', + value: 1, + }, + { + country_code: 'UAH', + value: 5, + }, + ], + }, + ], + status: 'OK', + success: true, + }) + }) + // TODO: test NV11 + + test('DISPENSED: protocol < 6', () => { + const data = [0xf0, 0xd2, 0x01, 0x00, 0x00, 0x00] + const result = parseData(data, 'POLL', 5, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 210, + description: 'The device has completed its pay-out request. The final value paid is given in the event data.', + name: 'DISPENSED', + value: 1, + }, + ], + status: 'OK', + success: true, + }) + }) + + test('DISPENSED: protocol >= 6', () => { + const data = [0xf0, 0xd2, 0x02, 0x01, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44, 0x05, 0x00, 0x00, 0x00, 0x55, 0x41, 0x48] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 210, + description: 'The device has completed its pay-out request. The final value paid is given in the event data.', + name: 'DISPENSED', + value: [ + { + country_code: 'USD', + value: 1, + }, + { + country_code: 'UAH', + value: 5, + }, + ], + }, + ], + status: 'OK', + success: true, + }) + }) + // TODO: test NV11 + + test('JAMMED: protocol < 6', () => { + const data = [0xf0, 0xd5, 0x01, 0x00, 0x00, 0x00] + const result = parseData(data, 'POLL', 5, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 213, + description: + 'The device has detected that coins are jammed in its mechanism and cannot be removed other than by manual intervention. The value paid at the jam point is given in the event data.', + name: 'JAMMED', + value: 1, + }, + ], + status: 'OK', + success: true, + }) + }) + + test('JAMMED: protocol >= 6', () => { + const data = [0xf0, 0xd5, 0x02, 0x01, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44, 0x05, 0x00, 0x00, 0x00, 0x55, 0x41, 0x48] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 213, + description: + 'The device has detected that coins are jammed in its mechanism and cannot be removed other than by manual intervention. The value paid at the jam point is given in the event data.', + name: 'JAMMED', + value: [ + { + country_code: 'USD', + value: 1, + }, + { + country_code: 'UAH', + value: 5, + }, + ], + }, + ], + status: 'OK', + success: true, + }) + }) + + test('JAMMED: NV11 protocol >= 6', () => { + const data = [0xf0, 0xd5, 0x01, 0xe6, 0x00, 0x00, 0x00, 0x45, 0x55, 0x52] + const result = parseData(data, 'POLL', 6, 'Note Float fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 213, + description: + 'The device has detected that coins are jammed in its mechanism and cannot be removed other than by manual intervention. The value paid at the jam point is given in the event data.', + name: 'JAMMED', + value: [ + { + country_code: 'EUR', + value: 230, + }, + ], + }, + ], + status: 'OK', + success: true, + }) + }) + + test('HALTED: protocol < 6', () => { + const data = [0xf0, 0xd6, 0x01, 0x00, 0x00, 0x00] + const result = parseData(data, 'POLL', 5, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 214, + description: + 'This event is given when the host has requested a halt to the device. The value paid at the point of halting is given in the event data.', + name: 'HALTED', + value: 1, + }, + ], + status: 'OK', + success: true, + }) + }) + + test('HALTED: protocol >= 6', () => { + const data = [0xf0, 0xd6, 0x02, 0x01, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44, 0x05, 0x00, 0x00, 0x00, 0x55, 0x41, 0x48] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 214, + description: + 'This event is given when the host has requested a halt to the device. The value paid at the point of halting is given in the event data.', + name: 'HALTED', + value: [ + { + country_code: 'USD', + value: 1, + }, + { + country_code: 'UAH', + value: 5, + }, + ], + }, + ], + status: 'OK', + success: true, + }) + }) + // TODO: test NV11 + + test('FLOATING: protocol < 6', () => { + const data = [0xf0, 0xd7, 0x01, 0x00, 0x00, 0x00] + const result = parseData(data, 'POLL', 5, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 215, + description: + 'The device is in the process of executing a float command and the value paid to the cashbox at the poll time is given in the event data.', + name: 'FLOATING', + value: 1, + }, + ], + status: 'OK', + success: true, + }) + }) + + test('FLOATING: protocol >= 6', () => { + const data = [0xf0, 0xd7, 0x02, 0x01, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44, 0x05, 0x00, 0x00, 0x00, 0x55, 0x41, 0x48] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 215, + description: + 'The device is in the process of executing a float command and the value paid to the cashbox at the poll time is given in the event data.', + name: 'FLOATING', + value: [ + { + country_code: 'USD', + value: 1, + }, + { + country_code: 'UAH', + value: 5, + }, + ], + }, + ], + status: 'OK', + success: true, + }) + }) + + test('FLOATED: protocol < 6', () => { + const data = [0xf0, 0xd8, 0x01, 0x00, 0x00, 0x00] + const result = parseData(data, 'POLL', 5, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 216, + description: 'The device has completed its float command and the final value floated to the cashbox is given in the event data.', + name: 'FLOATED', + value: 1, + }, + ], + status: 'OK', + success: true, + }) + }) + + test('FLOATED: protocol >= 6', () => { + const data = [0xf0, 0xd8, 0x02, 0x01, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44, 0x05, 0x00, 0x00, 0x00, 0x55, 0x41, 0x48] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 216, + description: 'The device has completed its float command and the final value floated to the cashbox is given in the event data.', + name: 'FLOATED', + value: [ + { + country_code: 'USD', + value: 1, + }, + { + country_code: 'UAH', + value: 5, + }, + ], + }, + ], + status: 'OK', + success: true, + }) + }) + + test('TIME_OUT: protocol < 6', () => { + const data = [0xf0, 0xd9, 0x01, 0x00, 0x00, 0x00] + const result = parseData(data, 'POLL', 5, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 217, + description: 'The device has been unable to complete a request. The value paid up until the time-out point is given in the event data.', + name: 'TIME_OUT', + value: 1, + }, + ], + status: 'OK', + success: true, + }) + }) + + test('TIME_OUT: protocol >= 6', () => { + const data = [0xf0, 0xd9, 0x02, 0x01, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44, 0x05, 0x00, 0x00, 0x00, 0x55, 0x41, 0x48] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 217, + description: 'The device has been unable to complete a request. The value paid up until the time-out point is given in the event data.', + name: 'TIME_OUT', + value: [ + { + country_code: 'USD', + value: 1, + }, + { + country_code: 'UAH', + value: 5, + }, + ], + }, + ], + status: 'OK', + success: true, + }) + }) + + test('INCOMPLETE_PAYOUT: protocol < 6', () => { + const data = [0xf0, 0xdc, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00] + const result = parseData(data, 'POLL', 5, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 220, + description: + 'The device has detected a discrepancy on power-up that the last payout request was interrupted (possibly due to a power failure). The amounts of the value paid and requested are given in the event data.', + name: 'INCOMPLETE_PAYOUT', + actual: 1, + requested: 5, + }, + ], + status: 'OK', + success: true, + }) + }) + + test('INCOMPLETE_PAYOUT: protocol >= 6', () => { + const data = [ + 0xf0, 0xdc, 0x02, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x55, + 0x41, 0x48, + ] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 220, + description: + 'The device has detected a discrepancy on power-up that the last payout request was interrupted (possibly due to a power failure). The amounts of the value paid and requested are given in the event data.', + name: 'INCOMPLETE_PAYOUT', + value: [ + { + country_code: 'USD', + actual: 1, + requested: 5, + }, + { + country_code: 'UAH', + actual: 2, + requested: 7, + }, + ], + }, + ], + status: 'OK', + success: true, + }) + }) + // TODO nv11 + + test('INCOMPLETE_FLOAT: protocol < 6', () => { + const data = [0xf0, 0xdd, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00] + const result = parseData(data, 'POLL', 5, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 221, + description: + 'The device has detected a discrepancy on power-up that the last float request was interrupted (possibly due to a power failure). The amounts of the value paid and requested are given in the event data.', + name: 'INCOMPLETE_FLOAT', + actual: 1, + requested: 5, + }, + ], + status: 'OK', + success: true, + }) + }) + + test('INCOMPLETE_FLOAT: protocol >= 6', () => { + const data = [ + 0xf0, 0xdd, 0x02, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x55, + 0x41, 0x48, + ] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 221, + description: + 'The device has detected a discrepancy on power-up that the last float request was interrupted (possibly due to a power failure). The amounts of the value paid and requested are given in the event data.', + name: 'INCOMPLETE_FLOAT', + value: [ + { + country_code: 'USD', + actual: 1, + requested: 5, + }, + { + country_code: 'UAH', + actual: 2, + requested: 7, + }, + ], + }, + ], + status: 'OK', + success: true, + }) + }) + + test('CASHBOX_PAID: protocol < 6', () => { + const data = [0xf0, 0xde, 0x01, 0x00, 0x00, 0x00] + const result = parseData(data, 'POLL', 5, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 222, + description: + 'This is given at the end of a payout cycle. It shows the value of stored coins that were routed to the cashbox that were paid into the cashbox during the payout cycle.', + name: 'CASHBOX_PAID', + value: 1, + }, + ], + status: 'OK', + success: true, + }) + }) + + test('CASHBOX_PAID: protocol >= 6', () => { + const data = [0xf0, 0xde, 0x02, 0x01, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44, 0x02, 0x00, 0x00, 0x00, 0x55, 0x41, 0x48] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 222, + description: + 'This is given at the end of a payout cycle. It shows the value of stored coins that were routed to the cashbox that were paid into the cashbox during the payout cycle.', + name: 'CASHBOX_PAID', + value: [ + { + country_code: 'USD', + value: 1, + }, + { + country_code: 'UAH', + value: 2, + }, + ], + }, + ], + status: 'OK', + success: true, + }) + }) + + test('COIN_CREDIT: protocol < 6', () => { + const data = [0xf0, 0xdf, 0x01, 0x00, 0x00, 0x00] + const result = parseData(data, 'POLL', 5, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 223, + description: + 'A coin has been detected as added to the system via the attached coin mechanism. The value of the coin detected is given in the event data.', + name: 'COIN_CREDIT', + value: 1, + }, + ], + status: 'OK', + success: true, + }) + }) + + test('COIN_CREDIT: protocol >= 6', () => { + const data = [0xf0, 0xdf, 0x02, 0x01, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44, 0x02, 0x00, 0x00, 0x00, 0x55, 0x41, 0x48] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 223, + description: + 'A coin has been detected as added to the system via the attached coin mechanism. The value of the coin detected is given in the event data.', + name: 'COIN_CREDIT', + value: [ + { + country_code: 'USD', + value: 1, + }, + { + country_code: 'UAH', + value: 2, + }, + ], + }, + ], + status: 'OK', + success: true, + }) + }) + + test('COIN_MECH_JAMMED', () => { + const data = [0xf0, 0xc4] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 196, + description: 'The attached coin mechanism has been detected as having a jam.', + name: 'COIN_MECH_JAMMED', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('COIN_MECH_RETURN_PRESSED', () => { + const data = [0xf0, 0xc5] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 197, + description: 'The attached coin mechanism has been detected as having is reject or return button pressed.', + name: 'COIN_MECH_RETURN_PRESSED', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('EMPTYING', () => { + const data = [0xf0, 0xc2] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 194, + description: 'The device is in the process of emptying its content to the system cashbox in response to an Empty command.', + name: 'EMPTYING', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('EMPTIED', () => { + const data = [0xf0, 0xc3] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 195, + description: 'The device has completed its Empty process in response to an Empty command from the host.', + name: 'EMPTIED', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('SMART_EMPTYING: protocol < 6', () => { + const data = [0xf0, 0xb3, 0x01, 0x00, 0x00, 0x00] + const result = parseData(data, 'POLL', 5, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 179, + description: + 'The device is in the process of carrying out its Smart Empty command from the host. The value emptied at the poll point is given in the event data.', + name: 'SMART_EMPTYING', + value: 1, + }, + ], + status: 'OK', + success: true, + }) + }) + + test('SMART_EMPTYING: protocol >= 6', () => { + const data = [0xf0, 0xb3, 0x02, 0x01, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44, 0x02, 0x00, 0x00, 0x00, 0x55, 0x41, 0x48] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 179, + description: + 'The device is in the process of carrying out its Smart Empty command from the host. The value emptied at the poll point is given in the event data.', + name: 'SMART_EMPTYING', + value: [ + { + country_code: 'USD', + value: 1, + }, + { + country_code: 'UAH', + value: 2, + }, + ], + }, + ], + status: 'OK', + success: true, + }) + }) + + test('SMART_EMPTIED: protocol < 6', () => { + const data = [0xf0, 0xb4, 0x01, 0x00, 0x00, 0x00] + const result = parseData(data, 'POLL', 5, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 180, + description: 'The device has completed its Smart Empty command. The total amount emptied is given in the event data.', + name: 'SMART_EMPTIED', + value: 1, + }, + ], + status: 'OK', + success: true, + }) + }) + + test('SMART_EMPTIED: protocol >= 6', () => { + const data = [0xf0, 0xb4, 0x02, 0x01, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44, 0x02, 0x00, 0x00, 0x00, 0x55, 0x41, 0x48] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 180, + description: 'The device has completed its Smart Empty command. The total amount emptied is given in the event data.', + name: 'SMART_EMPTIED', + value: [ + { + country_code: 'USD', + value: 1, + }, + { + country_code: 'UAH', + value: 2, + }, + ], + }, + ], + status: 'OK', + success: true, + }) + }) + + test('COIN_MECH_ERROR', () => { + const data = [0xf0, 0xb7] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 183, + description: 'The attached coin mechanism has generated an error. Its code is given in the event data.', + name: 'COIN_MECH_ERROR', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('NOTE_STORED_IN_PAYOUT', () => { + const data = [0xf0, 0xdb] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 219, + description: 'The note has been passed into the note store of the payout unit.', + name: 'NOTE_STORED_IN_PAYOUT', + }, + ], + status: 'OK', + success: true, + }) + }) + // TODO nv11 + + test('PAYOUT_OUT_OF_SERVICE', () => { + const data = [0xf0, 0xc6] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 198, + description: + 'This event is given if the payout goes out of service during operation. If this event is detected after a poll, the host can send the ENABLE PAYOUT DEVICE command to determine if the payout unit comes back into service.', + name: 'PAYOUT_OUT_OF_SERVICE', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('JAM_RECOVERY', () => { + const data = [0xf0, 0xb0] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 176, + description: + 'The SMART Payout unit is in the process of recovering from a detected jam. This process will typically move five notes to the cash box; this is done to minimise the possibility the unit will go out of service', + name: 'JAM_RECOVERY', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('ERROR_DURING_PAYOUT: protocol < 6', () => { + const data = [0xf0, 0xb1, 0x00] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 177, + description: + 'Returned if an error is detected whilst moving a note inside the SMART Payout unit. The cause of error (1 byte) indicates the source of the condition; 0x00 for note not being correctly detected as it is routed to cashbox or for payout, 0x01 if note is jammed in transport. In the case of the incorrect detection, the response to Cashbox Payout Operation Data request would report the note expected to be paid out.', + name: 'ERROR_DURING_PAYOUT', + error: 'Note not being correctly detected as it is routed', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('ERROR_DURING_PAYOUT: protocol >= 6', () => { + const data = [0xf0, 0xb1, 0x02, 0x01, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44, 0x02, 0x00, 0x00, 0x00, 0x55, 0x41, 0x48, 0x00] + const result = parseData(data, 'POLL', 7, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 177, + description: + 'Returned if an error is detected whilst moving a note inside the SMART Payout unit. The cause of error (1 byte) indicates the source of the condition; 0x00 for note not being correctly detected as it is routed to cashbox or for payout, 0x01 if note is jammed in transport. In the case of the incorrect detection, the response to Cashbox Payout Operation Data request would report the note expected to be paid out.', + name: 'ERROR_DURING_PAYOUT', + value: [ + { + country_code: 'USD', + value: 1, + }, + { + country_code: 'UAH', + value: 2, + }, + ], + error: 'Note not being correctly detected as it is routed', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('NOTE_TRANSFERED_TO_STACKER: protocol >=6', () => { + const data = [0xf0, 0xc9, 0x01, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 201, + description: 'Reported when a note has been successfully moved from the payout store into the stacker cashbox.', + name: 'NOTE_TRANSFERED_TO_STACKER', + value: { + country_code: 'USD', + value: 1, + }, + }, + ], + status: 'OK', + success: true, + }) + }) + + test('NOTE_HELD_IN_BEZEL: protocol >=8', () => { + const data = [0xf0, 0xce, 0x01, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44] + const result = parseData(data, 'POLL', 8, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 206, + description: 'Reported when a dispensing note is held in the bezel of the payout device.', + name: 'NOTE_HELD_IN_BEZEL', + value: { + country_code: 'USD', + value: 1, + }, + }, + ], + status: 'OK', + success: true, + }) + }) + + test('NOTE_PAID_INTO_STORE_AT_POWER-UP: protocol < 8', () => { + const data = [0xf0, 0xcb, 0x01, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44] + const result = parseData(data, 'POLL', 7, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 203, + description: 'Reported when a note has been detected as paid into the payout store as part of the power-up procedure.', + name: 'NOTE_PAID_INTO_STORE_AT_POWER-UP', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('NOTE_PAID_INTO_STORE_AT_POWER-UP: protocol >=8', () => { + const data = [0xf0, 0xcb, 0x01, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44] + const result = parseData(data, 'POLL', 8, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 203, + description: 'Reported when a note has been detected as paid into the payout store as part of the power-up procedure.', + name: 'NOTE_PAID_INTO_STORE_AT_POWER-UP', + value: { + country_code: 'USD', + value: 1, + }, + }, + ], + status: 'OK', + success: true, + }) + }) + + test('NOTE_PAID_INTO_STACKER_AT_POWER-UP: protocol >=8', () => { + const data = [0xf0, 0xca, 0x01, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44] + const result = parseData(data, 'POLL', 8, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 202, + description: 'Reported when a note has been detected as paid into the cashbox stacker as part of the power-up procedure.', + name: 'NOTE_PAID_INTO_STACKER_AT_POWER-UP', + value: { + country_code: 'USD', + value: 1, + }, + }, + ], + status: 'OK', + success: true, + }) + }) + + test('NOTE_DISPENSED_AT_POWER-UP: protocol < 6', () => { + const data = [0xf0, 0xcd, 0x01, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44] + const result = parseData(data, 'POLL', 4, 'Note Float fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 205, + description: 'Reported when a note has been dispensed as part of the power-up procedure.', + name: 'NOTE_DISPENSED_AT_POWER-UP', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('NOTE_DISPENSED_AT_POWER-UP: protocol >=6', () => { + const data = [0xf0, 0xcd, 0x01, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44] + const result = parseData(data, 'POLL', 6, 'Note Float fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 205, + description: 'Reported when a note has been dispensed as part of the power-up procedure.', + name: 'NOTE_DISPENSED_AT_POWER-UP', + value: { + country_code: 'USD', + value: 1, + }, + }, + ], + status: 'OK', + success: true, + }) + }) + + test('NOTE_FLOAT_REMOVED', () => { + const data = [0xf0, 0xc7] + const result = parseData(data, 'POLL', 6, 'Note Float fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 199, + description: 'Reported when a note float unit has been detected as removed from its validator.', + name: 'NOTE_FLOAT_REMOVED', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('NOTE_FLOAT_ATTACHED', () => { + const data = [0xf0, 0xc8] + const result = parseData(data, 'POLL', 6, 'Note Float fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 200, + description: 'Reported when a note float unit has been detected as removed from its validator.', + name: 'NOTE_FLOAT_ATTACHED', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('DEVICE_FULL', () => { + const data = [0xf0, 0xcf] + const result = parseData(data, 'POLL', 6, 'Note Float fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 207, + description: + 'This event is reported when the Note Float has reached its limit of stored notes. This event will be reported until a note is paid out or stacked.', + name: 'DEVICE_FULL', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('Skip unknown status', () => { + const data = [0xf0, 0x00, 0x00, 0x00, 0xb6, 0x00] + const result = parseData(data, 'POLL', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 182, + description: + 'This event is given only when using the Poll with ACK command. It is given when the BNV is powered up and setting its sensors and mechanisms to be ready for Note acceptance. When the event response does not contain this event, the BNV is ready to be enabled and used.', + name: 'INITIALISING', + }, + ], + status: 'OK', + success: true, + }) + }) + + test('Multiple events', () => { + const data = [ + 0xf0, 0xb1, 0x02, 0x01, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44, 0x02, 0x00, 0x00, 0x00, 0x55, 0x41, 0x48, 0x00, 0xb4, 0x02, 0x01, 0x00, 0x00, + 0x00, 0x55, 0x53, 0x44, 0x02, 0x00, 0x00, 0x00, 0x55, 0x41, 0x48, 0xe4, + ] + const result = parseData(data, 'POLL', 8, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'POLL', + info: [ + { + code: 177, + description: + 'Returned if an error is detected whilst moving a note inside the SMART Payout unit. The cause of error (1 byte) indicates the source of the condition; 0x00 for note not being correctly detected as it is routed to cashbox or for payout, 0x01 if note is jammed in transport. In the case of the incorrect detection, the response to Cashbox Payout Operation Data request would report the note expected to be paid out.', + name: 'ERROR_DURING_PAYOUT', + value: [ + { + country_code: 'USD', + value: 1, + }, + { + country_code: 'UAH', + value: 2, + }, + ], + error: 'Note not being correctly detected as it is routed', + }, + { + code: 180, + description: 'The device has completed its Smart Empty command. The total amount emptied is given in the event data.', + name: 'SMART_EMPTIED', + value: [ + { + country_code: 'USD', + value: 1, + }, + { + country_code: 'UAH', + value: 2, + }, + ], + }, + { + code: 228, + description: 'A device with a detectable cashbox has detected that it has been replaced.', + name: 'CASHBOX_REPLACED', + }, + ], + status: 'OK', + success: true, + }) + }) + }) + + describe('HOST_PROTOCOL_VERSION', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'HOST_PROTOCOL_VERSION', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'HOST_PROTOCOL_VERSION', + info: {}, + status: 'OK', + success: true, + }) + }) + + test('FAIL', () => { + const data = [0xf8] + const result = parseData(data, 'HOST_PROTOCOL_VERSION', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'HOST_PROTOCOL_VERSION', + info: {}, + status: 'FAIL', + success: false, + }) + }) + }) + + describe('SETUP_REQUEST', () => { + test('OK: SMART Hopper, protocol < 6', () => { + const data = [0xf0, 0x03, 0x30, 0x31, 0x30, 0x30, 0x45, 0x55, 0x52, 0x05, 0x03, 0x01, 0x00, 0x02, 0x00, 0x05, 0x00] + const result = parseData(data, 'SETUP_REQUEST', 5, 'Smart Hopper') + + expect(result).toEqual({ + command: 'SETUP_REQUEST', + info: { + coin_values: [1, 2, 5], + country_code: 'EUR', + firmware_version: '1.00', + number_of_coin_values: 3, + protocol_version: 5, + unit_type: 'Smart Hopper', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: SMART Hopper, protocol >= 6', () => { + const data = [ + 0xf0, 0x03, 0x30, 0x31, 0x30, 0x30, 0x45, 0x55, 0x52, 0x06, 0x03, 0x01, 0x00, 0x02, 0x00, 0x05, 0x00, 0x45, 0x55, 0x52, 0x45, 0x55, 0x52, + 0x45, 0x55, 0x52, + ] + const result = parseData(data, 'SETUP_REQUEST', 6, 'Smart Hopper') + + expect(result).toEqual({ + command: 'SETUP_REQUEST', + info: { + coin_values: [1, 2, 5], + country_code: 'EUR', + country_codes_for_values: ['EUR', 'EUR', 'EUR'], + firmware_version: '1.00', + number_of_coin_values: 3, + protocol_version: 6, + unit_type: 'Smart Hopper', + }, + status: 'OK', + success: true, + }) + }) + + test('OK: Banknote validator', () => { + const data = [ + 0xf0, 0x00, 0x30, 0x31, 0x30, 0x30, 0x45, 0x55, 0x52, 0x00, 0x00, 0x01, 0x03, 0x05, 0x0a, 0x14, 0x02, 0x02, 0x02, 0x00, 0x00, 0x64, 0x04, + ] + const result = parseData(data, 'SETUP_REQUEST', 4, 'Banknote validator') + + expect(result).toEqual({ + command: 'SETUP_REQUEST', + info: { + channel_security: [2, 2, 2], + channel_value: [5, 10, 20], + country_code: 'EUR', + firmware_version: '1.00', + number_of_channels: 3, + protocol_version: 4, + real_value_multiplier: 100, + unit_type: 'Banknote validator', + value_multiplier: 1, + }, + status: 'OK', + success: true, + }) + }) + + test('OK: protocol < 6', () => { + const data = [ + 0xf0, 0x06, 0x30, 0x34, 0x35, 0x39, 0x55, 0x53, 0x44, 0x00, 0x00, 0x01, 0x07, 0x01, 0x02, 0x05, 0x0a, 0x14, 0x32, 0x64, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x64, 0x04, + ] + const result = parseData(data, 'SETUP_REQUEST', 4, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SETUP_REQUEST', + info: { + channel_security: [2, 2, 2, 2, 2, 2, 2], + channel_value: [1, 2, 5, 10, 20, 50, 100], + country_code: 'USD', + firmware_version: '4.59', + number_of_channels: 7, + protocol_version: 4, + real_value_multiplier: 100, + unit_type: 'SMART payout fitted', + value_multiplier: 1, + }, + status: 'OK', + success: true, + }) + }) + + test('OK: protocol >= 6', () => { + const data = [ + 0xf0, 0x06, 0x30, 0x34, 0x35, 0x39, 0x55, 0x53, 0x44, 0x00, 0x00, 0x01, 0x07, 0x01, 0x02, 0x05, 0x0a, 0x14, 0x32, 0x64, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x64, 0x06, 0x55, 0x53, 0x44, 0x55, 0x53, 0x44, 0x55, 0x53, 0x44, 0x55, 0x53, 0x44, 0x55, 0x53, 0x44, + 0x55, 0x53, 0x44, 0x55, 0x53, 0x44, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, + ] + const result = parseData(data, 'SETUP_REQUEST', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SETUP_REQUEST', + info: { + channel_security: [2, 2, 2, 2, 2, 2, 2], + channel_value: [1, 2, 5, 10, 20, 50, 100], + country_code: 'USD', + expanded_channel_country_code: ['USD', 'USD', 'USD', 'USD', 'USD', 'USD', 'USD'], + expanded_channel_value: [1, 2, 5, 10, 20, 50, 100], + firmware_version: '4.59', + number_of_channels: 7, + protocol_version: 6, + real_value_multiplier: 100, + unit_type: 'SMART payout fitted', + value_multiplier: 1, + }, + status: 'OK', + success: true, + }) + }) + }) + + describe('DISPLAY_OFF', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'DISPLAY_OFF', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'DISPLAY_OFF', + info: {}, + status: 'OK', + success: true, + }) + }) + }) + + describe('DISPLAY_ON', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'DISPLAY_ON', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'DISPLAY_ON', + info: {}, + status: 'OK', + success: true, + }) + }) + }) + + describe('SET_CHANNEL_INHIBITS', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'SET_CHANNEL_INHIBITS', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'SET_CHANNEL_INHIBITS', + info: {}, + status: 'OK', + success: true, + }) + }) + }) + + describe('RESET', () => { + test('OK', () => { + const data = [0xf0] + const result = parseData(data, 'RESET', 6, 'SMART payout fitted') + + expect(result).toEqual({ + command: 'RESET', + info: {}, + status: 'OK', + success: true, + }) + }) + }) +}) diff --git a/lib/utils.test.js b/lib/utils.test.js new file mode 100644 index 0000000..725fb8d --- /dev/null +++ b/lib/utils.test.js @@ -0,0 +1,877 @@ +const crypto = require('node:crypto') +const semver = require('semver') +const { + absBigInt, + argsToByte, + CRC16, + createSSPHostEncryptionKey, + decrypt, + encrypt, + extractPacketData, + generateKeys, + getPacket, + randomInt, + readBytesFromBuffer, + stuffBuffer, + uInt16LE, + uInt32LE, + uInt64LE, + validateNodeVersion, +} = require('./utils') +const { engines } = require('../package.json') + +// Mocking the chalk module +jest.mock('chalk', () => ({ + red: jest.fn(text => text), // Mocking the red method to return the text as is +})) + +describe('absBigInt', () => { + test('should return 1 for -1', () => { + const num = BigInt(-1) + expect(absBigInt(num)).toBe(BigInt(1)) + }) + + test('should return 1 for 1', () => { + const num = BigInt(1) + expect(absBigInt(num)).toBe(BigInt(1)) + }) +}) + +describe('encrypt', () => { + const key = Buffer.concat([Buffer.from('0123456701234567', 'hex').swap64(), uInt64LE(23150n)]) + const data = Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + + test('should encrypt data using AES encryption with ECB mode', () => { + const encryptedData = encrypt(key, data) + expect(encryptedData).toBeDefined() + expect(encryptedData).toBeInstanceOf(Buffer) + expect(encryptedData.length).toBeGreaterThan(0) + expect(encryptedData).not.toEqual(data) // Encrypted data should not be the same as original data + }) + + test('should throw an error if key is not provided', () => { + expect(() => { + encrypt(null, data) + }).toThrow('Key must be a Buffer') + }) + + test('should throw an error if key is not a Buffer', () => { + expect(() => { + encrypt('not a buffer', data) + }).toThrow('Key must be a Buffer') + }) + + test('should throw an error if data is not provided', () => { + expect(() => { + encrypt(key, null) + }).toThrow('Data must be a Buffer') + }) + + test('should throw an error if data is not a Buffer', () => { + expect(() => { + encrypt(key, 'not a buffer') + }).toThrow('Data must be a Buffer') + }) +}) + +describe('decrypt', () => { + const key = Buffer.concat([Buffer.from('0123456701234567', 'hex').swap64(), uInt64LE(23150n)]) + const data = Buffer.from([134, 163, 14, 111, 155, 193, 109, 210, 180, 37, 128, 45, 45, 157, 68, 152]) + + // Encrypt the original data to get the encrypted data + const encryptedData = encrypt(key, data) + + test('should decrypt data using AES decryption with ECB mode', () => { + const decryptedData = decrypt(key, encryptedData) + expect(decryptedData).toBeDefined() + expect(decryptedData).toBeInstanceOf(Buffer) + expect(decryptedData.length).toBeGreaterThan(0) + expect(decryptedData).toEqual(data) // Decrypted data should be the same as original data + }) + + test('should throw an error if key is not provided', () => { + expect(() => { + decrypt(null, encryptedData) + }).toThrow('Key must be a Buffer') + }) + + test('should throw an error if key is not a Buffer', () => { + expect(() => { + decrypt('not a buffer', encryptedData) + }).toThrow('Key must be a Buffer') + }) + + test('should throw an error if data is not provided', () => { + expect(() => { + decrypt(key, null) + }).toThrow('Data must be a Buffer') + }) + + test('should throw an error if data is not a Buffer', () => { + expect(() => { + decrypt(key, 'not a buffer') + }).toThrow('Data must be a Buffer') + }) +}) + +describe('readBytesFromBuffer', () => { + test('should return a new Buffer with the specified bytes', () => { + const buffer = Buffer.from([1, 2, 3, 4, 5]) + const result = readBytesFromBuffer(buffer, 1, 3) + expect(result).toEqual(Buffer.from([2, 3, 4])) + }) + + test('should throw an error if input is not a Buffer', () => { + expect(() => { + readBytesFromBuffer('not a buffer', 0, 3) + }).toThrow('Input must be a Buffer object') + }) + + test('should throw an error if start index is negative', () => { + const buffer = Buffer.from([1, 2, 3]) + expect(() => { + readBytesFromBuffer(buffer, -1, 2) + }).toThrow('Invalid start index') + }) + + test('should throw an error if start index is greater than buffer length', () => { + const buffer = Buffer.from([1, 2, 3]) + expect(() => { + readBytesFromBuffer(buffer, 4, 2) + }).toThrow('Invalid start index') + }) + + test('should throw an error if length is negative', () => { + const buffer = Buffer.from([1, 2, 3]) + expect(() => { + readBytesFromBuffer(buffer, 0, -2) + }).toThrow('Invalid length or exceeds buffer size') + }) + + test('should throw an error if length exceeds buffer size', () => { + const buffer = Buffer.from([1, 2, 3]) + expect(() => { + readBytesFromBuffer(buffer, 0, 4) + }).toThrow('Invalid length or exceeds buffer size') + }) + + test('should handle reading from an empty buffer', () => { + const buffer = Buffer.from([]) + expect(() => { + readBytesFromBuffer(buffer, 0, 1) + }).toThrow('Invalid start index') + }) + + test('should handle reading zero length from buffer', () => { + const buffer = Buffer.from([1, 2, 3]) + const result = readBytesFromBuffer(buffer, 0, 0) + expect(result).toEqual(Buffer.from([])) + }) +}) + +describe('randomInt', () => { + test('should return random int', () => { + const int = randomInt(10, 100) + + expect(typeof int).toBe('number') + expect(int >= 10).toBeTruthy() + expect(int <= 100).toBeTruthy() + }) +}) + +describe('CRC16', () => { + it('calculates CRC16 correctly for an empty string', () => { + expect(CRC16([])).toEqual(Buffer.from([255, 255])) + }) + + it('calculates CRC16 correctly for a single character string', () => { + expect(CRC16([65])).toEqual(Buffer.from([132, 252])) + }) + + it('calculates CRC16 correctly for a multiple character string', () => { + expect(CRC16([65, 66, 67])).toEqual(Buffer.from([155, 6])) + }) + + it('calculates CRC16 correctly for a large string', () => { + expect(CRC16([65, 65, 65, 65, 65, 65, 65, 65])).toEqual(Buffer.from([232, 166])) + }) + + it('calculates CRC16 correctly for a Buffer input', () => { + const buffer = Buffer.from('ABCD', 'utf-8') + expect(CRC16(buffer)).toEqual(Buffer.from([140, 154])) + }) +}) + +describe('uInt64LE', () => { + test('should return a Buffer representing the given unsigned 64-bit integer in little-endian format', () => { + const number = 12345678901234567890n + const result = uInt64LE(number) + expect(result).toBeDefined() + expect(result).toBeInstanceOf(Buffer) + expect(result).toHaveLength(8) + expect(result.readBigUInt64LE()).toBe(number) + }) + + test('should throw an error if input is not an unsigned 64-bit integer', () => { + expect(() => { + uInt64LE(-1) + }).toThrow('Input must be an unsigned 64-bit integer') + + expect(() => { + uInt64LE(18446744073709551616n) + }).toThrow('Input must be an unsigned 64-bit integer') + + expect(() => { + uInt64LE('not a number') + }).toThrow('Input must be an unsigned 64-bit integer') + }) +}) + +describe('uInt32LE', () => { + test('should return a Buffer representing the given unsigned 32-bit integer in little-endian format', () => { + const number = 1234567890 + const result = uInt32LE(number) + expect(result).toBeDefined() + expect(result).toBeInstanceOf(Buffer) + expect(result).toHaveLength(4) + expect(result.readUInt32LE()).toBe(number) + }) + + test('should throw an error if input is not an unsigned 32-bit integer', () => { + expect(() => { + uInt32LE(-1) + }).toThrow('Input must be an unsigned 32-bit integer') + + expect(() => { + uInt32LE(4294967296) + }).toThrow('Input must be an unsigned 32-bit integer') + + expect(() => { + uInt32LE('not a number') + }).toThrow('Input must be an unsigned 32-bit integer') + }) +}) + +describe('uInt16LE', () => { + test('should return a Buffer representing the given unsigned 16-bit integer in little-endian format', () => { + const number = 1234 + const result = uInt16LE(number) + expect(result).toBeDefined() + expect(result).toBeInstanceOf(Buffer) + expect(result).toHaveLength(2) + expect(result.readUInt16LE()).toBe(number) + }) + + test('should throw an error if input is not an unsigned 16-bit integer', () => { + expect(() => { + uInt16LE(-1) + }).toThrow('Input must be an unsigned 16-bit integer') + + expect(() => { + uInt16LE(65536) + }).toThrow('Input must be an unsigned 16-bit integer') + + expect(() => { + uInt16LE('not a number') + }).toThrow('Input must be an unsigned 16-bit integer') + }) +}) + +describe('argsToByte function', () => { + test('SET_GENERATOR', () => { + const result = argsToByte('SET_GENERATOR', { key: 982451653 }, 6) + expect(result).toEqual(Buffer.from([0xc5, 0x05, 0x8f, 0x3a, 0x00, 0x00, 0x00, 0x00])) + }) + + test('SET_MODULUS', () => { + const result = argsToByte('SET_MODULUS', { key: 1287821 }, 6) + expect(result).toEqual(Buffer.from([0x8d, 0xa6, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00])) + }) + + test('REQUEST_KEY_EXCHANGE', () => { + const result = argsToByte('REQUEST_KEY_EXCHANGE', { key: 7554354432121 }, 6) + expect(result).toEqual(Buffer.from([0x79, 0xc8, 0x9c, 0xe2, 0xde, 0x06, 0x00, 0x00])) + }) + + test('SET_DENOMINATION_ROUTE: protocol < 6', () => { + const result = argsToByte('SET_DENOMINATION_ROUTE', { route: 'cashbox', value: 10 }, 5) + expect(result).toEqual(Buffer.from([0x01, 0x0a, 0x00, 0x00, 0x00])) + }) + + test('SET_DENOMINATION_ROUTE: protocol < 6, hopper', () => { + const result = argsToByte('SET_DENOMINATION_ROUTE', { route: 'cashbox', value: 10, isHopper: true }, 5) + expect(result).toEqual(Buffer.from([0x01, 0x0a, 0x00])) + }) + + test('SET_DENOMINATION_ROUTE: protocol >= 6', () => { + const result = argsToByte('SET_DENOMINATION_ROUTE', { route: 'payout', value: 10, country_code: 'EUR' }, 6) + expect(result).toEqual(Buffer.from([0x00, 0x0a, 0x00, 0x00, 0x00, 0x45, 0x55, 0x52])) + }) + + test('SET_CHANNEL_INHIBITS', () => { + const result = argsToByte('SET_CHANNEL_INHIBITS', { channels: [1, 1, 1, 0, 0, 0] }, 6) + expect(result).toEqual(Buffer.from([0x07, 0x00])) + }) + + test('SET_COIN_MECH_GLOBAL_INHIBIT: enable', () => { + const result = argsToByte('SET_COIN_MECH_GLOBAL_INHIBIT', { enable: true }, 6) + expect(result).toEqual(Buffer.from([0x01])) + }) + + test('SET_COIN_MECH_GLOBAL_INHIBIT: disable', () => { + const result = argsToByte('SET_COIN_MECH_GLOBAL_INHIBIT', { enable: false }, 6) + expect(result).toEqual(Buffer.from([0x00])) + }) + + test('SET_HOPPER_OPTIONS: 1', () => { + const result = argsToByte('SET_HOPPER_OPTIONS', { payMode: 0, levelCheck: false, motorSpeed: 1, cashBoxPayActive: false }, 6) + expect(result).toEqual(Buffer.from([0x04, 0x00])) + }) + + test('SET_HOPPER_OPTIONS: 2', () => { + const result = argsToByte('SET_HOPPER_OPTIONS', { payMode: 1, levelCheck: true, motorSpeed: 0, cashBoxPayActive: true }, 6) + expect(result).toEqual(Buffer.from([0x0b, 0x00])) + }) + + test('GET_DENOMINATION_ROUTE: protocol < 6', () => { + const result = argsToByte('GET_DENOMINATION_ROUTE', { value: 500 }, 5) + expect(result).toEqual(Buffer.from([0xf4, 0x01, 0x00, 0x00])) + }) + + test('GET_DENOMINATION_ROUTE: hopper, protocol < 6', () => { + const result = argsToByte('GET_DENOMINATION_ROUTE', { value: 500, isHopper: true }, 5) + expect(result).toEqual(Buffer.from([0xf4, 0x01])) + }) + + test('GET_DENOMINATION_ROUTE: protocol >= 6', () => { + const result = argsToByte('GET_DENOMINATION_ROUTE', { value: 500, country_code: 'EUR' }, 6) + expect(result).toEqual(Buffer.from([0xf4, 0x01, 0x00, 0x00, 0x45, 0x55, 0x52])) + }) + + test('SET_DENOMINATION_LEVEL: protocol < 6', () => { + const result = argsToByte('SET_DENOMINATION_LEVEL', { value: 20, denomination: 50 }, 5) + expect(result).toEqual(Buffer.from([0x14, 0x00, 0x32, 0x00])) + }) + + test('SET_DENOMINATION_LEVEL: protocol >= 6', () => { + const result = argsToByte('SET_DENOMINATION_LEVEL', { value: 12, denomination: 100, country_code: 'EUR' }, 6) + expect(result).toEqual(Buffer.from([0x0c, 0x00, 0x64, 0x00, 0x00, 0x00, 0x45, 0x55, 0x52])) + }) + + test('SET_REFILL_MODE: on', () => { + const result = argsToByte('SET_REFILL_MODE', { mode: 'on' }, 6) + expect(result).toEqual(Buffer.from([0x05, 0x81, 0x10, 0x11, 0x01])) + }) + + test('SET_REFILL_MODE: off', () => { + const result = argsToByte('SET_REFILL_MODE', { mode: 'off' }, 6) + expect(result).toEqual(Buffer.from([0x05, 0x81, 0x10, 0x11, 0x00])) + }) + + test('SET_REFILL_MODE: get', () => { + const result = argsToByte('SET_REFILL_MODE', { mode: 'get' }, 6) + expect(result).toEqual(Buffer.from([0x05, 0x81, 0x10, 0x01])) + }) + + test('HOST_PROTOCOL_VERSION', () => { + const result = argsToByte('HOST_PROTOCOL_VERSION', { version: 6 }, 6) + expect(result).toEqual(Buffer.from([0x06])) + }) + + test('SET_BAR_CODE_CONFIGURATION', () => { + const result = argsToByte('SET_BAR_CODE_CONFIGURATION', { enable: 'both', numChar: 18 }, 6) + expect(result).toEqual(Buffer.from([0x03, 0x01, 0x12])) + }) + + test('SET_BAR_CODE_CONFIGURATION: bound low', () => { + const result = argsToByte('SET_BAR_CODE_CONFIGURATION', { enable: 'top', numChar: 5 }, 6) + expect(result).toEqual(Buffer.from([0x01, 0x01, 0x06])) + }) + + test('SET_BAR_CODE_CONFIGURATION: bound up', () => { + const result = argsToByte('SET_BAR_CODE_CONFIGURATION', { enable: 'bottom', numChar: 30 }, 6) + expect(result).toEqual(Buffer.from([0x02, 0x01, 0x18])) + }) + + test('SET_BAR_CODE_CONFIGURATION: none', () => { + const result = argsToByte('SET_BAR_CODE_CONFIGURATION', {}, 6) + expect(result).toEqual(Buffer.from([0x00, 0x01, 0x06])) + }) + + test('SET_BAR_CODE_INHIBIT_STATUS', () => { + const result = argsToByte('SET_BAR_CODE_INHIBIT_STATUS', { currencyRead: true, barCode: true }, 6) + expect(result).toEqual(Buffer.from([0xff])) + }) + + test('SET_BAR_CODE_INHIBIT_STATUS: turned off', () => { + const result = argsToByte('SET_BAR_CODE_INHIBIT_STATUS', { currencyRead: false, barCode: false }, 6) + expect(result).toEqual(Buffer.from([0xfc])) + }) + + test('PAYOUT_AMOUNT: protocol < 6', () => { + const result = argsToByte('PAYOUT_AMOUNT', { amount: 500 }, 4) + expect(result).toEqual(Buffer.from([0xf4, 0x01, 0x00, 0x00])) + }) + + test('PAYOUT_AMOUNT: protocol >= 6', () => { + const result = argsToByte('PAYOUT_AMOUNT', { amount: 500, country_code: 'EUR' }, 6) + expect(result).toEqual(Buffer.from([0xf4, 0x01, 0x00, 0x00, 0x45, 0x55, 0x52, 0x58])) + }) + + test('PAYOUT_AMOUNT: protocol >= 6, test', () => { + const result = argsToByte('PAYOUT_AMOUNT', { test: true, amount: 500, country_code: 'EUR' }, 6) + expect(result).toEqual(Buffer.from([0xf4, 0x01, 0x00, 0x00, 0x45, 0x55, 0x52, 0x19])) + }) + + test('GET_DENOMINATION_LEVEL: protocol < 6', () => { + const result = argsToByte('GET_DENOMINATION_LEVEL', { amount: 10 }, 5) + expect(result).toEqual(Buffer.from([0x0a, 0x00, 0x00, 0x00])) + }) + + test('GET_DENOMINATION_LEVEL: protocol >= 6', () => { + const result = argsToByte('GET_DENOMINATION_LEVEL', { amount: 500, country_code: 'EUR' }, 6) + expect(result).toEqual(Buffer.from([0xf4, 0x01, 0x00, 0x00, 0x45, 0x55, 0x52])) + }) + + test('FLOAT_AMOUNT: protocol < 6', () => { + const result = argsToByte('FLOAT_AMOUNT', { min_possible_payout: 50, amount: 10000 }, 5) + expect(result).toEqual(Buffer.from([0x32, 0x00, 0x10, 0x27, 0x00, 0x00])) + }) + + test('FLOAT_AMOUNT: protocol >= 6', () => { + const result = argsToByte('FLOAT_AMOUNT', { min_possible_payout: 50, amount: 10000, country_code: 'EUR' }, 6) + expect(result).toEqual(Buffer.from([0x32, 0x00, 0x10, 0x27, 0x00, 0x00, 0x45, 0x55, 0x52, 0x58])) + }) + + test('FLOAT_AMOUNT: protocol >= 6, test', () => { + const result = argsToByte('FLOAT_AMOUNT', { test: true, min_possible_payout: 50, amount: 10000, country_code: 'EUR' }, 6) + expect(result).toEqual(Buffer.from([0x32, 0x00, 0x10, 0x27, 0x00, 0x00, 0x45, 0x55, 0x52, 0x19])) + }) + + test('SET_COIN_MECH_INHIBITS: protocol < 6', () => { + const result = argsToByte('SET_COIN_MECH_INHIBITS', { inhibited: true, amount: 100 }, 5) + expect(result).toEqual(Buffer.from([0x00, 0x64, 0x00])) + }) + + test('SET_COIN_MECH_INHIBITS: protocol >= 6', () => { + const result = argsToByte('SET_COIN_MECH_INHIBITS', { inhibited: false, amount: 50, country_code: 'EUR' }, 6) + expect(result).toEqual(Buffer.from([0x01, 0x32, 0x00, 0x45, 0x55, 0x52])) + }) + + test('FLOAT_BY_DENOMINATION', () => { + const value = [ + { number: 4, denomination: 100, country_code: 'EUR' }, + { number: 5, denomination: 10, country_code: 'EUR' }, + { number: 3, denomination: 100, country_code: 'GBP' }, + { number: 2, denomination: 50, country_code: 'GBP' }, + ] + + const result = argsToByte('FLOAT_BY_DENOMINATION', { value }, 6) + expect(result).toEqual( + Buffer.from([ + 0x04, 0x04, 0x00, 0x64, 0x00, 0x00, 0x00, 0x45, 0x55, 0x52, 0x05, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x45, 0x55, 0x52, 0x03, 0x00, 0x64, 0x00, + 0x00, 0x00, 0x47, 0x42, 0x50, 0x02, 0x00, 0x32, 0x00, 0x00, 0x00, 0x47, 0x42, 0x50, 0x58, + ]), + ) + }) + + test('FLOAT_BY_DENOMINATION: test', () => { + const value = [ + { number: 4, denomination: 100, country_code: 'EUR' }, + { number: 5, denomination: 10, country_code: 'EUR' }, + { number: 3, denomination: 100, country_code: 'GBP' }, + { number: 2, denomination: 50, country_code: 'GBP' }, + ] + + const result = argsToByte('FLOAT_BY_DENOMINATION', { value, test: true }, 6) + expect(result).toEqual( + Buffer.from([ + 0x04, 0x04, 0x00, 0x64, 0x00, 0x00, 0x00, 0x45, 0x55, 0x52, 0x05, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x45, 0x55, 0x52, 0x03, 0x00, 0x64, 0x00, + 0x00, 0x00, 0x47, 0x42, 0x50, 0x02, 0x00, 0x32, 0x00, 0x00, 0x00, 0x47, 0x42, 0x50, 0x19, + ]), + ) + }) + + test('PAYOUT_BY_DENOMINATION', () => { + const value = [ + { number: 4, denomination: 100, country_code: 'EUR' }, + { number: 5, denomination: 10, country_code: 'EUR' }, + { number: 3, denomination: 100, country_code: 'GBP' }, + { number: 2, denomination: 50, country_code: 'GBP' }, + ] + + const result = argsToByte('PAYOUT_BY_DENOMINATION', { value }, 6) + expect(result).toEqual( + Buffer.from([ + 0x04, 0x04, 0x00, 0x64, 0x00, 0x00, 0x00, 0x45, 0x55, 0x52, 0x05, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x45, 0x55, 0x52, 0x03, 0x00, 0x64, 0x00, + 0x00, 0x00, 0x47, 0x42, 0x50, 0x02, 0x00, 0x32, 0x00, 0x00, 0x00, 0x47, 0x42, 0x50, 0x58, + ]), + ) + }) + + test('PAYOUT_BY_DENOMINATION: test', () => { + const value = [ + { number: 4, denomination: 100, country_code: 'EUR' }, + { number: 5, denomination: 10, country_code: 'EUR' }, + { number: 3, denomination: 100, country_code: 'GBP' }, + { number: 2, denomination: 50, country_code: 'GBP' }, + ] + + const result = argsToByte('PAYOUT_BY_DENOMINATION', { value, test: true }, 6) + expect(result).toEqual( + Buffer.from([ + 0x04, 0x04, 0x00, 0x64, 0x00, 0x00, 0x00, 0x45, 0x55, 0x52, 0x05, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x45, 0x55, 0x52, 0x03, 0x00, 0x64, 0x00, + 0x00, 0x00, 0x47, 0x42, 0x50, 0x02, 0x00, 0x32, 0x00, 0x00, 0x00, 0x47, 0x42, 0x50, 0x19, + ]), + ) + }) + + test('SET_VALUE_REPORTING_TYPE: channel', () => { + const result = argsToByte('SET_VALUE_REPORTING_TYPE', { reportBy: 'channel' }, 6) + expect(result).toEqual(Buffer.from([0x01])) + }) + + test('SET_VALUE_REPORTING_TYPE: value', () => { + const result = argsToByte('SET_VALUE_REPORTING_TYPE', { reportBy: 'value' }, 6) + expect(result).toEqual(Buffer.from([0x00])) + }) + + test('SET_BAUD_RATE: 9600', () => { + const result = argsToByte('SET_BAUD_RATE', { baudrate: 9600, reset_to_default_on_reset: true }, 6) + expect(result).toEqual(Buffer.from([0x00, 0x00])) + }) + + test('SET_BAUD_RATE: 38400', () => { + const result = argsToByte('SET_BAUD_RATE', { baudrate: 38400, reset_to_default_on_reset: true }, 6) + expect(result).toEqual(Buffer.from([0x01, 0x00])) + }) + + test('SET_BAUD_RATE: 115200', () => { + const result = argsToByte('SET_BAUD_RATE', { baudrate: 115200, reset_to_default_on_reset: false }, 6) + expect(result).toEqual(Buffer.from([0x02, 0x01])) + }) + + test('CONFIGURE_BEZEL: non volatile', () => { + const result = argsToByte('CONFIGURE_BEZEL', { RGB: 'FF0000', volatile: false }, 6) + expect(result).toEqual(Buffer.from([0xff, 0x00, 0x00, 0x01])) + }) + + test('CONFIGURE_BEZEL: volatile', () => { + const result = argsToByte('CONFIGURE_BEZEL', { RGB: 'FF0000', volatile: true }, 6) + expect(result).toEqual(Buffer.from([0xff, 0x00, 0x00, 0x00])) + }) + + test('ENABLE_PAYOUT_DEVICE: nv11', () => { + const result = argsToByte('ENABLE_PAYOUT_DEVICE', { GIVE_VALUE_ON_STORED: true, NO_HOLD_NOTE_ON_PAYOUT: true }, 6) + expect(result).toEqual(Buffer.from([0x03])) + }) + + test('ENABLE_PAYOUT_DEVICE: SMART Payout', () => { + const result = argsToByte('ENABLE_PAYOUT_DEVICE', { REQUIRE_FULL_STARTUP: false, OPTIMISE_FOR_PAYIN_SPEED: false }, 6) + expect(result).toEqual(Buffer.from([0x00])) + }) + + test('SET_FIXED_ENCRYPTION_KEY', () => { + const result = argsToByte('SET_FIXED_ENCRYPTION_KEY', { fixedKey: '0123456701234567' }, 6) + expect(result).toEqual(Buffer.from([0x67, 0x45, 0x23, 0x01, 0x67, 0x45, 0x23, 0x01])) + }) + + test('COIN_MECH_OPTIONS', () => { + const result = argsToByte('COIN_MECH_OPTIONS', { ccTalk: false }, 6) + expect(result).toEqual(Buffer.from([0x00])) + }) + + test('COIN_MECH_OPTIONS: ccTalk', () => { + const result = argsToByte('COIN_MECH_OPTIONS', { ccTalk: true }, 6) + expect(result).toEqual(Buffer.from([0x01])) + }) + + test('empty if no args', () => { + const result = argsToByte('RANDOM_COMMAND_TEST', undefined, 6) + expect(result).toEqual(Buffer.alloc(0)) + }) + + test('empty if unknown command', () => { + const result = argsToByte('RANDOM_COMMAND_TEST', { param: true }, 6) + expect(result).toEqual(Buffer.alloc(0)) + }) + + describe('stuffBuffer', () => { + it('should stuff bytes in buffer according to the requirements', () => { + const inputBuffer = Buffer.from([1, 2, 0x7f, 3, 0x7f, 4]) + const expectedBuffer = Buffer.from([1, 2, 0x7f, 0x7f, 3, 0x7f, 0x7f, 4]) + + expect(stuffBuffer(inputBuffer)).toEqual(expectedBuffer) + }) + + it('should not modify buffer if no bytes with value 0x7f are found', () => { + const inputBuffer = Buffer.from([1, 2, 3, 4]) + const expectedBuffer = Buffer.from([1, 2, 3, 4]) + + expect(stuffBuffer(inputBuffer)).toEqual(expectedBuffer) + }) + + it('should return empty buffer when input buffer is empty', () => { + const inputBuffer = Buffer.alloc(0) + const expectedBuffer = Buffer.alloc(0) + + expect(stuffBuffer(inputBuffer)).toEqual(expectedBuffer) + }) + + it('should correctly handle buffer with all bytes as 0x7f', () => { + const inputBuffer = Buffer.from([0x7f, 0x7f, 0x7f]) + const expectedBuffer = Buffer.from([0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f]) + + expect(stuffBuffer(inputBuffer)).toEqual(expectedBuffer) + }) + }) +}) + +describe('validateNodeVersion', () => { + let originalEnginesNode + + beforeEach(() => { + originalEnginesNode = engines.node + }) + + afterEach(() => { + engines.node = originalEnginesNode + }) + + test('should not throw error when Node.js version satisfies the required version', () => { + engines.node = process.version.replace('v', '>=') + + // Mocking the semver.satisfies function + const mockSatisfies = jest.spyOn(semver, 'satisfies').mockReturnValue(true) + + expect(() => { + validateNodeVersion() + }).not.toThrow() + + // Restore the original function + mockSatisfies.mockRestore() + }) + + test('should throw error when Node.js version does not satisfy the required version', () => { + engines.node = process.version.replace('v', '<') + + // Mocking the semver.satisfies function + const mockSatisfies = jest.spyOn(semver, 'satisfies').mockReturnValue(false) + + expect(() => { + validateNodeVersion() + }).toThrow() + + // Restore the original function + mockSatisfies.mockRestore() + }) +}) + +describe('extractPacketData', () => { + const encryptKey = Buffer.from('67452301674523019565000000000000', 'hex') + const count = 0 + + afterEach(() => { + jest.clearAllMocks() + }) + + test('extracts data from a valid packet buffer', () => { + const buffer = Buffer.from([0x7f, 0x80, 0x09, 0xf0, 0x27, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc7, 0xea]) + const expectedData = Buffer.from([0xf0, 0x27, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + + const result = extractPacketData(buffer, null, count) + + expect(result).toEqual(expectedData) + expect(require('chalk').red).not.toHaveBeenCalled() + }) + + test('extracts and decrypts data from an encrypted packet buffer', () => { + const buffer = Buffer.from([ + 0x7f, 0x00, 0x11, 0x7e, 0x70, 0x2f, 0xfe, 0x1d, 0x41, 0x19, 0x44, 0xad, 0xda, 0xd1, 0xdf, 0xd6, 0x77, 0xf5, 0x33, 0xcc, 0x63, 0x1d, + ]) + const expectedData = Buffer.from([0xf0, 0x00, 0x4a, 0x78, 0xb4]) + + const result = extractPacketData(buffer, encryptKey, count, true) + + expect(result).toEqual(expectedData) + expect(require('chalk').red).toHaveBeenCalled() + }) + + test('throws error for unknown response', () => { + const buffer = Buffer.from([0x01, 0x02, 0x03]) + + expect(() => extractPacketData(buffer, null, count)).toThrow('Unknown response') + }) + + test('throws error for wrong CRC16', () => { + const buffer = Buffer.from([ + 0x7f, 0x00, 0x11, 0x7e, 0x70, 0x2f, 0xfe, 0x1d, 0x41, 0x19, 0x44, 0xad, 0xda, 0xd1, 0xdf, 0xd6, 0x77, 0xf5, 0x33, 0xcc, 0x63, 0xff, + ]) + + expect(() => extractPacketData(buffer, null, count)).toThrow('Wrong CRC16') + }) + + test('throws error for encrypted counter mismatch', () => { + const buffer = Buffer.from([ + 0x7f, 0x00, 0x11, 0x7e, 0x70, 0x2f, 0xfe, 0x1d, 0x41, 0x19, 0x44, 0xad, 0xda, 0xd1, 0xdf, 0xd6, 0x77, 0xf5, 0x33, 0xcc, 0x63, 0x1d, + ]) + const count = 1 + + expect(() => extractPacketData(buffer, encryptKey, count)).toThrow('Encrypted counter mismatch') + }) +}) + +describe('generateKeys function', () => { + let originalGeneratePrimeSync + let originalRandomBytes + + beforeEach(() => { + originalGeneratePrimeSync = crypto.generatePrime + originalRandomBytes = crypto.randomBytes + }) + + afterEach(() => { + crypto.generatePrimeSync = originalGeneratePrimeSync + crypto.randomBytes = originalRandomBytes + }) + + test('should return an object with the expected properties', () => { + const keys = generateKeys() + expect(keys).toEqual( + expect.objectContaining({ + generator: expect.any(BigInt), + modulus: expect.any(BigInt), + hostRandom: expect.any(BigInt), + hostInter: expect.any(BigInt), + }), + ) + }) + + test('should swap generator and modulus in case generator < modulus', () => { + crypto.generatePrimeSync = jest.fn().mockReturnValueOnce(7n).mockReturnValueOnce(13n) + + const keys = generateKeys() + expect(keys.generator).toEqual(13n) + expect(keys.modulus).toEqual(7n) + }) + + test('should throw an error if either generator or modulus is zero', () => { + crypto.generatePrimeSync = jest.fn().mockReturnValueOnce(0n) + + expect(generateKeys).toThrow('GENERATOR and MODULUS should be > 0') + }) + + test('should correctly calculate the hostInter value', () => { + crypto.randomBytes = jest.fn().mockReturnValueOnce(Buffer.from([1, 2])) + crypto.generatePrimeSync = jest.fn().mockReturnValueOnce(13n).mockReturnValueOnce(7n) + + // Calculating the expected hostInter value manually + const hostRandom = 513n + const generator = 13n + const modulus = 7n + const expectedHostInter = generator ** hostRandom % modulus + + const keys = generateKeys() + expect(keys.hostInter).toEqual(expectedHostInter) + }) +}) + +// Your updated test suite +describe('getPacket', () => { + // let originalGeneratePrimeSync + let originalRandomBytes + + beforeEach(() => { + // originalGeneratePrimeSync = crypto.generatePrime + originalRandomBytes = crypto.randomBytes + }) + + afterEach(() => { + // crypto.generatePrimeSync = originalGeneratePrimeSync + crypto.randomBytes = originalRandomBytes + }) + + const key = Buffer.from('674523016745230137dd000000000000', 'hex') + + test('throws error when args are missing for a command that requires them', () => { + const args = Buffer.from([]) + const sequence = 1 + + expect(() => getPacket('SET_CHANNEL_INHIBITS', args, sequence)).toThrow('Args missings') + }) + + test('throws error when encryption key is missing for a command that requires encryption', () => { + const args = Buffer.from([1, 2, 3]) + const sequence = 1 + + expect(() => getPacket('PAYOUT_AMOUNT', args, sequence)).toThrow('Command requires ecnryption') + }) + + test('generates packet with encrypted data when encryption key is provided', () => { + const args = argsToByte('SET_CHANNEL_INHIBITS', { channels: Array(5).fill(1) }, 6) + const sequence = 1 + const count = 1 + + const result = getPacket('SET_CHANNEL_INHIBITS', args, sequence, key, count) + expect(result).toBeDefined() + }) + + test('generates packet without encryption when encryption key is not provided', () => { + const sequence = 1 + const result = getPacket('RESET', Buffer.from([]), sequence) + expect(result).toBeDefined() + }) + + test('generates packet with encrypted data for PAYOUT_AMOUNT command when encryption key is provided', () => { + const args = argsToByte('PAYOUT_AMOUNT', { amount: 5, country_code: 'EUR' }, 6) + const sequence = 1 + const count = 1 + + const result = getPacket('PAYOUT_AMOUNT', args, sequence, key, count) + expect(result).toBeDefined() // Assert whatever you expect from the result + }) + + test('throws error when encryption key is missing for PAYOUT_AMOUNT command that requires encryption', () => { + const args = argsToByte('PAYOUT_AMOUNT', { amount: 5, country_code: 'EUR' }, 6) + const sequence = 1 + const count = 1 + + expect(() => getPacket('PAYOUT_AMOUNT', args, sequence, null, count)).toThrow('Command requires ecnryption') + }) +}) + +describe('createSSPHostEncryptionKey', () => { + // Test case 1: Test with sample values + test('should create SSP host encryption key correctly', () => { + const buffer = Buffer.from('0123456789abcdef', 'hex') // Sample buffer + const keys = { + fixedKey: '0123456789abcdef', // Sample fixed key + hostRandom: 12152n, // Sample host random value + modulus: 50459n, // Sample modulus value + } + const result = createSSPHostEncryptionKey(buffer, keys) + expect(result).toHaveProperty('slaveInterKey') + expect(result).toHaveProperty('key') + expect(result).toHaveProperty('encryptKey') + expect(typeof result.slaveInterKey).toBe('bigint') + expect(typeof result.key).toBe('bigint') + expect(result.encryptKey).toBeInstanceOf(Buffer) + }) + + // Test case 2: Test with zero values + test('should handle zero values correctly', () => { + const buffer = Buffer.alloc(8) // Empty buffer + const keys = { + fixedKey: '0000000000000000', // Zero fixed key + hostRandom: 0n, // Zero host random value + modulus: 50459n, // Sample modulus value + } + const result = createSSPHostEncryptionKey(buffer, keys) + expect(result).toHaveProperty('slaveInterKey') + expect(result).toHaveProperty('key') + expect(result).toHaveProperty('encryptKey') + expect(typeof result.slaveInterKey).toBe('bigint') + expect(typeof result.key).toBe('bigint') + expect(result.encryptKey).toBeInstanceOf(Buffer) + }) +}) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..af485cc --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5522 @@ +{ + "name": "@kybarg/ssp", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@kybarg/ssp", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "chalk": "^4.1.2", + "debug": "^4.3.4", + "semver": "^7.6.0", + "serialport": "^12.0.0" + }, + "devDependencies": { + "@babel/eslint-parser": "^7.24.1", + "@babel/plugin-syntax-import-assertions": "^7.24.1", + "esbuild-register": "^3.5.0", + "eslint": "^8.57.0", + "eslint-config-prettier": "^8.10.0", + "eslint-plugin-jest": "^27.9.0", + "eslint-plugin-node": "^11.1.0", + "jest": "^29.7.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", + "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz", + "integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.4", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.24.4", + "@babel/parser": "^7.24.4", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/eslint-parser": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.24.1.tgz", + "integrity": "sha512-d5guuzMlPeDfZIbpQ8+g1NaCNuAGBBGNECh0HVqz1sjOeVLh2CEaifuOysCH18URW6R7pqXINvf5PaR/dC6jLQ==", + "dev": true, + "dependencies": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0", + "eslint": "^7.5.0 || ^8.0.0" + } + }, + "node_modules/@babel/eslint-parser/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz", + "integrity": "sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", + "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", + "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.4.tgz", + "integrity": "sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", + "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz", + "integrity": "sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", + "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", + "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", + "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.1", + "@babel/generator": "^7.24.1", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.24.1", + "@babel/types": "^7.24.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dev": true, + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@serialport/binding-mock": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-10.2.2.tgz", + "integrity": "sha512-HAFzGhk9OuFMpuor7aT5G1ChPgn5qSsklTFOTUX72Rl6p0xwcSVsRtG/xaGp6bxpN7fI9D/S8THLBWbBgS6ldw==", + "dependencies": { + "@serialport/bindings-interface": "^1.2.1", + "debug": "^4.3.3" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@serialport/bindings-cpp": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-12.0.1.tgz", + "integrity": "sha512-r2XOwY2dDvbW7dKqSPIk2gzsr6M6Qpe9+/Ngs94fNaNlcTRCV02PfaoDmRgcubpNVVcLATlxSxPTIDw12dbKOg==", + "hasInstallScript": true, + "dependencies": { + "@serialport/bindings-interface": "1.2.2", + "@serialport/parser-readline": "11.0.0", + "debug": "4.3.4", + "node-addon-api": "7.0.0", + "node-gyp-build": "4.6.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-delimiter": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-11.0.0.tgz", + "integrity": "sha512-aZLJhlRTjSmEwllLG7S4J8s8ctRAS0cbvCpO87smLvl3e4BgzbVgF6Z6zaJd3Aji2uSiYgfedCdNc4L6W+1E2g==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-readline": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-11.0.0.tgz", + "integrity": "sha512-rRAivhRkT3YO28WjmmG4FQX6L+KMb5/ikhyylRfzWPw0nSXy97+u07peS9CbHqaNvJkMhH1locp2H36aGMOEIA==", + "dependencies": { + "@serialport/parser-delimiter": "11.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/bindings-interface": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@serialport/bindings-interface/-/bindings-interface-1.2.2.tgz", + "integrity": "sha512-CJaUd5bLvtM9c5dmO9rPBHPXTa9R2UwpkJ0wdh9JCYcbrPWsKz+ErvR0hBLeo7NPeiFdjFO4sonRljiw4d2XiA==", + "engines": { + "node": "^12.22 || ^14.13 || >=16" + } + }, + "node_modules/@serialport/parser-byte-length": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-12.0.0.tgz", + "integrity": "sha512-0ei0txFAj+s6FTiCJFBJ1T2hpKkX8Md0Pu6dqMrYoirjPskDLJRgZGLqoy3/lnU1bkvHpnJO+9oJ3PB9v8rNlg==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-cctalk": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-12.0.0.tgz", + "integrity": "sha512-0PfLzO9t2X5ufKuBO34DQKLXrCCqS9xz2D0pfuaLNeTkyGUBv426zxoMf3rsMRodDOZNbFblu3Ae84MOQXjnZw==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-delimiter": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-12.0.0.tgz", + "integrity": "sha512-gu26tVt5lQoybhorLTPsH2j2LnX3AOP2x/34+DUSTNaUTzu2fBXw+isVjQJpUBFWu6aeQRZw5bJol5X9Gxjblw==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-inter-byte-timeout": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-12.0.0.tgz", + "integrity": "sha512-GnCh8K0NAESfhCuXAt+FfBRz1Cf9CzIgXfp7SdMgXwrtuUnCC/yuRTUFWRvuzhYKoAo1TL0hhUo77SFHUH1T/w==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-packet-length": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-packet-length/-/parser-packet-length-12.0.0.tgz", + "integrity": "sha512-p1hiCRqvGHHLCN/8ZiPUY/G0zrxd7gtZs251n+cfNTn+87rwcdUeu9Dps3Aadx30/sOGGFL6brIRGK4l/t7MuQ==", + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/@serialport/parser-readline": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-12.0.0.tgz", + "integrity": "sha512-O7cywCWC8PiOMvo/gglEBfAkLjp/SENEML46BXDykfKP5mTPM46XMaX1L0waWU6DXJpBgjaL7+yX6VriVPbN4w==", + "dependencies": { + "@serialport/parser-delimiter": "12.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-ready": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-12.0.0.tgz", + "integrity": "sha512-ygDwj3O4SDpZlbrRUraoXIoIqb8sM7aMKryGjYTIF0JRnKeB1ys8+wIp0RFMdFbO62YriUDextHB5Um5cKFSWg==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-regex": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-12.0.0.tgz", + "integrity": "sha512-dCAVh4P/pZrLcPv9NJ2mvPRBg64L5jXuiRxIlyxxdZGH4WubwXVXY/kBTihQmiAMPxbT3yshSX8f2+feqWsxqA==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-slip-encoder": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-slip-encoder/-/parser-slip-encoder-12.0.0.tgz", + "integrity": "sha512-0APxDGR9YvJXTRfY+uRGhzOhTpU5akSH183RUcwzN7QXh8/1jwFsFLCu0grmAUfi+fItCkR+Xr1TcNJLR13VNA==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-spacepacket": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-spacepacket/-/parser-spacepacket-12.0.0.tgz", + "integrity": "sha512-dozONxhPC/78pntuxpz/NOtVps8qIc/UZzdc/LuPvVsqCoJXiRxOg6ZtCP/W58iibJDKPZPAWPGYeZt9DJxI+Q==", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/stream": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-12.0.0.tgz", + "integrity": "sha512-9On64rhzuqKdOQyiYLYv2lQOh3TZU/D3+IWCR5gk0alPel2nwpp4YwDEGiUBfrQZEdQ6xww0PWkzqth4wqwX3Q==", + "dependencies": { + "@serialport/bindings-interface": "1.2.2", + "debug": "4.3.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", + "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001612", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001612.tgz", + "integrity": "sha512-lFgnZ07UhaCcsSZgWW0K5j4e69dK1u/ltrL9lTUiFOwNHs12S3UMIEYgBV0Z6C6hRDev7iRnMzzYmKabYdXF9g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.749", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.749.tgz", + "integrity": "sha512-LRMMrM9ITOvue0PoBrvNIraVmuDbJV5QC9ierz/z5VilMdPOVMjOtpICNld3PuXuTZ3CHH/UPxX9gHhAPwi+0Q==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "peer": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/esbuild-register": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.5.0.tgz", + "integrity": "sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "esbuild": ">=0.12 <1" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", + "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, + "dependencies": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-jest": { + "version": "27.9.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.9.0.tgz", + "integrity": "sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^5.10.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.0.0 || ^6.0.0 || ^7.0.0", + "eslint": "^7.0.0 || ^8.0.0", + "jest": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "dependencies": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" + } + }, + "node_modules/eslint-plugin-node/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz", + "integrity": "sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-addon-api": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz", + "integrity": "sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==" + }, + "node_modules/node-gyp-build": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/serialport": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/serialport/-/serialport-12.0.0.tgz", + "integrity": "sha512-AmH3D9hHPFmnF/oq/rvigfiAouAKyK/TjnrkwZRYSFZxNggJxwvbAbfYrLeuvq7ktUdhuHdVdSjj852Z55R+uA==", + "dependencies": { + "@serialport/binding-mock": "10.2.2", + "@serialport/bindings-cpp": "12.0.1", + "@serialport/parser-byte-length": "12.0.0", + "@serialport/parser-cctalk": "12.0.0", + "@serialport/parser-delimiter": "12.0.0", + "@serialport/parser-inter-byte-timeout": "12.0.0", + "@serialport/parser-packet-length": "12.0.0", + "@serialport/parser-readline": "12.0.0", + "@serialport/parser-ready": "12.0.0", + "@serialport/parser-regex": "12.0.0", + "@serialport/parser-slip-encoder": "12.0.0", + "@serialport/parser-spacepacket": "12.0.0", + "@serialport/stream": "12.0.0", + "debug": "4.3.4" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", + "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1a4c025 --- /dev/null +++ b/package.json @@ -0,0 +1,64 @@ +{ + "name": "@kybarg/ssp", + "version": "1.0.0", + "description": "Node.JS library Encrypted Smiley ® Secure Protocol (eSSP, SSP)", + "license": "MIT", + "author": { + "name": "Ruslan Kyba", + "email": "kybargr+ssp@gmail.com" + }, + "contributors": [ + { + "name": "Roman Skokov", + "url": "https://github.com/skokov3812" + } + ], + "homepage": "https://github.com/kybarg/ssp#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/kybarg/ssp.git" + }, + "bugs": { + "url": "https://github.com/kybarg/ssp/issues" + }, + "main": "lib/index.js", + "scripts": { + "lint": "eslint lib", + "test": "jest --coverage", + "test:watch": "jest --watch", + "docs": "node scripts/docs.js" + }, + "dependencies": { + "chalk": "^4.1.2", + "debug": "^4.3.4", + "semver": "^7.6.0", + "serialport": "^12.0.0" + }, + "devDependencies": { + "@babel/eslint-parser": "^7.24.1", + "@babel/plugin-syntax-import-assertions": "^7.24.1", + "esbuild-register": "^3.5.0", + "eslint": "^8.57.0", + "eslint-config-prettier": "^8.10.0", + "eslint-plugin-jest": "^27.9.0", + "eslint-plugin-node": "^11.1.0", + "jest": "^29.7.0" + }, + "keywords": [ + "NV11", + "NV200", + "NV22", + "NV9", + "ITL", + "SSP", + "Encrypted", + "Smiley", + "Secure", + "Protocol", + "eSSP" + ], + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + } +} diff --git a/scripts/docs.js b/scripts/docs.js new file mode 100644 index 0000000..aac3293 --- /dev/null +++ b/scripts/docs.js @@ -0,0 +1,157 @@ +const fs = require('node:fs') +const commands = require('../lib/static/commands.json') +const statuses = require('../lib/static/status_desc.json') +let filePath = 'docs/commands.md' + +// Function to make a file empty +function makeFileEmpty() { + fs.writeFileSync(filePath, '') +} + +// Function to add a line to a file +function addLineToFile(lineToAdd) { + fs.appendFileSync(filePath, lineToAdd + '\n', 'utf8') +} + +function decimalToHex(decimal) { + return '0x' + ('0' + decimal.toString(16)).slice(-2) +} + +function convertToTitleCase(str) { + // Replace underscores with spaces + str = str.replace(/_/g, ' ') + + // Convert string to title case + return str + .toLowerCase() + .split(' ') + .map(function (word) { + return word.charAt(0).toUpperCase() + word.slice(1) + }) + .join(' ') +} + +// Make the file empty +makeFileEmpty() + +addLineToFile('') +addLineToFile('[Back to Readme](../README.md)') +addLineToFile('# Commands') + +const sortedCommands = Object.entries(commands).sort(([, a], [, b]) => a.code - b.code) + +sortedCommands.forEach(([name], index) => { + addLineToFile(`${index + 1}. [${convertToTitleCase(name)}](#${name})`) +}) + +sortedCommands.forEach(([name, rest]) => { + const { code, encrypted, args, device, description, example } = rest + + // Add a line to the file + addLineToFile(`## ${name}`) + addLineToFile(`| Code dec | Code hex |`) + addLineToFile(`| --- | --- |`) + addLineToFile(`| ${code} | ${decimalToHex(code)} |`) + addLineToFile('') + + if (encrypted) { + addLineToFile('> [!IMPORTANT]') + addLineToFile('> **Requires encryption**') + addLineToFile('') + } + + if (device) { + addLineToFile('### Devices') + addLineToFile(`${device.map(d => `\`${d}\``).join(', ')}`) + addLineToFile('') + } + + if (description) { + addLineToFile('### Description') + addLineToFile(description) + addLineToFile('') + } + + if (args) { + addLineToFile('### Arguments') + addLineToFile('| Name | Type |') + addLineToFile('| --- | --- |') + + Object.entries(args).forEach(([name, value]) => { + addLineToFile(`| ${name} | \`${value.replace(/\|/g, '\\|')}\` |`) + }) + addLineToFile('') + } + + addLineToFile('### Example') + addLineToFile('```javascript') + if (example) { + addLineToFile(example) + } else { + addLineToFile(`SSP.command('${name}')`) + } + addLineToFile('```') + addLineToFile('') + addLineToFile('

(back to top)

') + addLineToFile('') +}) + +filePath = 'docs/events.md' + +// Make the file empty +makeFileEmpty() + +addLineToFile('') +addLineToFile('[Back to Readme](../README.md)') +addLineToFile('# Events') + +const sortedStatuses = Object.entries(statuses).sort(([a], [b]) => a - b) + +sortedStatuses.forEach(([, { name }], index) => { + addLineToFile(`${index + 1}. [${convertToTitleCase(name)}](#${name})`) +}) + +addLineToFile('') + +sortedStatuses.forEach(([code, rest]) => { + const { name, description, devices, data } = rest + + // Add a line to the file + addLineToFile(`## ${name}`) + addLineToFile(`| Code dec | Code hex |`) + addLineToFile(`| --- | --- |`) + addLineToFile(`| ${code} | ${decimalToHex(code)} |`) + addLineToFile('') + + if (devices) { + addLineToFile('### Devices') + addLineToFile(`${devices.map(d => `\`${d}\``).join(', ')}`) + addLineToFile('') + } + + if (description) { + addLineToFile('### Description') + addLineToFile(description) + addLineToFile('') + } + + addLineToFile('### Data') + if (data) { + Object.entries(data).forEach(([title, args]) => { + addLineToFile(`#### ${title}`) + + addLineToFile('| Name | Type |') + addLineToFile('| --- | --- |') + + Object.entries(args).forEach(([name, value]) => { + addLineToFile(`| ${name} | \`${value.replace(/\|/g, '\\|')}\` |`) + }) + addLineToFile('') + }) + } else { + addLineToFile('This event has no data associated with it') + } + addLineToFile('') + addLineToFile('

(back to top)

') + addLineToFile('') +})