diff --git a/.gitignore b/.gitignore index 1e59e82..61b2c59 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules .nyc_output coverage +.DS_Store diff --git a/examples/buttons.ts b/examples/buttons.ts new file mode 100644 index 0000000..cf5e92a --- /dev/null +++ b/examples/buttons.ts @@ -0,0 +1,48 @@ +/** + * This demo works best if you have a video app like Hulu open. + * But works ok on the home screen as well. + */ +import xboxSmartGlass from "../src"; + +// Start the discovery process. +xboxSmartGlass.startDiscovery(); + +xboxSmartGlass.on("discovery", async (xbox) => { + xboxSmartGlass.stopDiscovery(); + + await xbox.connect(); + + // TODO we need an event for letting us know the channel is ready. + // Also need a way to only turn the channel on if the user wants it. + await new Promise((resolve) => setTimeout(resolve, 5000)); + + // All of the button options can be accessed like this. + // xbox.Buttons.[Button Name] + + // Calling quickPress handles both pressing and releasing a button. + // The button will be pressed for approximately 200ms. + // If you have Hulu open with something playing, you'll now have the seeker bar displayed. + xbox.controller.quickPressButton(xbox.Buttons.DPadRight); + + await new Promise((resolve) => setTimeout(resolve, 2000)); + + // Calls to pressButton do not automatically release. + // If you have Hulu open, you'll now be seeking backwards. + xbox.controller.pressButton(xbox.Buttons.DPadLeft); + + // Wait 2 seconds + await new Promise((resolve) => setTimeout(resolve, 2000)); + + // Now release the button. + xbox.controller.releaseButton(xbox.Buttons.DPadLeft); + + // Wait 2 seconds + await new Promise((resolve) => setTimeout(resolve, 2000)); + + // You can supply a timeout to pressButton and then it WILL automatically + // release after the given time in ms. + await xbox.controller.pressButton(xbox.Buttons.DPadRight, 2000); + + // Quick press A to start playing again. + xbox.controller.quickPressButton(xbox.Buttons.A); +}); diff --git a/examples/connect.js b/examples/connect.js deleted file mode 100755 index abf735d..0000000 --- a/examples/connect.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -var Smartglass = require('../src/smartglass'); - -var deviceStatus = { - current_app: false, - connection_status: false, - client: false -} - -deviceStatus.client = Smartglass() - -deviceStatus.client.connect('192.168.2.5').then(function(){ - console.log('Xbox succesfully connected!'); - deviceStatus.connection_status = true -}, function(error){ - console.log('Failed to connect to xbox:', error); -}); - -deviceStatus.client.on('_on_console_status', function(message, xbox, remote, smartglass){ - if(message.packet_decoded.protected_payload.apps[0] != undefined){ - if(deviceStatus.current_app != message.packet_decoded.protected_payload.apps[0].aum_id){ - deviceStatus.current_app = message.packet_decoded.protected_payload.apps[0].aum_id - console.log('xbox: Current active app:', deviceStatus) - } - } -}.bind(deviceStatus)); - -deviceStatus.client.on('_on_timeout', function(message, xbox, remote, smartglass){ - deviceStatus.connection_status = false - console.log('Connection timed out.') - clearInterval(interval) - - deviceStatus.client = Smartglass() - deviceStatus.client.connect('192.168.2.5').then(function(){ - console.log('Xbox succesfully connected!'); - }, function(error){ - console.log('Failed to connect to xbox:', result); - }); -}.bind(deviceStatus, interval)); - -var interval = setInterval(function(){ - console.log('connection_status:', deviceStatus.client._connection_status) -}.bind(deviceStatus), 5000) diff --git a/examples/connect_and_disconnect.js b/examples/connect_and_disconnect.js deleted file mode 100755 index 591d2bf..0000000 --- a/examples/connect_and_disconnect.js +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env node -var Smartglass = require('../src/smartglass'); - -var deviceStatus = { - current_app: false, - connection_status: false -} - -var sgClient = Smartglass() -sgClient.connect('192.168.2.5').then(function(){ - console.log('Xbox succesfully connected!'); -}, function(error){ - console.log('Failed to connect to xbox:', error); -}); - -sgClient.on('_on_console_status', function(message, xbox, remote, smartglass){ - deviceStatus.connection_status = true - - if(message.packet_decoded.protected_payload.apps[0] != undefined){ - if(deviceStatus.current_app != message.packet_decoded.protected_payload.apps[0].aum_id){ - deviceStatus.current_app = message.packet_decoded.protected_payload.apps[0].aum_id - console.log('Current active app:', deviceStatus) - } - } -}.bind(deviceStatus)); - -sgClient.on('_on_console_status', function(message, xbox, remote, smartglass){ - deviceStatus.connection_status = false - console.log('Sending disconnect to console...') - smartglass.disconnect() -}.bind(deviceStatus)); diff --git a/examples/connect_channels.js b/examples/connect_channels.js deleted file mode 100755 index 3045ed1..0000000 --- a/examples/connect_channels.js +++ /dev/null @@ -1,150 +0,0 @@ -#!/usr/bin/env node -var Smartglass = require('../src/smartglass'); -var SystemInputChannel = require('../src/channels/systeminput'); -var SystemMediaChannel = require('../src/channels/systemmedia'); -var TvRemoteChannel = require('../src/channels/tvremote'); - -var deviceStatus = { - current_app: false, - connection_status: false, - client: false -} - -deviceStatus.client = Smartglass() - -deviceStatus.client.connect('192.168.2.5').then(function(){ - console.log('Xbox succesfully connected!'); - - deviceStatus.client.addManager('system_input', SystemInputChannel()) - deviceStatus.client.addManager('system_media', SystemMediaChannel()) - deviceStatus.client.addManager('tv_remote', TvRemoteChannel()) - - // - // Volume tests - // - - setTimeout(function(){ - deviceStatus.client.getManager('tv_remote').getConfiguration().then(function(configuration){ - console.log('Configuration:', configuration) - }, function(error){ - console.log('Failed to get configuration:', error) - }); - - setTimeout(function(){ - deviceStatus.client.getManager('tv_remote').sendIrCommand('btn.vol_up').then(function(response){ - console.log('send button:', response) - }, function(error){ - console.log(error) - }); - }.bind(deviceStatus), 1500) - - setTimeout(function(){ - deviceStatus.client.getManager('tv_remote').sendIrCommand('btn.vol_down').then(function(response){ - console.log('send button:', response) - }, function(error){ - console.log(error) - }); - }.bind(deviceStatus), 2500) - - }.bind(deviceStatus), 1000) - - // - // Input tests - // - - setTimeout(function(){ - // deviceStatus.client.getManager('tv_remote').getConfiguration().then(function(configuration){ - // console.log('Configuration:', configuration) - // }, function(error){ - // console.log('Failed to get configuration:', error) - // }); - - setTimeout(function(){ - deviceStatus.client.getManager('system_input').sendCommand('nexus').then(function(){ - // Send button - }, function(error){ - console.log(error) - }); - }.bind(deviceStatus), 1000) - - setTimeout(function(){ - deviceStatus.client.getManager('system_input').sendCommand('nexus').then(function(){ - // Send button - }, function(error){ - console.log(error) - }); - }.bind(deviceStatus), 2000) - - }.bind(deviceStatus), 1000) - - // deviceStatus.client.on('_on_console_status', function(message, xbox, remote){ - // // console.log('CONSOLE STATE', message.packet_decoded.protected_payload) - // // console.log(message.packet_decoded.protected_payload) - // console.log(deviceStatus.client.getActiveApp()) - // //deviceStatus.client.getManager('system_input').sendCommand('nexus'); - // }); - - // setTimeout(function(){ - // console.log('Send nexus button') - // deviceStatus.client.getManager('system_input').sendCommand('nexus'); - // - // setTimeout(function(){ - // deviceStatus.client.getManager('system_input').sendCommand('down'); - // }.bind(deviceStatus), 1000) - // - // setTimeout(function(){ - // deviceStatus.client.getManager('tv_remote').sendIrCommand('btn.vol_up'); - // }.bind(deviceStatus), 1500) - // - // setTimeout(function(){ - // deviceStatus.client.getManager('system_input').sendCommand('up'); - // }.bind(deviceStatus), 2000) - // - // setTimeout(function(){ - // deviceStatus.client.getManager('system_input').sendCommand('left'); - // }.bind(deviceStatus), 3000) - // - // setTimeout(function(){ - // deviceStatus.client.getManager('tv_remote').sendIrCommand('btn.vol_down'); - // }.bind(deviceStatus), 3500) - // - // setTimeout(function(){ - // deviceStatus.client.getManager('system_input').sendCommand('right'); - // }.bind(deviceStatus), 4000) - // - // setTimeout(function(){ - // deviceStatus.client.getManager('system_media').sendCommand('pause'); - // }.bind(deviceStatus), 500) - // - // setTimeout(function(){ - // deviceStatus.client.getManager('system_input').sendCommand('nexus'); - // - // console.log(deviceStatus.client.getActiveApp()) - // console.log(deviceStatus.client.getManager('system_media').getState()) - // }.bind(deviceStatus), 5000) - // - // setTimeout(function(){ - // deviceStatus.client.getManager('system_media').sendCommand('play'); - // }.bind(deviceStatus), 2500) - // - // }.bind(deviceStatus), 5000) -}.bind(deviceStatus), function(error){ - console.log('Failed to connect to xbox:', error); -}); - -deviceStatus.client.on('_on_timeout', function(message, xbox, remote, smartglass){ - deviceStatus.connection_status = false - console.log('Connection timed out.') - clearInterval(interval) - - deviceStatus.client = Smartglass() - deviceStatus.client.connect('192.168.2.5').then(function(){ - console.log('Xbox succesfully connected!'); - }, function(error){ - console.log('Failed to connect to xbox:', error); - }); -}.bind(deviceStatus, interval)); - -var interval = setInterval(function(){ - console.log('connection_status:', deviceStatus.client._connection_status) -}.bind(deviceStatus), 5000) diff --git a/examples/discovery.js b/examples/discovery.js deleted file mode 100755 index 0e71ef0..0000000 --- a/examples/discovery.js +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env node -var Smartglass = require('../src/smartglass'); // require('xbox-smartglass-core-node'); - -Smartglass().discovery().then(function(consoles){ - for(var xbox in consoles){ - console.log('- Device found: ' + consoles[xbox].message.name); - console.log(' Address: '+ consoles[xbox].remote.address + ':' + consoles[xbox].remote.port); - } - if(consoles.length == 0){ - console.log('No consoles found on the network') - } -}, function(error){ - console.log(error) -}); diff --git a/examples/discovery.ts b/examples/discovery.ts new file mode 100644 index 0000000..62f501e --- /dev/null +++ b/examples/discovery.ts @@ -0,0 +1,18 @@ +import xboxSmartGlass from "../src"; + +// Start the discovery process. +xboxSmartGlass.startDiscovery(); + +xboxSmartGlass.on("discovery", (xbox) => { + // Every time a discovery response is received from an Xbox, + // log that Xbox's ip address and liveId. + console.log( + `Discovered xbox with ip address "${xbox.ip}" and liveId "${xbox.liveId}"` + ); + + // You can always see all discovered Xboxes by calling + // xboxSmartGlass.getXboxConsoles(). + console.log( + `Total discovered consoles: ${xboxSmartGlass.getXboxConsoles().length}` + ); +}); diff --git a/examples/disposeXbox.ts b/examples/disposeXbox.ts new file mode 100644 index 0000000..3903692 --- /dev/null +++ b/examples/disposeXbox.ts @@ -0,0 +1,43 @@ +/** + * In some cases, you may wish to dispose of an Xbox instance + * and have it removed from memory. In order to do this, we need to + * shut down its socket and remove all of its listeners. + * + * ! ! ! ! ! + * Once you dispose of an Xbox instance it cannot be revived. The behavior of + * the Xbox instance is undefined once it is disposed. Do not hold a reference to it + * after you call dispose. + */ +import xboxSmartGlass from "../src"; + +// Start listening for Xbox consoles. +xboxSmartGlass.startDiscovery(); + +// Emits an Xbox console every time a discovery response is received. +xboxSmartGlass.once("discovery", async (xbox) => { + xboxSmartGlass.stopDiscovery(); + + await xbox.connect(); + + console.log("Successfully connected to Xbox"); + + // Demonstrating that xboxSmartGlass currently knows about this Xbox. + console.log( + `xboxSmartGlass has a reference to this xbox: ${xboxSmartGlass + .getXboxConsoles() + .includes(xbox)}` + ); + + xbox.dispose(); + + console.log( + "Xbox has been disposed. Logs should indicate a complete shutdown" + ); + + // Demonstrating that xboxSmartGlass does NOT currently knows about this Xbox. + console.log( + `xboxSmartGlass has a reference to this xbox: ${xboxSmartGlass + .getXboxConsoles() + .includes(xbox)}` + ); +}); diff --git a/examples/disposeXboxSmartGlass.ts b/examples/disposeXboxSmartGlass.ts new file mode 100644 index 0000000..2912079 --- /dev/null +++ b/examples/disposeXboxSmartGlass.ts @@ -0,0 +1,29 @@ +/** + * ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! + * Only call `xboxSmartGlass.dispose()` when you are completely done with it + * and all Xbox instances. It cannot be revived. And since xboxSmartGlass is a singleton + * you cannot make a new one. + * + * This should only be called in order to cleanly shutdown your NodeJS process. + * + * Behavior of xboxSmartGlass and all Xbox instances is undefined after being disposed. + */ +import xboxSmartGlass from "../src"; + +// Start listening for Xbox consoles, just to demonstrate +// that the process will cleanly exit once disposed. +xboxSmartGlass.startDiscovery(); + +// Emits an Xbox console every time a discovery response is received. +xboxSmartGlass.on("discovery", async (xbox) => { + console.log("Received Xbox discovery response!"); +}); + +xboxSmartGlass.on("disposed", () => { + console.log("XboxSmartGlass has been disposed!"); + console.log("You can now safely exit."); +}); + +setTimeout(() => { + xboxSmartGlass.dispose(); +}, 5000); diff --git a/examples/double_connect.js b/examples/double_connect.js deleted file mode 100755 index a6c63ea..0000000 --- a/examples/double_connect.js +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env node -var Smartglass = require('../src/smartglass'); - -var deviceStatus = { - current_app: false, - connection_status: false, - clients: [] -} - -var client1 = Smartglass() -var client2 = Smartglass() - -client1.connect('192.168.2.5').then(function(){ - console.log('Xbox succesfully connected!'); -}); - -setTimeout(function(){ - client2.connect('192.168.2.5').then(function(){ - console.log('Xbox succesfully connected!'); - }); -}.bind(deviceStatus), 10000) - -// deviceStatus.clients[0].on('_on_console_status', function(message, xbox, remote, smartglass){ -// deviceStatus.connection_status = true -// -// if(message.packet_decoded.protected_payload.apps[0] != undefined){ -// if(deviceStatus.current_app != message.packet_decoded.protected_payload.apps[0].aum_id){ -// deviceStatus.current_app = message.packet_decoded.protected_payload.apps[0].aum_id -// console.log('Current active app:', deviceStatus) -// } -// } -// }.bind(deviceStatus)); - -// deviceStatus.clients[0].on('_on_timeout', function(message, xbox, remote, smartglass){ -// deviceStatus.connection_status = false -// console.log('Connection timed out.') -// clearInterval(interval) -// -// deviceStatus.client = Smartglass() -// deviceStatus.client.connect({ -// ip: '192.168.2.5' -// }, function(result){ -// if(result === true){ -// console.log('Xbox succesfully connected!'); -// } else { -// console.log('Failed to connect to xbox:', result); -// } -// }); -// }.bind(deviceStatus, interval)); - -var interval = setInterval(function(){ - console.log('connection_status:') - console.log('- 1:', client1._connection_status) - console.log('- 2:', client2._connection_status) -}.bind(deviceStatus), 5000) diff --git a/examples/onAndOff.ts b/examples/onAndOff.ts new file mode 100644 index 0000000..0016575 --- /dev/null +++ b/examples/onAndOff.ts @@ -0,0 +1,51 @@ +/** + * This demo will do the following: + * 1. Start the discovery process. + * 2. Listen for discovered Xboxes. + * 3. Stop the discovery process once the first Xbox is discovered. + * 4. Setup listeners to turn on and off the Xbox. + * 5. Constantly press the Nexus button every 3 seconds. + * + * When the demo is run, you should see your Xbox turn on and of forever. + * While the Xbox is on, you should see the side menu toggle in and out. + * Automatic reconnection is enabled by default. + * This can be turned off by calling `xbox.setAutoReconnect(false);` + */ +import xboxSmartGlass from "../src"; + +// Start listening for Xbox consoles. +xboxSmartGlass.startDiscovery(); + +// Emits an Xbox console every time a discovery response is received. +xboxSmartGlass.once("discovery", async (xbox) => { + // Stop listening now that we have a console. + // We won't receive any more discover events now. + xboxSmartGlass.stopDiscovery(); + + // Whenever the Xbox becomes disconnected, + // we'll wait 15 seconds and power it on. + xbox.on("disconnected", () => { + setTimeout(() => { + // Adding a longer timeout here, since we just turned it off + // it may need some time to shutdown before we can turn it on again. + xbox.powerOn(30000); + }, 15000); + }); + + // Whenever the Xbox is connected, + // we'll wait 30 seconds and power it off. + xbox.on("connected", () => { + setTimeout(() => { + xbox.powerOff(); + }, 30000); + }); + + // Connect to the discovered xbox. + await xbox.connect(); + + // Constantly do a quick press of the Nexus button + // every 3 seconds, just to demonstrate we're connected. + setInterval(() => { + xbox.controller.quickPressButton(xbox.Buttons.Nexus); + }, 3000); +}); diff --git a/examples/powerOff.ts b/examples/powerOff.ts new file mode 100644 index 0000000..3910b88 --- /dev/null +++ b/examples/powerOff.ts @@ -0,0 +1,20 @@ +import xboxSmartGlass from "../src"; + +// Start the discovery process. +xboxSmartGlass.startDiscovery(); + +xboxSmartGlass.once("discovery", async (xbox) => { + // We found an xbox, so we can stop the discovery process. + xboxSmartGlass.stopDiscovery(); + + console.log( + `Discovered xbox with ip address "${xbox.ip}" and liveId "${xbox.liveId}"` + ); + + // You must connect to the Xbox before you can power it off. + // Generally, you should wrap calls to connect in a try/catch. + await xbox.connect(); + + console.log("Connected to Xbox! Powering off!"); + xbox.powerOff(); +}); diff --git a/examples/powerOn.ts b/examples/powerOn.ts new file mode 100644 index 0000000..7f7b7d2 --- /dev/null +++ b/examples/powerOn.ts @@ -0,0 +1,30 @@ +/** + * This demonstrates powering on an Xbox when you know its liveId and ip address. + * This can be used to power on an Xbox that was not on when your Node.js server started. + * In order to get the liveId and ip address, you will first need to discover the Xbox through + * the discovery process. You can then store those values somewhere and use them like this + * to power on an undiscovered Xbox. + */ +import xboxSmartGlass from "../src"; + +const powerOnDemo = async () => { + try { + // An optional timeout can be provided to indicate how long you would like to + // attempt to turn on the console. If the timeout is omitted, the default timeout + // will be used. + const xbox = await xboxSmartGlass.powerOn( + "FD00000000000000", // Put your liveId here + "10.0.0.195", + 20000 + ); + + console.log(`Xbox turned on! Has ip address of ${xbox.ip}`); + + // Now we have the Xbox we just turned on, we can now connect to it if we want. + // await Xbox.connect(); + } catch (err) { + console.error("Failed to turn on Xbox", err); + } +}; + +powerOnDemo(); diff --git a/examples/poweroff.js b/examples/poweroff.js deleted file mode 100755 index 1f52f96..0000000 --- a/examples/poweroff.js +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env node -var Smartglass = require('../src/smartglass'); - -var sgClient = Smartglass() - -sgClient.connect('192.168.2.5').then(function(){ - console.log('Xbox succesfully connected!'); - - setTimeout(function(){ - sgClient.powerOff().then(function(status){ - console.log('Shutdown succes!') - }, function(error){ - console.log('Shutdown error:', error) - }) - }.bind(sgClient), 1000) -}, function(error){ - console.log(error) -}); diff --git a/examples/poweron.js b/examples/poweron.js deleted file mode 100755 index fcf9e27..0000000 --- a/examples/poweron.js +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env node -var Smartglass = require('../src/smartglass'); - -Smartglass().powerOn({ - live_id: 'FD00000000000000', - tries: 5, - ip: '192.168.2.5' -}).then(function(response){ - console.log('Console booted:', response) -}, function(error){ - console.log('Booting console failed:', error) -}); diff --git a/package-lock.json b/package-lock.json index 0939984..878b37a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,359 +1,1549 @@ { "name": "xbox-smartglass-core-node", "version": "0.6.10", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "packages": { + "": { + "name": "xbox-smartglass-core-node", + "version": "0.6.10", + "license": "MIT", + "devDependencies": { + "@types/chai": "^4.3.1", + "@types/debug": "^4.1.7", + "@types/elliptic": "^6.4.14", + "@types/jasmine": "^4.0.3", + "@types/jsrsasign": "^10.2.1", + "@types/mocha": "^9.1.1", + "@types/node": "^17.0.25", + "@types/uuid": "^8.3.4", + "@types/uuid-parse": "^1.0.0", + "chai": "^4.3.6", + "debug": "^4.3.4", + "elliptic": "^6.5.4", + "jsrsasign": "^10.5.17", + "mocha": "^9.2.2", + "prettier": "2.6.2", + "ts-node": "^10.7.0", + "uuid": "^8.3.2", + "uuid-parse": "^1.1.0" + } + }, + "node_modules/@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-consumer": "0.8.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "node_modules/@types/bn.js": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.0.tgz", + "integrity": "sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/chai": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.1.tgz", + "integrity": "sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==", + "dev": true + }, + "node_modules/@types/debug": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", + "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", + "dev": true, + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/elliptic": { + "version": "6.4.14", + "resolved": "https://registry.npmjs.org/@types/elliptic/-/elliptic-6.4.14.tgz", + "integrity": "sha512-z4OBcDAU0GVwDTuwJzQCiL6188QvZMkvoERgcVjq0/mPM8jCfdwZ3x5zQEVoL9WCAru3aG5wl3Z5Ww5wBWn7ZQ==", + "dev": true, + "dependencies": { + "@types/bn.js": "*" + } + }, + "node_modules/@types/jasmine": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.3.tgz", + "integrity": "sha512-Opp1LvvEuZdk8fSSvchK2mZwhVrsNT0JgJE9Di6MjnaIpmEXM8TLCPPrVtNTYh8+5MPdY8j9bAHMu2SSfwpZJg==", + "dev": true + }, + "node_modules/@types/jsrsasign": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/@types/jsrsasign/-/jsrsasign-10.2.1.tgz", + "integrity": "sha512-piCIOMY0+d2wwRNcRw56VBqFYCYYeZ1c/NlUKVHTV3Y9j1RE2qpgCQvClI6yhH2sk8OoXiah43i9FmnC5tL2RQ==", + "dev": true + }, + "node_modules/@types/mocha": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", + "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", + "dev": true + }, + "node_modules/@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "17.0.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.27.tgz", + "integrity": "sha512-4/Ke7bbWOasuT3kceBZFGakP1dYN2XFd8v2l9bqF2LNWrmeU07JLpp56aEeG6+Q3olqO5TvXpW0yaiYnZJ5CXg==", + "dev": true + }, + "node_modules/@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", + "dev": true + }, + "node_modules/@types/uuid-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid-parse/-/uuid-parse-1.0.0.tgz", + "integrity": "sha512-iptjRzo+N2P5MWRIZSe1iTfi6jV5WdpuC5XGZZw04LSWQ0C9QTicdpHUt93myV9Tgf0wK+E4O8RE+wq+V1zIkA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "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.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "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/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "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/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "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/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "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/chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "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/chalk/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==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "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==", + "dev": true, + "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==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.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/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "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/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": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.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-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/glob/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/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "engines": { + "node": ">=4.x" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "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-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "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-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-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "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": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "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/jsrsasign": { + "version": "10.5.20", + "resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-10.5.20.tgz", + "integrity": "sha512-YHL6y8o6cnRoxwUY0eGpfvwj0pm9o0NToD4KXVp2UJC19oXSR2RgnSdSMplIRRKFovLAJGrAHqdb5MMznnhQDQ==", + "dev": true, + "funding": { + "url": "https://github.com/kjur/jsrsasign#donations" + } + }, + "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/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loupe": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", + "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.0" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "node_modules/minimatch": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", + "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", + "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", + "dev": true, + "dependencies": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.3", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "4.2.1", + "ms": "2.1.3", + "nanoid": "3.3.1", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "workerpool": "6.2.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mocha/node_modules/debug/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==", + "dev": true + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "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==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "@babel/core": { - "version": "7.12.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.3.tgz", - "integrity": "sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g==", + "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, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.1", - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helpers": "^7.12.1", - "@babel/parser": "^7.12.3", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.19", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "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": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "@babel/generator": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.5.tgz", - "integrity": "sha512-m16TQQJ8hPt7E+OS/XVQg/7U184MLXtvuGbCdA7na61vha+ImkyyNM/9DDA0unYCVZn3ZOhng+qz48/KBOT96A==", + "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, - "requires": { - "@babel/types": "^7.12.5", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "engines": { + "node": ">=8" } }, - "@babel/helper-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", - "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "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": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" + "engines": { + "node": ">=0.10.0" } }, - "@babel/helper-get-function-arity": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", - "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true, - "requires": { - "@babel/types": "^7.10.4" + "engines": { + "node": "*" } }, - "@babel/helper-member-expression-to-functions": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.1.tgz", - "integrity": "sha512-k0CIe3tXUKTRSoEx1LQEPFU9vRQfqHtl+kf8eNnDqb4AUJEy5pz6aIiog+YWtVm2jpggjS1laH68bPsR+KWWPQ==", + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, - "requires": { - "@babel/types": "^7.12.1" + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "@babel/helper-module-imports": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz", - "integrity": "sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==", + "node_modules/prettier": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz", + "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==", "dev": true, - "requires": { - "@babel/types": "^7.12.5" + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "@babel/helper-module-transforms": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz", - "integrity": "sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==", + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.12.1", - "@babel/helper-replace-supers": "^7.12.1", - "@babel/helper-simple-access": "^7.12.1", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/helper-validator-identifier": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1", - "lodash": "^4.17.19" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", - "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, - "requires": { - "@babel/types": "^7.10.4" + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" } }, - "@babel/helper-replace-supers": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.5.tgz", - "integrity": "sha512-5YILoed0ZyIpF4gKcpZitEnXEJ9UoDRki1Ey6xz46rxOzfNMAhVIJMoune1hmPVxh40LRv1+oafz7UsWX+vyWA==", + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.12.1", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/traverse": "^7.12.5", - "@babel/types": "^7.12.5" + "engines": { + "node": ">=0.10.0" } }, - "@babel/helper-simple-access": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz", - "integrity": "sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==", + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true, - "requires": { - "@babel/types": "^7.12.1" + "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/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" } }, - "@babel/helper-split-export-declaration": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", - "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "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, - "requires": { - "@babel/types": "^7.11.0" + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", - "dev": true + "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" + } }, - "@babel/helpers": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz", - "integrity": "sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==", + "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, - "requires": { - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.5", - "@babel/types": "^7.12.5" + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "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, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "dependencies": { + "has-flag": "^4.0.0" }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "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": { - "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, - "requires": { - "color-convert": "^1.9.0" - } - }, - "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, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "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, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-node": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", + "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.0", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true }, - "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, - "requires": { - "has-flag": "^3.0.0" - } + "@swc/wasm": { + "optional": true } } }, - "@babel/parser": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.5.tgz", - "integrity": "sha512-FVM6RZQ0mn2KCf1VUED7KepYeUWoVShczewOCfm3nzoBybaih51h+sYVVGthW9M6lPByEPTQf+xm27PBdlpwmQ==", + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "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/typescript": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", + "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/uuid-parse": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/uuid-parse/-/uuid-parse-1.1.0.tgz", + "integrity": "sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A==", + "dev": true + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "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/workerpool": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", + "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", + "dev": true + }, + "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": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "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/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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" + } + } + }, + "dependencies": { + "@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true + }, + "@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "requires": { + "@cspotcode/source-map-consumer": "0.8.0" + } + }, + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "@types/bn.js": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.0.tgz", + "integrity": "sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/chai": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.1.tgz", + "integrity": "sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==", "dev": true }, - "@babel/template": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", - "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "@types/debug": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", + "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", "dev": true, "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4" + "@types/ms": "*" } }, - "@babel/traverse": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.5.tgz", - "integrity": "sha512-xa15FbQnias7z9a62LwYAA5SZZPkHIXpd42C6uW68o8uTuua96FHZy1y61Va5P/i83FAAcMpW8+A/QayntzuqA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.5", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.12.5", - "@babel/types": "^7.12.5", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "@babel/types": { - "version": "7.12.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.6.tgz", - "integrity": "sha512-hwyjw6GvjBLiyy3W0YQf0Z5Zf4NpYejUnKFcfcUhZCSffoBBp30w6wP2Wn6pk31jMYZvcOrB/1b7cGXvEoKogA==", + "@types/elliptic": { + "version": "6.4.14", + "resolved": "https://registry.npmjs.org/@types/elliptic/-/elliptic-6.4.14.tgz", + "integrity": "sha512-z4OBcDAU0GVwDTuwJzQCiL6188QvZMkvoERgcVjq0/mPM8jCfdwZ3x5zQEVoL9WCAru3aG5wl3Z5Ww5wBWn7ZQ==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" + "@types/bn.js": "*" } }, - "@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==", + "@types/jasmine": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.3.tgz", + "integrity": "sha512-Opp1LvvEuZdk8fSSvchK2mZwhVrsNT0JgJE9Di6MjnaIpmEXM8TLCPPrVtNTYh8+5MPdY8j9bAHMu2SSfwpZJg==", + "dev": true + }, + "@types/jsrsasign": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/@types/jsrsasign/-/jsrsasign-10.2.1.tgz", + "integrity": "sha512-piCIOMY0+d2wwRNcRw56VBqFYCYYeZ1c/NlUKVHTV3Y9j1RE2qpgCQvClI6yhH2sk8OoXiah43i9FmnC5tL2RQ==", + "dev": true + }, + "@types/mocha": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", + "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", + "dev": true + }, + "@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", + "dev": true + }, + "@types/node": { + "version": "17.0.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.27.tgz", + "integrity": "sha512-4/Ke7bbWOasuT3kceBZFGakP1dYN2XFd8v2l9bqF2LNWrmeU07JLpp56aEeG6+Q3olqO5TvXpW0yaiYnZJ5CXg==", + "dev": true + }, + "@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", + "dev": true + }, + "@types/uuid-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid-parse/-/uuid-parse-1.0.0.tgz", + "integrity": "sha512-iptjRzo+N2P5MWRIZSe1iTfi6jV5WdpuC5XGZZw04LSWQ0C9QTicdpHUt93myV9Tgf0wK+E4O8RE+wq+V1zIkA==", "dev": true, "requires": { - "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" - }, - "dependencies": { - "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, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "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, - "requires": { - "p-locate": "^4.1.0" - } - }, - "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, - "requires": { - "p-try": "^2.0.0" - } - }, - "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, - "requires": { - "p-limit": "^2.2.0" - } - } + "@types/node": "*" } }, - "@istanbuljs/schema": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", - "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", - "dev": true - }, "@ungap/promise-all-settled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", "dev": true }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } + "acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true }, "ansi-colors": { "version": "4.1.1", @@ -386,34 +1576,28 @@ "picomatch": "^2.0.4" } }, - "append-transform": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", - "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", - "dev": true, - "requires": { - "default-require-extensions": "^3.0.0" - } - }, - "archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } + "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 + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true }, "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, "binary-extensions": { @@ -425,7 +1609,8 @@ "bn.js": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true }, "brace-expansion": { "version": "1.1.11", @@ -449,7 +1634,8 @@ "brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true }, "browser-stdout": { "version": "1.3.1", @@ -457,24 +1643,27 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, - "caching-transform": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", - "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", "dev": true, "requires": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" } }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -483,12 +1672,29 @@ "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" + }, + "dependencies": { + "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==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, "chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, "requires": { "anymatch": "~3.1.2", @@ -501,12 +1707,6 @@ "readdirp": "~3.6.0" } }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -516,34 +1716,6 @@ "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "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 - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } } }, "color-convert": { @@ -561,67 +1733,40 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, - "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, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true }, "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "requires": { "ms": "2.1.2" } }, "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true }, - "default-require-extensions": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", - "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, "requires": { - "strip-bom": "^4.0.0" + "type-detect": "^4.0.0" } }, "diff": { @@ -634,6 +1779,7 @@ "version": "6.5.4", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, "requires": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -642,13 +1788,6 @@ "inherits": "^2.0.4", "minimalistic-assert": "^1.0.1", "minimalistic-crypto-utils": "^1.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - } } }, "emoji-regex": { @@ -657,12 +1796,6 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true - }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -675,12 +1808,6 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, - "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 - }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -690,17 +1817,6 @@ "to-regex-range": "^5.0.1" } }, - "find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - } - }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -717,22 +1833,6 @@ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true }, - "foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - } - }, - "fromentries": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", - "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", - "dev": true - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -746,34 +1846,22 @@ "dev": true, "optional": true }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "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 - }, "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 }, - "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==", + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -782,6 +1870,17 @@ "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" + }, + "dependencies": { + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } } }, "glob-parent": { @@ -793,33 +1892,12 @@ "is-glob": "^4.0.1" } }, - "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 - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -830,60 +1908,29 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, "requires": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" } }, - "hasha": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", - "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", - "dev": true, - "requires": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" - } - }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, - "hex-to-binary": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hex-to-binary/-/hex-to-binary-1.0.1.tgz", - "integrity": "sha1-YcevAW/CK86pcE2fLpo46MfbuFQ=" - }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, "requires": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", "minimalistic-crypto-utils": "^1.0.1" } }, - "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 - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -895,9 +1942,10 @@ } }, "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "is-binary-path": { "version": "2.1.0", @@ -908,21 +1956,18 @@ "binary-extensions": "^2.0.0" } }, - "is-core-module": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.1.0.tgz", - "integrity": "sha512-YcV7BgVMRFRua2FqQzKtTDMz8iCuLEyGKjr70q8Zm1yy2qKcurbFEd79PAdHV77oL3NrAaOVQIbMmiHQCHB7ZA==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, + "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 + }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -940,165 +1985,36 @@ }, "is-plain-obj": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", - "dev": true - }, - "istanbul-lib-hook": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", - "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", - "dev": true, - "requires": { - "append-transform": "^2.0.0" - } - }, - "istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "requires": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - } - }, - "istanbul-lib-processinfo": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", - "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", - "dev": true, - "requires": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.0", - "istanbul-lib-coverage": "^3.0.0-alpha.1", - "make-dir": "^3.0.0", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "uuid": "^3.3.3" - }, - "dependencies": { - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - } - } - }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", - "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "dependencies": { - "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 - } - } - }, - "istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true }, - "js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, - "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "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, "requires": { - "minimist": "^1.2.5" + "argparse": "^2.0.1" } }, "jsrsasign": { - "version": "10.5.1", - "resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-10.5.1.tgz", - "integrity": "sha512-yW0fq87KNZFw4Pn5ySllXs3ztZAROQZczEheKZTqmiNpCe/Xj9r5NhuAQ7MXTOyEZGJ/+MPHGTsfbgPFaLpwHQ==" + "version": "10.5.20", + "resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-10.5.20.tgz", + "integrity": "sha512-YHL6y8o6cnRoxwUY0eGpfvwj0pm9o0NToD4KXVp2UJC19oXSR2RgnSdSMplIRRKFovLAJGrAHqdb5MMznnhQDQ==", + "dev": true }, "locate-path": { "version": "6.0.0", @@ -1109,18 +2025,6 @@ "p-locate": "^5.0.0" } }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", - "dev": true - }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -1131,82 +2035,78 @@ "is-unicode-supported": "^0.1.0" } }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "loupe": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", + "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", "dev": true, "requires": { - "semver": "^6.0.0" + "get-func-name": "^2.0.0" } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true }, "minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", + "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", "dev": true, "requires": { "brace-expansion": "^1.1.7" } }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, "mocha": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.3.tgz", - "integrity": "sha512-Xcpl9FqXOAYqI3j79pEtHBBnQgVXIhpULjGQa7DVb0Po+VzmSIK9kanAiWLHoRR/dbZ2qpdPshuXr8l1VaHCzw==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", + "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", "dev": true, "requires": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.5.2", - "debug": "4.3.2", + "chokidar": "3.5.3", + "debug": "4.3.3", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.1.7", + "glob": "7.2.0", "growl": "1.10.5", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", - "minimatch": "3.0.4", + "minimatch": "4.2.1", "ms": "2.1.3", - "nanoid": "3.1.25", + "nanoid": "3.3.1", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", "which": "2.0.2", - "workerpool": "6.1.5", + "workerpool": "6.2.0", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" }, "dependencies": { - "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 - }, "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "requires": { "ms": "2.1.2" @@ -1220,214 +2120,32 @@ } } }, - "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, - "requires": { - "argparse": "^2.0.1" - } - }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true - }, - "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, - "requires": { - "has-flag": "^4.0.0" - } } } }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "nanoid": { - "version": "3.1.25", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", - "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", "dev": true }, - "node-preload": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", - "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", - "dev": true, - "requires": { - "process-on-spawn": "^1.0.0" - } - }, "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 }, - "nyc": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", - "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", - "dev": true, - "requires": { - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "caching-transform": "^4.0.0", - "convert-source-map": "^1.7.0", - "decamelize": "^1.2.0", - "find-cache-dir": "^3.2.0", - "find-up": "^4.1.0", - "foreground-child": "^2.0.0", - "get-package-type": "^0.1.0", - "glob": "^7.1.6", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-hook": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", - "istanbul-lib-processinfo": "^2.0.2", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "make-dir": "^3.0.0", - "node-preload": "^0.2.1", - "p-map": "^3.0.0", - "process-on-spawn": "^1.0.0", - "resolve-from": "^5.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "spawn-wrap": "^2.0.0", - "test-exclude": "^6.0.0", - "yargs": "^15.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "", - "dev": true - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "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, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "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 - }, - "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, - "requires": { - "p-locate": "^4.1.0" - } - }, - "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, - "requires": { - "p-try": "^2.0.0" - } - }, - "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, - "requires": { - "p-limit": "^2.2.0" - } - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1438,12 +2156,12 @@ } }, "p-limit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", - "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "yocto-queue": "^0.1.0" } }, "p-locate": { @@ -1455,33 +2173,6 @@ "p-limit": "^3.0.2" } }, - "p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "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 - }, - "package-hash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", - "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.15", - "hasha": "^5.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" - } - }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -1494,80 +2185,23 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, - "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 - }, - "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==", + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true }, "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, - "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, - "requires": { - "find-up": "^4.0.0" - }, - "dependencies": { - "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, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "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, - "requires": { - "p-locate": "^4.1.0" - } - }, - "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, - "requires": { - "p-try": "^2.0.0" - } - }, - "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, - "requires": { - "p-limit": "^2.2.0" - } - } - } - }, - "process-on-spawn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", - "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", - "dev": true, - "requires": { - "fromentries": "^1.2.0" - } + "prettier": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz", + "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==", + "dev": true }, "randombytes": { "version": "2.1.0", @@ -1587,64 +2221,18 @@ "picomatch": "^2.2.1" } }, - "release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", - "dev": true, - "requires": { - "es6-error": "^4.0.1" - } - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "resolve": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", - "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", - "dev": true, - "requires": { - "is-core-module": "^2.1.0", - "path-parse": "^1.0.6" - } - }, - "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 - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, "serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -1654,65 +2242,26 @@ "randombytes": "^2.1.0" } }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "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==", + "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, "requires": { - "shebang-regex": "^3.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, - "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 - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "spawn-wrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", - "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "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, "requires": { - "foreground-child": "^2.0.0", - "is-windows": "^1.0.2", - "make-dir": "^3.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "which": "^2.0.1" + "ansi-regex": "^5.0.1" } }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "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 - }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -1720,31 +2269,14 @@ "dev": true }, "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==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "requires": { "has-flag": "^4.0.0" } }, - "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, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -1754,30 +2286,65 @@ "is-number": "^7.0.0" } }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "ts-node": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", + "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.0", + "yn": "3.1.1" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + } + } + }, + "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 }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "typescript": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", + "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } + "peer": true }, "uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true }, "uuid-parse": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/uuid-parse/-/uuid-parse-1.1.0.tgz", - "integrity": "sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A==" + "integrity": "sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A==", + "dev": true + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true }, "which": { "version": "2.0.2", @@ -1788,16 +2355,10 @@ "isexe": "^2.0.0" } }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, "workerpool": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz", - "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", + "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", "dev": true }, "wrap-ansi": { @@ -1809,40 +2370,6 @@ "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" - }, - "dependencies": { - "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 - }, - "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 - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } } }, "wrappy": { @@ -1851,22 +2378,10 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, "y18n": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", - "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true }, "yargs": { @@ -1882,45 +2397,6 @@ "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "", - "dev": true - }, - "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 - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - } } }, "yargs-parser": { @@ -1939,21 +2415,19 @@ "decamelize": "^4.0.0", "flat": "^5.0.2", "is-plain-obj": "^2.1.0" - }, - "dependencies": { - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true - }, - "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true - } } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, + "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 } } } diff --git a/package.json b/package.json index 32c8c68..e37f8d1 100644 --- a/package.json +++ b/package.json @@ -4,14 +4,14 @@ "description": "NodeJS smartglass library for controlling a Xbox", "main": "src/smartglass.js", "scripts": { - "test": "nyc --reporter=html --reporter=lcov --reporter=text ./node_modules/mocha/bin/mocha tests/", - "discovery": "DEBUG=smartglass:* ./examples/discovery.js", - "connect": "DEBUG=smartglass:* ./examples/connect.js", - "double_connect": "DEBUG=smartglass:* ./examples/double_connect.js", - "connect_channels": "DEBUG=smartglass:* ./examples/connect_channels.js", - "connect_disconnect": "DEBUG=smartglass:* ./examples/connect_and_disconnect.js", - "poweron": "DEBUG=smartglass:* ./examples/poweron.js", - "poweroff": "DEBUG=smartglass:* ./examples/poweroff.js" + "test": "mocha -r ts-node/register test/**/*.spec.ts", + "buttons": "DEBUG=* DEBUG_DEPTH=-1 ts-node examples/buttons", + "discovery": "DEBUG=* DEBUG_DEPTH=-1 ts-node examples/discovery", + "disposeXbox": "DEBUG=* DEBUG_DEPTH=-1 ts-node examples/disposeXbox", + "disposeXboxSmartGlass": "DEBUG=* DEBUG_DEPTH=-1 ts-node examples/disposeXboxSmartGlass", + "onAndOff": "DEBUG=* DEBUG_DEPTH=-1 ts-node examples/onAndOff", + "powerOff": "DEBUG=* DEBUG_DEPTH=-1 ts-node examples/powerOff", + "powerOn": "DEBUG=* DEBUG_DEPTH=-1 ts-node examples/powerOn" }, "repository": { "type": "git", @@ -28,16 +28,24 @@ "xbox one", "smartglass" ], - "dependencies": { - "debug": "^4.3.2", - "elliptic": "^6.5.2", - "hex-to-binary": "^1.0.1", - "jsrsasign": "^10.3.0", + "devDependencies": { + "@types/chai": "^4.3.1", + "@types/debug": "^4.1.7", + "@types/elliptic": "^6.4.14", + "@types/jasmine": "^4.0.3", + "@types/jsrsasign": "^10.2.1", + "@types/mocha": "^9.1.1", + "@types/node": "^17.0.25", + "@types/uuid": "^8.3.4", + "@types/uuid-parse": "^1.0.0", + "chai": "^4.3.6", + "debug": "^4.3.4", + "elliptic": "^6.5.4", + "jsrsasign": "^10.5.17", + "mocha": "^9.2.2", + "prettier": "2.6.2", + "ts-node": "^10.7.0", "uuid": "^8.3.2", "uuid-parse": "^1.1.0" - }, - "devDependencies": { - "mocha": "^9.1.3", - "nyc": "^15.1.0" } } diff --git a/src/Controller.ts b/src/Controller.ts new file mode 100644 index 0000000..49c60d5 --- /dev/null +++ b/src/Controller.ts @@ -0,0 +1,76 @@ +export enum ControllerButtons { + // TODO I don't understand the use case for this yet, + // so leaving it commented out for now. + // Enroll = Math.pow(2, 0, + Nexus = 14, + Menu = 13, + View = 12, + A = 11, + B = 10, + X = 9, + Y = 8, + DPadUp = 7, + DPadDown = 6, + DPadLeft = 5, + DPadRight = 4, + LeftShoulder = 3, + RightShoulder = 2, + LeftThumbStick = 1, + RightThumbStick = 0, +} + +// TODO only buttons seem to work here. Not sure how to get the analog controls working. +export class Controller { + static Buttons = ControllerButtons; + // 16 bits, all 0 to start. + private buttons = new Array(16).fill(0); + private leftTrigger = 0; + private rightTrigger = 0; + private leftThumbStickX = 0; + private leftThumbStickY = 0; + private rightThumbStickX = 0; + private rightThumbStickY = 0; + + getState() { + return { + buttons: parseInt(this.buttons.join(""), 2), + leftTrigger: this.leftTrigger, + rightTrigger: this.rightTrigger, + leftThumbStickX: this.leftThumbStickX, + leftThumbStickY: this.leftThumbStickY, + rightThumbStickX: this.rightThumbStickX, + rightThumbStickY: this.rightThumbStickY, + }; + } + + async pressButton(button: ControllerButtons, time?: number) { + if (button > 15 || button < 0) { + throw new Error("Button value must be between 0 and 15."); + } + this.buttons[button] = 1; + + if (time) { + return new Promise((resolve) => { + setTimeout(() => { + this.buttons[button] = 0; + resolve(); + }, time); + }); + } + } + + releaseButton(button: ControllerButtons) { + this.buttons[button] = 0; + } + + quickPressButton(button: ControllerButtons) { + this.buttons[button] = 1; + setTimeout(() => { + this.buttons[button] = 0; + }, 200); + } + + releaseAllButtons() { + this.buttons = new Array(16).fill(0); + } +} diff --git a/src/DedicatedSocket.ts b/src/DedicatedSocket.ts new file mode 100644 index 0000000..1ad97dd --- /dev/null +++ b/src/DedicatedSocket.ts @@ -0,0 +1,104 @@ +import dgram from "dgram"; +import { OutgoingPacket } from "./OutgoingPackets/OutgoingPacket"; +import { SGCrypto } from "./SGCrypto"; +import { Unpacker } from "./IncomingPackets/Unpacker"; +import { debug, Debugger } from "debug"; +import { ConnectRequest } from "./OutgoingPackets/Simple/ConnectRequest"; +import { ConnectResponse } from "./IncomingPackets/Simple/ConnectResponse"; +import { LocalJoin } from "./OutgoingPackets/Messages/LocalJoin"; + +export class DedicatedSocket extends Unpacker { + private readonly socket: dgram.Socket; + private readonly log: Debugger; + // TODO confirm that 0 is not a valid participant ID? + private participantId = 0; + private totalRequests = 1; + + private ready = false; + private queuedPackets: OutgoingPacket[] = []; + + constructor(ip: string, port: number, sgCrypto: SGCrypto) { + super(sgCrypto); + this.log = debug("Dedicated Socket:" + ip + ":" + port); + this.socket = dgram.createSocket("udp4"); + this.socket.connect(port, ip, () => { + this.log("Connected"); + }); + + this.socket.on("error", (err) => { + this.log("Server Error"); + this.log(err); + this.socket.close(); + }); + + this.socket.on("close", () => { + this.log("Server closed"); + // TODO, what should we do now?? + }); + + this.socket.on("message", (msg, rinfo) => { + const packet = this.unpack(msg); + this.log("Packet Received"); + this.log(packet); + + if (packet instanceof ConnectResponse) { + this.participantId = packet.participantId; + this.totalRequests = 1; + // TODO make sure connection state is 0? + // TODO maybe we want to have some retry logic for sending LocalJoin? + const localJoin = new LocalJoin(sgCrypto); + this.sendPacket(localJoin); + } + }); + + this.socket.on("listening", () => { + const address = this.socket.address(); + this.log(`Listening on ${address.address}:${address.port}`); + this.queuedPackets.forEach((packet) => { + setTimeout(() => this.sendPacket(packet)); + }); + this.queuedPackets = []; + this.ready = true; + }); + } + + dispose() { + this.socket.close(); + } + + sendPacket(packet: OutgoingPacket, silent = false) { + if (!this.ready) { + this.queuedPackets.push(packet); + return; + } + + if (!this.participantId && !(packet instanceof ConnectRequest)) { + this.log("Must connect first"); + return; + } + + if (!silent) { + this.log(`Sending Packet: ${this.totalRequests}`); + this.log(packet); + } + + this.socket.send( + packet.toBuffer({ + requestNumber: this.totalRequests, + participantId: this.participantId, + }), + (err, bytes) => { + // TODO should we check for errors and retry? + } + ); + + this.totalRequests++; + } + + getNextRequestNumber() { + return this.totalRequests; + } + getParticipantId() { + return this.participantId; + } +} diff --git a/src/DiscoverySocket.ts b/src/DiscoverySocket.ts new file mode 100644 index 0000000..0f2b230 --- /dev/null +++ b/src/DiscoverySocket.ts @@ -0,0 +1,100 @@ +import dgram from "dgram"; +import { DiscoveryRequest } from "./OutgoingPackets/Simple/DiscoveryRequest"; +import { GetPacketType } from "./IncomingPackets/unpack"; +import { DiscoveryResponse } from "./IncomingPackets/Simple/DiscoveryResponse"; +import { EventEmitter } from "events"; +import { debug } from "debug"; +import { PowerOn } from "./OutgoingPackets/Simple/PowerOn"; +const log = debug("Discovery Socket"); + +export declare interface DiscoverySocket { + on( + event: "discovery", + listener: (discoveryResponse: DiscoveryResponse) => void + ): this; +} + +export class DiscoverySocket extends EventEmitter { + private readonly socket: dgram.Socket; + private discoveryInterval?: NodeJS.Timeout; + private activeStartCalls = 0; + + startDiscovery() { + this.activeStartCalls++; + // TODO, we need to make sure that the server is listening. + this.discoveryInterval = setInterval(() => { + this.sendDiscoveryPacket(); + }, 1000); + } + + stopDiscovery() { + // Attempting to make sure that if multiple calls to + // startDiscovery are made, that we wait for the same amount + // of calls to stopDiscovery before stopping. + if (this.activeStartCalls > 0) { + this.activeStartCalls--; + } + if (this.activeStartCalls > 0) { + return; + } + + if (this.discoveryInterval) { + clearInterval(this.discoveryInterval); + this.discoveryInterval = undefined; + } + } + + dispose() { + if (this.discoveryInterval) { + clearInterval(this.discoveryInterval); + } + + this.socket.close(); + } + + constructor() { + super(); + this.socket = dgram.createSocket("udp4"); + this.socket.bind(); + + this.socket.on("error", (err) => { + log("Server Error"); + log(err); + this.socket.close(); + }); + + this.socket.on("close", () => { + log("Server Closed"); + // TODO, what should we do now?? + }); + + this.socket.on("message", (msg, rinfo) => { + const type = GetPacketType(msg); + // We only care about DiscoveryResponses here. + if (type.toLowerCase() === DiscoveryResponse.type.toLowerCase()) { + log(`Xbox discovered at ${rinfo.address}:${rinfo.port}`); + const discoveryResponse = new DiscoveryResponse(msg, rinfo); + log(discoveryResponse); + this.emit("discovery", discoveryResponse); + } + }); + + this.socket.on("listening", () => { + this.socket.setBroadcast(true); + const address = this.socket.address(); + log(`Discovery socket listening ${address.address}:${address.port}`); + }); + } + + sendPowerOn(liveId: string, ip: string) { + const buffer = new PowerOn(liveId).toBuffer(); + this.socket.send(buffer, 0, buffer.length, 5050, ip); + } + + private sendDiscoveryPacket() { + log("Sending Discovery Packet"); + const packet = new DiscoveryRequest(); + const buffer = packet.toBuffer(); + this.socket.send(buffer, 0, buffer.length, 5050, "255.255.255.255"); + } +} diff --git a/src/Flags.ts b/src/Flags.ts new file mode 100644 index 0000000..3743f31 --- /dev/null +++ b/src/Flags.ts @@ -0,0 +1,61 @@ +import { bufferToBin } from "./Utilities"; + +export class Flags { + readonly version: number; + readonly needsAck: boolean; + readonly isFragment: boolean; + readonly messageType: number; + + static parse(buff: Buffer) { + if (buff.length !== 2) { + throw new Error("Invalid buffer for flags. Must be of length 2."); + } + + const flagBinaryString = bufferToBin(buff); + + return new Flags({ + needsAck: flagBinaryString.slice(2, 3) === "1", + isFragment: flagBinaryString.slice(3, 4) === "1", + version: parseInt(flagBinaryString.slice(0, 2), 2), + messageType: parseInt(flagBinaryString.slice(4, 16), 2), + }); + } + + constructor(flags: { + version: number; + messageType: number; + needsAck: boolean; + isFragment: boolean; + }) { + if (flags.messageType > 4095 || flags.messageType < 0) { + throw new Error("Message Type is out of range"); + } + + if (flags.version > 3 || flags.version < 0) { + throw new Error("Version is out of range"); + } + + this.version = flags.version; + this.messageType = flags.messageType; + this.needsAck = flags.needsAck; + this.isFragment = flags.isFragment; + } + + // This method is only used for testing. + toBinary() { + return bufferToBin(this.toBuffer()); + } + + toBuffer() { + const needsAck = this.needsAck ? 1 : 0; + const isFragment = this.isFragment ? 1 : 0; + const number = + (this.version << 14) + + (needsAck << 13) + + (isFragment << 12) + + this.messageType; + const buff = Buffer.alloc(2); + buff.writeUInt16BE(number); + return buff; + } +} diff --git a/src/HeartBeat.ts b/src/HeartBeat.ts new file mode 100644 index 0000000..67f710a --- /dev/null +++ b/src/HeartBeat.ts @@ -0,0 +1,75 @@ +import EventEmitter from "events"; +import { DedicatedSocket } from "./DedicatedSocket"; +import { Message } from "./IncomingPackets/Messages/Message"; +import { Acknowledge as AckIn } from "./IncomingPackets/Messages/Acknowledge"; +import { Acknowledge as AckOut } from "./OutgoingPackets/Messages/Acknowledge"; +import { SGCrypto } from "./SGCrypto"; + +// TODO need to come back to connection management. +// We need to do a better job of tracking sequence numbers and acks. +export class HeartBeat extends EventEmitter { + private readonly deadTimeoutInMS = 10000; + private readonly retryTimeoutInMS = 5000; + private deadTimeout?: NodeJS.Timeout; + private retryTimeout?: NodeJS.Timeout; + private isAlive = false; + + constructor( + private readonly socket: DedicatedSocket, + private readonly crypto: SGCrypto + ) { + super(); + + socket.on("message", this.handleMessage.bind(this)); + } + + stop() { + if (this.deadTimeout) { + clearTimeout(this.deadTimeout); + } + if (this.retryTimeout) { + clearTimeout(this.retryTimeout); + } + } + + private handleMessage(message: Message) { + if (message instanceof AckIn && !this.isAlive) { + this.isAlive = true; + this.emit("alive"); + } + + if (this.isAlive) { + this.resetDeadTimeout(); + this.resetRetryTimeout(); + } + + if (message.header.needsAck) { + this.socket.sendPacket( + new AckOut(this.crypto, [message.header.sequenceNumber]) + ); + } + } + + private resetRetryTimeout() { + if (this.retryTimeout) { + clearTimeout(this.retryTimeout); + } + this.retryTimeout = setTimeout(() => { + this.socket.sendPacket(new AckOut(this.crypto, [])); + this.resetRetryTimeout(); + }, this.retryTimeoutInMS); + } + + private resetDeadTimeout() { + if (this.deadTimeout) { + clearTimeout(this.deadTimeout); + } + this.deadTimeout = setTimeout(() => { + this.isAlive = false; + if (this.retryTimeout) { + clearTimeout(this.retryTimeout); + } + this.emit("dead"); + }, this.deadTimeoutInMS); + } +} diff --git a/src/IncomingPackets/Messages/Acknowledge.ts b/src/IncomingPackets/Messages/Acknowledge.ts new file mode 100644 index 0000000..a660455 --- /dev/null +++ b/src/IncomingPackets/Messages/Acknowledge.ts @@ -0,0 +1,15 @@ +import { Cursor, SmartGlassLongArray, UInt32 } from "../unpack"; +import { Message, MessageHeader } from "./Message"; + +export class Acknowledge implements Message { + readonly lowWaterMark: number; + readonly processedList: number[]; + readonly rejectedList: number[]; + + constructor(readonly header: MessageHeader, payload: Buffer) { + const cursor = new Cursor(); + this.lowWaterMark = UInt32(payload, cursor); + this.processedList = SmartGlassLongArray(payload, cursor, UInt32); + this.rejectedList = SmartGlassLongArray(payload, cursor, UInt32); + } +} diff --git a/src/IncomingPackets/Messages/ConsoleStatus.ts b/src/IncomingPackets/Messages/ConsoleStatus.ts new file mode 100644 index 0000000..fc7ee6d --- /dev/null +++ b/src/IncomingPackets/Messages/ConsoleStatus.ts @@ -0,0 +1,43 @@ +import { + Bytes, + Cursor, + SmartGlassShortArray, + SmartGlassString, + UInt32, +} from "../unpack"; +import { Message, MessageHeader } from "./Message"; + +interface App { + id: number; + flags: Buffer; + productId: Buffer; + sandboxId: Buffer; + aumId: String; +} + +export class ConsoleStatus implements Message { + readonly liveTVProvider: number; + readonly majorVersion: number; + readonly minorVersion: number; + readonly buildNumber: number; + readonly locale: string; + readonly apps: App[]; + + constructor(readonly header: MessageHeader, payload: Buffer) { + const cursor = new Cursor(); + this.liveTVProvider = UInt32(payload, cursor); + this.majorVersion = UInt32(payload, cursor); + this.minorVersion = UInt32(payload, cursor); + this.buildNumber = UInt32(payload, cursor); + this.locale = SmartGlassString(payload, cursor); + this.apps = SmartGlassShortArray(payload, cursor, (data, cursor) => { + return { + id: UInt32(data, cursor), + flags: Bytes(data, cursor, 2), + productId: Bytes(data, cursor, 16), + sandboxId: Bytes(data, cursor, 16), + aumId: SmartGlassString(data, cursor), + }; + }); + } +} diff --git a/src/IncomingPackets/Messages/Message.ts b/src/IncomingPackets/Messages/Message.ts new file mode 100644 index 0000000..27e8a0f --- /dev/null +++ b/src/IncomingPackets/Messages/Message.ts @@ -0,0 +1,40 @@ +import { Flags } from "../../Flags"; +import { Bytes, Cursor, UInt16, UInt32 } from "../unpack"; + +export class MessageHeader { + readonly type: string; + readonly headerEndOffset: number; + readonly payloadLength: number; + readonly sequenceNumber: number; + readonly targetParticipantId: number; + readonly sourceParticipantId: number; + readonly version: number; + readonly needsAck: boolean; + readonly isFragment: boolean; + readonly channelId: String; + readonly messageType: number; + + constructor(msg: Buffer) { + const cursor = new Cursor(); + this.type = Bytes(msg, cursor, 2).toString("hex"); + this.payloadLength = UInt16(msg, cursor); + this.sequenceNumber = UInt32(msg, cursor); + this.targetParticipantId = UInt32(msg, cursor); + this.sourceParticipantId = UInt32(msg, cursor); + const flags = Flags.parse(Bytes(msg, cursor, 2)); + this.needsAck = flags.needsAck; + this.isFragment = flags.isFragment; + this.version = flags.version; + this.messageType = flags.messageType; + this.channelId = Bytes(msg, cursor, 8).toString("hex"); + this.headerEndOffset = cursor.getOffset(); + } +} + +export interface Message { + readonly header: MessageHeader; +} + +export interface MessageConstructable { + new (header: MessageHeader, payload: Buffer): Message; +} diff --git a/src/IncomingPackets/Messages/StartChannelResponse.ts b/src/IncomingPackets/Messages/StartChannelResponse.ts new file mode 100644 index 0000000..eeaffb8 --- /dev/null +++ b/src/IncomingPackets/Messages/StartChannelResponse.ts @@ -0,0 +1,19 @@ +import { Bytes, Cursor, UInt32 } from "../unpack"; +import { Message, MessageHeader } from "./Message"; + +export class StartChannelResponse implements Message { + readonly channelRequestId: number; + readonly channelId: Buffer; + readonly result: number; + + get success() { + return this.result === 0; + } + + constructor(readonly header: MessageHeader, payload: Buffer) { + const cursor = new Cursor(); + this.channelRequestId = UInt32(payload, cursor); + this.channelId = Bytes(payload, cursor, 8); + this.result = UInt32(payload, cursor); + } +} diff --git a/src/IncomingPackets/Simple/ConnectResponse.ts b/src/IncomingPackets/Simple/ConnectResponse.ts new file mode 100644 index 0000000..fc55413 --- /dev/null +++ b/src/IncomingPackets/Simple/ConnectResponse.ts @@ -0,0 +1,21 @@ +import { SGCrypto } from "../../SGCrypto"; +import { Bytes, Cursor, UInt16, UInt32 } from "../unpack"; + +export class ConnectResponse { + readonly connectResult: number; + readonly pairingState: number; + readonly participantId: number; + constructor(payload: Buffer, sgCrypto: SGCrypto) { + const cursor = new Cursor(2); + const payloadLength = UInt16(payload, cursor); + const protectedPayloadLength = UInt16(payload, cursor); + const version = UInt16(payload, cursor); + const iv = Bytes(payload, cursor, 16); + const encryptedPayload = Bytes(payload, cursor); + const decryptedPayload = sgCrypto.decrypt(encryptedPayload, iv); + const cursor2 = new Cursor(); + this.connectResult = UInt16(decryptedPayload, cursor2); + this.pairingState = UInt16(decryptedPayload, cursor2); + this.participantId = UInt32(decryptedPayload, cursor2); + } +} diff --git a/src/IncomingPackets/Simple/DiscoveryResponse.ts b/src/IncomingPackets/Simple/DiscoveryResponse.ts new file mode 100644 index 0000000..a50d0c1 --- /dev/null +++ b/src/IncomingPackets/Simple/DiscoveryResponse.ts @@ -0,0 +1,36 @@ +import dgram from "dgram"; +import os from "os"; +import { Cursor, SmartGlassString, UInt16, UInt32 } from "../unpack"; + +export class DiscoveryResponse { + static readonly type = "DD01"; + readonly flags: number; + readonly clientType: number; + readonly name: string; + readonly uuid: string; + readonly lastError: number; + readonly certificate: string; + readonly ip: string; + readonly port: number; + + constructor(rawPacket: Buffer, rinfo: dgram.RemoteInfo) { + // TODO this throw away at the start should probably be its own function. + const cursor = new Cursor(2); + const payloadLength = UInt16(rawPacket, cursor); + const version = UInt16(rawPacket, cursor); + this.flags = UInt32(rawPacket, cursor); + this.clientType = UInt16(rawPacket, cursor); + this.name = SmartGlassString(rawPacket, cursor); + this.uuid = SmartGlassString(rawPacket, cursor); + this.lastError = UInt32(rawPacket, cursor); + this.certificate = + "-----BEGIN CERTIFICATE-----" + + os.EOL + + SmartGlassString(rawPacket, cursor, "base64") + .match(/.{0,64}/g)! + .join("\n") + + "-----END CERTIFICATE-----"; + this.ip = rinfo.address; + this.port = rinfo.port; + } +} diff --git a/src/IncomingPackets/Unpacker.ts b/src/IncomingPackets/Unpacker.ts new file mode 100644 index 0000000..9d67480 --- /dev/null +++ b/src/IncomingPackets/Unpacker.ts @@ -0,0 +1,84 @@ +import { EventEmitter } from "events"; +import { ConnectResponse } from "./Simple/ConnectResponse"; +import { ConsoleStatus } from "./Messages/ConsoleStatus"; +import { + Message, + MessageConstructable, + MessageHeader, +} from "./Messages/Message"; +import { Bytes, Cursor, GetPacketType, UInt16 } from "./unpack"; +import { SGCrypto } from "../SGCrypto"; +import { debug } from "debug"; +import { Acknowledge } from "./Messages/Acknowledge"; +import { StartChannelResponse } from "./Messages/StartChannelResponse"; +const log = debug("Unpacker"); + +const MESSAGE_CLASSES: { [key: number]: MessageConstructable } = { + 0x1: Acknowledge, + 0x1e: ConsoleStatus, + 0x27: StartChannelResponse, +}; + +export declare interface Unpacker { + on(event: "message", listener: (connectResponse: Message) => void): this; + on( + event: "connect_response", + listener: (connectResponse: ConnectResponse) => void + ): this; + on(event: "console_status", listener: (packet: ConsoleStatus) => void): this; + on(event: "acknowledge", listener: (packet: Acknowledge) => void): this; + on( + event: "start_channel", + listener: (packet: StartChannelResponse) => void + ): this; +} +export class Unpacker extends EventEmitter { + constructor(readonly crypto: SGCrypto) { + super(); + } + + protected unpack = (msg: Buffer) => { + log("Unpacking Message"); + const packetType = GetPacketType(msg); + log(`Packet type: ${packetType}`); + + switch (packetType) { + // A generic message + case "d00d": + const messageHeader = new MessageHeader(msg); + const cursor = new Cursor(messageHeader.headerEndOffset); + const remainder = Bytes(msg, cursor); + const protectedPayload = remainder.slice(0, -32); + // const signature = remainder.slice(-32); + const iv = this.crypto.encrypt(msg.slice(0, 16), this.crypto.iv); + // TODO this can throw. everything throws here.... + const decryptedPayload = this.crypto.decrypt(protectedPayload, iv); + + const MessageClass = MESSAGE_CLASSES[messageHeader.messageType]; + if (!MessageClass) { + log("No message class found"); + return; + } + + const message = new MessageClass(messageHeader, decryptedPayload); + if (message instanceof ConsoleStatus) { + this.emit("console_status", message); + } else if (message instanceof Acknowledge) { + this.emit("acknowledge", message); + } else if (message instanceof StartChannelResponse) { + this.emit("start_channel", message); + } + + this.emit("message", message); + + return message; + // A connect response + case "cc01": + const connectResponse = new ConnectResponse(msg, this.crypto); + this.emit("connect_response", connectResponse); + return connectResponse; + default: + log(`Unsupported packet type: ${packetType}`); + } + }; +} diff --git a/src/IncomingPackets/unpack.ts b/src/IncomingPackets/unpack.ts new file mode 100644 index 0000000..274ac6e --- /dev/null +++ b/src/IncomingPackets/unpack.ts @@ -0,0 +1,96 @@ +export const GetPacketType = (packet: Buffer) => { + return packet.slice(0, 2).toString("hex"); +}; + +export class Cursor { + private offset = 0; + getOffset = () => this.offset; + increment = (amount: number) => { + this.offset += amount; + }; + constructor(initialOffset = 0) { + this.offset = initialOffset; + } +} + +export const SmartGlassString = ( + data: Buffer, + cursor: Cursor, + base?: "base64" +) => { + const dataLength = UInt16(data, cursor); + const result = data.slice( + cursor.getOffset(), + cursor.getOffset() + dataLength + ); + cursor.increment(dataLength + 1); + return result.toString(base); +}; + +const SmartGlassArray: ( + data: Buffer, + cursor: Cursor, + readLength: (data: Buffer, cursor: Cursor) => number, + unpacker: (data: Buffer, cursor: Cursor) => T +) => T[] = (data, cursor, readLength, unpacker) => { + const arrayCount = readLength(data, cursor); + const result = []; + for (let i = 0; i < arrayCount; i++) { + result.push(unpacker(data, cursor)); + } + return result; +}; + +export const SmartGlassShortArray: ( + data: Buffer, + cursor: Cursor, + unpacker: (data: Buffer, cursor: Cursor) => T +) => T[] = (data, cursor, unpacker) => { + return SmartGlassArray(data, cursor, UInt16, unpacker); +}; + +export const SmartGlassLongArray: ( + data: Buffer, + cursor: Cursor, + unpacker: (data: Buffer, cursor: Cursor) => T +) => T[] = (data, cursor, unpacker) => { + return SmartGlassArray(data, cursor, UInt32, unpacker); +}; + +export const Bytes = (data: Buffer, cursor: Cursor, count?: number) => { + if (count === 0) { + console.warn("Did you really mean to read 0 bytes?"); + return Buffer.from([]); + } + + const result = data.slice( + cursor.getOffset(), + count ? cursor.getOffset() + count : undefined + ); + cursor.increment(result.length); + return result; +}; + +export const UInt8 = (data: Buffer, cursor: Cursor) => { + const result = data.readUint8(cursor.getOffset()); + cursor.increment(1); + return result; +}; + +export const UInt16 = (data: Buffer, cursor: Cursor) => { + const result = data.readUint16BE(cursor.getOffset()); + cursor.increment(2); + return result; +}; + +export const UInt32 = (data: Buffer, cursor: Cursor) => { + const result = data.readUInt32BE(cursor.getOffset()); + cursor.increment(4); + return result; +}; + +export const Int32 = (data: Buffer, cursor: Cursor) => { + const result = data.readInt32BE(cursor.getOffset()); + cursor.increment(4); + return result; +}; diff --git a/src/InputChannel.ts b/src/InputChannel.ts new file mode 100644 index 0000000..6211bbc --- /dev/null +++ b/src/InputChannel.ts @@ -0,0 +1,70 @@ +import { debug, Debugger } from "debug"; +import { Controller } from "./Controller"; +import { DedicatedSocket } from "./DedicatedSocket"; +import { GamePad } from "./OutgoingPackets/Messages/GamePad"; +import { StartChannelRequest } from "./OutgoingPackets/Messages/StartChannelRequest"; +import { StopChannel } from "./OutgoingPackets/Messages/StopChannel"; +import { SGCrypto } from "./SGCrypto"; + +export class InputChannel { + private channelId?: Buffer; + private channelInterval?: NodeJS.Timeout; + private readonly log: Debugger; + + stop() { + this.log("Stopping Input Channel"); + if (this.channelId) { + this.socket.sendPacket(new StopChannel(this.crypto, this.channelId)); + this.channelId = undefined; + } + + if (this.channelInterval) { + clearInterval(this.channelInterval); + } + } + + // TODO this needs to be handled by xboxConsole. + private channelNumber = 0; + + constructor( + private readonly crypto: SGCrypto, + private readonly socket: DedicatedSocket, + private readonly controller: Controller, + ip: string + ) { + this.log = debug(`InputChannel:${ip}`); + this.socket.on("start_channel", (response) => { + if (!response.success) { + this.log( + "***\n\n\n\n****\n\n\nNeed to handle start channel failing\n\n\n\n*****" + ); + } + if (response.success && !this.channelId) { + this.channelId = response.channelId; + this.channelInterval = setInterval(() => { + if (this.channelId) { + this.socket.sendPacket( + new GamePad( + this.crypto, + this.channelId, + this.controller.getState() + ), + true + ); + } + }, 20); // TODO this value should be modifiable by the user. + } + }); + + this.socket.on("acknowledge", () => { + if (this.channelId) return; + this.socket.sendPacket( + new StartChannelRequest( + this.crypto, + this.channelNumber++, + "fa20b8ca66fb46e0adb60b978a59d35f" + ) + ); + }); + } +} diff --git a/src/OutgoingPackets/Messages/Acknowledge.ts b/src/OutgoingPackets/Messages/Acknowledge.ts new file mode 100644 index 0000000..3d5dc29 --- /dev/null +++ b/src/OutgoingPackets/Messages/Acknowledge.ts @@ -0,0 +1,27 @@ +import { Flags } from "../../Flags"; +import { SGCrypto } from "../../SGCrypto"; +import { SmartGlassLongArray, UInt32 } from "../pack"; +import { Message } from "./Message"; + +export class Acknowledge extends Message { + flags = new Flags({ + needsAck: true, + isFragment: false, + version: 2, + messageType: 0x01, + }); + + constructor(crypto: SGCrypto, readonly acknowledgedSequenceIds: number[]) { + super(crypto); + } + + protected getPayload() { + return [ + UInt32( + this.acknowledgedSequenceIds[this.acknowledgedSequenceIds.length - 1] + ), + SmartGlassLongArray(this.acknowledgedSequenceIds, UInt32), + SmartGlassLongArray([], UInt32), + ]; + } +} diff --git a/src/OutgoingPackets/Messages/Disconnect.ts b/src/OutgoingPackets/Messages/Disconnect.ts new file mode 100644 index 0000000..c90d7f1 --- /dev/null +++ b/src/OutgoingPackets/Messages/Disconnect.ts @@ -0,0 +1,19 @@ +import { Flags } from "../../Flags"; +import { UInt32 } from "../pack"; +import { Message } from "./Message"; + +export class Disconnect extends Message { + readonly reason = UInt32(0); + readonly error = UInt32(0); + + readonly flags = new Flags({ + version: 2, + messageType: 0x2a, + needsAck: false, + isFragment: false, + }); + + protected getPayload() { + return [this.reason, this.error]; + } +} diff --git a/src/OutgoingPackets/Messages/GamePad.ts b/src/OutgoingPackets/Messages/GamePad.ts new file mode 100644 index 0000000..2502808 --- /dev/null +++ b/src/OutgoingPackets/Messages/GamePad.ts @@ -0,0 +1,60 @@ +import { Flags } from "../../Flags"; +import { SGCrypto } from "../../SGCrypto"; +import { UInt16, Int32 } from "../pack"; +import { Message } from "./Message"; + +export class GamePad extends Message { + readonly timestamp = Buffer.from( + "000" + new Date().getTime().toString(), + "hex" + ); + readonly buttons: Buffer; + readonly leftTrigger: Buffer; + readonly rightTrigger: Buffer; + readonly leftThumbStickX: Buffer; + readonly leftThumbStickY: Buffer; + readonly rightThumbStickX: Buffer; + readonly rightThumbStickY: Buffer; + readonly flags = new Flags({ + version: 2, + messageType: 0xf0a, + needsAck: false, + isFragment: false, + }); + + constructor( + crypto: SGCrypto, + channel: Buffer, + state: { + buttons: number; + leftTrigger: number; + rightTrigger: number; + leftThumbStickX: number; + leftThumbStickY: number; + rightThumbStickX: number; + rightThumbStickY: number; + } + ) { + super(crypto, channel); + this.buttons = UInt16(state.buttons); + this.leftTrigger = Int32(state.leftTrigger); + this.rightTrigger = Int32(state.rightTrigger); + this.leftThumbStickX = Int32(state.leftThumbStickX); + this.leftThumbStickY = Int32(state.leftThumbStickY); + this.rightThumbStickX = Int32(state.rightThumbStickX); + this.rightThumbStickY = Int32(state.rightThumbStickY); + } + + protected getPayload() { + return [ + this.timestamp, + this.buttons, + this.leftTrigger, + this.rightTrigger, + this.leftThumbStickX, + this.leftThumbStickY, + this.rightThumbStickX, + this.rightThumbStickY, + ]; + } +} diff --git a/src/OutgoingPackets/Messages/LocalJoin.ts b/src/OutgoingPackets/Messages/LocalJoin.ts new file mode 100644 index 0000000..b0edc68 --- /dev/null +++ b/src/OutgoingPackets/Messages/LocalJoin.ts @@ -0,0 +1,37 @@ +import { Flags } from "../../Flags"; +import { UInt32, UInt16, SmartGlassString } from "../pack"; +import { Message } from "./Message"; + +export class LocalJoin extends Message { + readonly clientType = UInt16(3); + readonly nativeWidth = UInt16(1080); + readonly nativeHeight = UInt16(1920); + readonly dpiX = UInt16(96); + readonly dpiY = UInt16(96); + readonly deviceCapabilities = Buffer.from("FFFFFFFFFFFFFFFF", "hex"); + readonly clientVersion = UInt32(15); + readonly osMajorVersion = UInt32(6); + readonly osMinorVersion = UInt32(2); + readonly displayName = SmartGlassString("Xbox-SmartGlass-Node"); + readonly flags = new Flags({ + version: 2, + messageType: 0x03, + needsAck: true, + isFragment: false, + }); + + protected getPayload() { + return [ + this.clientType, + this.nativeWidth, + this.nativeHeight, + this.dpiX, + this.dpiY, + this.deviceCapabilities, + this.clientVersion, + this.osMajorVersion, + this.osMinorVersion, + this.displayName, + ]; + } +} diff --git a/src/OutgoingPackets/Messages/Message.ts b/src/OutgoingPackets/Messages/Message.ts new file mode 100644 index 0000000..b03a2d9 --- /dev/null +++ b/src/OutgoingPackets/Messages/Message.ts @@ -0,0 +1,53 @@ +import { Flags } from "../../Flags"; +import { SGCrypto } from "../../SGCrypto"; +import { OutgoingPacket } from "../OutgoingPacket"; +import { UInt16, UInt32 } from "../pack"; + +export abstract class Message extends OutgoingPacket { + readonly version = 2; + readonly type = "d00d"; + readonly channel = Buffer.from("\x00\x00\x00\x00\x00\x00\x00\x00"); + protected abstract readonly flags: Flags; + protected abstract getPayload(): Buffer[]; + + constructor(private readonly crypto: SGCrypto, channel?: Buffer) { + super(); + if (channel) { + // TODO ensure channel buffer is correct length + this.channel = channel; + } + } + + toBuffer(packetInfo: { requestNumber: number; participantId: number }) { + const payload = Buffer.concat(this.getPayload()); + const header = this.getHeader(packetInfo, payload.length, this.channel); + const iv = this.crypto.encrypt(header.slice(0, 16), this.crypto.iv); + + const encryptedPayload = this.crypto.encrypt( + this.pad(payload), + this.crypto.encryptionkey, + iv + ); + + return this.crypto.sign(Buffer.concat([header, encryptedPayload])); + } + + private getHeader( + packetInfo: { + requestNumber: number; + participantId: number; + }, + payloadLength: number, + channel: Buffer + ) { + return Buffer.concat([ + Buffer.from(this.type, "hex"), + UInt16(payloadLength), + UInt32(packetInfo.requestNumber), + UInt32(0), // Target participant ID is always 0. + UInt32(packetInfo.participantId), + this.flags.toBuffer(), + channel, + ]); + } +} diff --git a/src/OutgoingPackets/Messages/PowerOff.ts b/src/OutgoingPackets/Messages/PowerOff.ts new file mode 100644 index 0000000..f98994d --- /dev/null +++ b/src/OutgoingPackets/Messages/PowerOff.ts @@ -0,0 +1,24 @@ +import { Flags } from "../../Flags"; +import { SGCrypto } from "../../SGCrypto"; +import { SmartGlassString } from "../pack"; +import { Message } from "./Message"; + +export class PowerOff extends Message { + readonly flags = new Flags({ + version: 2, + messageType: 0x39, + needsAck: false, + isFragment: false, + }); + + readonly liveId: string; + + constructor(crypto: SGCrypto) { + super(crypto); + this.liveId = crypto.liveId; + } + + protected getPayload() { + return [SmartGlassString(this.liveId)]; + } +} diff --git a/src/OutgoingPackets/Messages/StartChannelRequest.ts b/src/OutgoingPackets/Messages/StartChannelRequest.ts new file mode 100644 index 0000000..c70814d --- /dev/null +++ b/src/OutgoingPackets/Messages/StartChannelRequest.ts @@ -0,0 +1,32 @@ +import { Flags } from "../../Flags"; +import { SGCrypto } from "../../SGCrypto"; +import { UInt32 } from "../pack"; +import { Message } from "./Message"; + +export class StartChannelRequest extends Message { + private readonly titleId = 0; + private readonly activityId = 0; + flags = new Flags({ + needsAck: false, + isFragment: false, + version: 2, + messageType: 0x26, + }); + + constructor( + crypto: SGCrypto, + private readonly channelRequestId: number, + private readonly service: string + ) { + super(crypto); + } + + protected getPayload() { + return [ + UInt32(this.channelRequestId), + UInt32(this.titleId), + Buffer.from(this.service, "hex"), + UInt32(this.activityId), + ]; + } +} diff --git a/src/OutgoingPackets/Messages/StopChannel.ts b/src/OutgoingPackets/Messages/StopChannel.ts new file mode 100644 index 0000000..f258538 --- /dev/null +++ b/src/OutgoingPackets/Messages/StopChannel.ts @@ -0,0 +1,20 @@ +import { Flags } from "../../Flags"; +import { SGCrypto } from "../../SGCrypto"; +import { Message } from "./Message"; + +export class StopChannel extends Message { + readonly flags = new Flags({ + needsAck: false, + isFragment: false, + version: 2, + messageType: 0x28, + }); + + constructor(crypto: SGCrypto, private readonly stopChannel: Buffer) { + super(crypto); + } + + protected getPayload() { + return [this.stopChannel]; + } +} diff --git a/src/OutgoingPackets/OutgoingPacket.ts b/src/OutgoingPackets/OutgoingPacket.ts new file mode 100644 index 0000000..1e18fe6 --- /dev/null +++ b/src/OutgoingPackets/OutgoingPacket.ts @@ -0,0 +1,26 @@ +export abstract class OutgoingPacket { + abstract readonly type: string; + abstract readonly version: number; + + abstract toBuffer(packetInfo: { + requestNumber: number; + participantId: number; + }): Buffer; + + /** + * Pads the buffer to be evenly divisible by 16. + * Padding consists of the count of bytes added. + * @param buff The buffer to apply padding to. + * @returns A new buffer with padding applied. + */ + protected pad(buff: Buffer) { + return Buffer.concat([ + buff, + buff.length % 16 === 0 + ? Buffer.from([]) + : Buffer.from( + new Array(16 - (buff.length % 16)).fill(16 - (buff.length % 16)) + ), + ]); + } +} diff --git a/src/OutgoingPackets/Simple/ConnectRequest.ts b/src/OutgoingPackets/Simple/ConnectRequest.ts new file mode 100644 index 0000000..fd94b20 --- /dev/null +++ b/src/OutgoingPackets/Simple/ConnectRequest.ts @@ -0,0 +1,32 @@ +import { SGCrypto } from "../../SGCrypto"; +import { OutgoingPacket } from "../OutgoingPacket"; +import { SmartGlassString, UInt16, UInt32, UInt8 } from "../pack"; +import { Simple } from "./Simple"; + +export class ConnectRequest extends Simple { + readonly type = "cc00"; + readonly version = 2; + readonly uuid: Buffer; + readonly pubkey: Buffer; + readonly iv: Buffer; + constructor(crypto: SGCrypto) { + super(crypto); + this.uuid = crypto.uuid; + this.pubkey = crypto.pubkey; + this.iv = crypto.iv; + } + + getProtectedPayload() { + return [ + SmartGlassString(""), + SmartGlassString(""), + UInt32(0), + UInt32(0), + UInt32(1), + ]; + } + + getUnprotectedPayload() { + return [this.uuid, UInt16(0), this.pubkey, this.iv]; + } +} diff --git a/src/OutgoingPackets/Simple/DiscoveryRequest.ts b/src/OutgoingPackets/Simple/DiscoveryRequest.ts new file mode 100644 index 0000000..5d04253 --- /dev/null +++ b/src/OutgoingPackets/Simple/DiscoveryRequest.ts @@ -0,0 +1,20 @@ +import { UInt32, UInt16 } from "../pack"; +import { Simple } from "./Simple"; + +export class DiscoveryRequest extends Simple { + readonly type = "DD00"; + readonly version = 0; + readonly flags = UInt32(0); + readonly client_type = 3; + readonly min_version = 0; + readonly max_version = 2; + + protected getUnprotectedPayload() { + return [ + this.flags, + UInt16(this.client_type), + UInt16(this.min_version), + UInt16(this.max_version), + ]; + } +} diff --git a/src/OutgoingPackets/Simple/PowerOn.ts b/src/OutgoingPackets/Simple/PowerOn.ts new file mode 100644 index 0000000..c92a6ff --- /dev/null +++ b/src/OutgoingPackets/Simple/PowerOn.ts @@ -0,0 +1,14 @@ +import { SmartGlassString } from "../pack"; +import { Simple } from "./Simple"; + +export class PowerOn extends Simple { + readonly type = "dd02"; + readonly version = 0; + constructor(readonly liveId: string) { + super(); + } + + getUnprotectedPayload() { + return [SmartGlassString(this.liveId)]; + } +} diff --git a/src/OutgoingPackets/Simple/Simple.ts b/src/OutgoingPackets/Simple/Simple.ts new file mode 100644 index 0000000..8340c97 --- /dev/null +++ b/src/OutgoingPackets/Simple/Simple.ts @@ -0,0 +1,49 @@ +import { SGCrypto } from "../../SGCrypto"; +import { OutgoingPacket } from "../OutgoingPacket"; +import { UInt16 } from "../pack"; + +export abstract class Simple extends OutgoingPacket { + protected abstract getUnprotectedPayload(): Buffer[]; + protected getProtectedPayload?(crypto: SGCrypto): Buffer[]; + + constructor(private readonly crypto?: SGCrypto) { + super(); + } + + toBuffer() { + const unprotectedPayload = Buffer.concat(this.getUnprotectedPayload()); + + if (this.getProtectedPayload) { + if (!this.crypto) { + throw new Error("SGCrypto must be provided for getProtectedPayload."); + } + + const protectedPayload = Buffer.concat( + this.getProtectedPayload(this.crypto) + ); + const encryptedPayload = this.crypto.encrypt( + this.pad(protectedPayload), + this.crypto.encryptionkey, + this.crypto.iv + ); + + return this.crypto.sign( + Buffer.concat([ + Buffer.from(this.type, "hex"), + UInt16(unprotectedPayload.length), + UInt16(protectedPayload.length), + UInt16(this.version), + unprotectedPayload, + encryptedPayload, + ]) + ); + } + + return Buffer.concat([ + Buffer.from(this.type, "hex"), + UInt16(unprotectedPayload.length), + UInt16(this.version), + unprotectedPayload, + ]); + } +} diff --git a/src/OutgoingPackets/pack.ts b/src/OutgoingPackets/pack.ts new file mode 100644 index 0000000..094b8e8 --- /dev/null +++ b/src/OutgoingPackets/pack.ts @@ -0,0 +1,48 @@ +export const SmartGlassString = (data: string) => { + const lengthBuff = Buffer.alloc(2); + lengthBuff.writeUInt16BE(data.length, 0); + const dataBuff = Buffer.from(data + "\x00"); + + return Buffer.concat([lengthBuff, dataBuff]); +}; + +export const SmartGlassShortArray: ( + data: T[], + packer: (data: T) => Buffer +) => Buffer = (data, packer) => { + const result = Buffer.concat(data.map(packer)); + return Buffer.concat([UInt16(data.length), result]); +}; + +export const SmartGlassLongArray: ( + data: T[], + packer: (data: T) => Buffer +) => Buffer = (data, packer) => { + const result = Buffer.concat(data.map(packer)); + + return Buffer.concat([UInt32(data.length), result]); +}; + +export const UInt8 = (data: number) => { + const buff = Buffer.alloc(1); + buff.writeUInt8(data, 0); + return buff; +}; + +export const UInt16 = (data: number) => { + const buff = Buffer.alloc(2); + buff.writeUInt16BE(data, 0); + return buff; +}; + +export const UInt32 = (data: number) => { + const buff = Buffer.alloc(4); + buff.writeUInt32BE(data, 0); + return buff; +}; + +export const Int32 = (data: number) => { + const buff = Buffer.alloc(4); + buff.writeInt32BE(data, 0); + return buff; +}; diff --git a/src/SGCrypto.ts b/src/SGCrypto.ts new file mode 100644 index 0000000..30a6d84 --- /dev/null +++ b/src/SGCrypto.ts @@ -0,0 +1,106 @@ +import crypto from "crypto"; +import jsrsasign from "jsrsasign"; +import * as uuid from "uuid"; +import uuidParse from "uuid-parse"; +import { ec as EC } from "elliptic"; + +export class SGCrypto { + readonly liveId: string; + readonly pubkey: Buffer; + readonly secret: Buffer; + readonly encryptionkey: Buffer; + readonly iv: Buffer; + readonly hash_key: Buffer; + readonly uuid: Buffer; + + constructor(pem: string) { + const deviceCert = new jsrsasign.X509(); + deviceCert.readCertPEM(pem); + this.liveId = deviceCert.getSubjectString().slice(4); + this.uuid = Buffer.from(uuidParse.parse(uuid.v4())); + const ecKey = jsrsasign.X509.getPublicKeyFromCertPEM(pem); + // @ts-ignore (Don't know why pubKeyHex doesn't exist on ecKey...) + const result = this.signPublicKey(ecKey.pubKeyHex); + this.pubkey = Buffer.from(result.public_key, "hex"); + this.secret = Buffer.from(result.secret, "hex"); + this.encryptionkey = this.secret.slice(0, 16); + this.iv = this.secret.slice(16, 32); + this.hash_key = this.secret.slice(32); + } + + private signPublicKey(public_key: string) { + const sha512 = crypto.createHash("sha512"); + + const ec = new EC("p256"); + + // Generate keys + const key1 = ec.genKeyPair(); + const key2 = ec.keyFromPublic(public_key, "hex"); + + const shared1 = key1.derive(key2.getPublic()); + let derived_secret = Buffer.from(shared1.toString(16), "hex"); + + const public_key_client = key1.getPublic("hex"); + + const pre_salt = Buffer.from("d637f1aae2f0418c", "hex"); + const post_salt = Buffer.from("a8f81a574e228ab7", "hex"); + derived_secret = Buffer.from( + pre_salt.toString("hex") + + derived_secret.toString("hex") + + post_salt.toString("hex"), + "hex" + ); + // Hash shared secret + const sha = sha512.update(derived_secret); + derived_secret = sha.digest(); + + return { + public_key: public_key_client.toString().slice(2), + secret: derived_secret.toString("hex"), + }; + } + + private removePadding(payload: Buffer) { + const buff = Buffer.from(payload.slice(-1)); + const length = buff.readUInt8(0); + + if (length > 0 && length < 16) { + return Buffer.from(payload.slice(0, payload.length - length)); + } else { + return payload; + } + } + + encrypt(data: Buffer, optionalKey?: Buffer, optionalIv?: Buffer) { + const cipher = crypto.createCipheriv( + "aes-128-cbc", + optionalKey || this.encryptionkey, + optionalIv || + Buffer.from( + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + ); + + cipher.setAutoPadding(false); + return Buffer.concat([cipher.update(data), cipher.final()]); + } + + decrypt(data: Buffer, iv: Buffer) { + const decipher = crypto.createDecipheriv( + "aes-128-cbc", + this.encryptionkey, + iv + ); + decipher.setAutoPadding(false); + + return this.removePadding( + Buffer.concat([decipher.update(data), decipher.final()]) + ); + } + + sign(data: Buffer) { + const hashHmac = crypto.createHmac("sha256", this.hash_key); + hashHmac.update(data); + return Buffer.concat([data, hashHmac.digest()]); + } +} diff --git a/src/TurnOn.ts b/src/TurnOn.ts new file mode 100644 index 0000000..f30247b --- /dev/null +++ b/src/TurnOn.ts @@ -0,0 +1,30 @@ +import { DiscoverySocket } from "./DiscoverySocket"; + +export const TurnOn = ( + liveId: string, + ip: string, + discoverySocket: DiscoverySocket, + timeout = 15000 +) => { + discoverySocket.startDiscovery(); + + const powerOnInterval = setInterval(() => { + discoverySocket.sendPowerOn(liveId, ip); + }, 500); + + return Promise.race([ + new Promise((_resolve, reject) => + setTimeout(() => reject(new Error("Power on timed out")), timeout) + ), + new Promise((resolve) => { + discoverySocket.on("discovery", (discoveryResponse) => { + if (discoveryResponse.ip === ip) { + resolve(); + } + }); + }), + ]).finally(() => { + clearInterval(powerOnInterval); + discoverySocket.stopDiscovery(); + }); +}; diff --git a/src/Utilities.ts b/src/Utilities.ts new file mode 100644 index 0000000..a0f1bd3 --- /dev/null +++ b/src/Utilities.ts @@ -0,0 +1,7 @@ +export const bufferToBin = (buff: Buffer) => { + let binary = ""; + buff.forEach((byte) => { + binary += byte.toString(2).padStart(8, "0"); + }); + return binary; +}; diff --git a/src/Xbox.ts b/src/Xbox.ts new file mode 100644 index 0000000..5a986c3 --- /dev/null +++ b/src/Xbox.ts @@ -0,0 +1,168 @@ +import { debug, Debugger } from "debug"; +import { EventEmitter } from "events"; +import { Controller } from "./Controller"; +import { DedicatedSocket } from "./DedicatedSocket"; +import { DiscoverySocket } from "./DiscoverySocket"; +import { HeartBeat } from "./HeartBeat"; +import { DiscoveryResponse } from "./IncomingPackets/Simple/DiscoveryResponse"; +import { InputChannel } from "./InputChannel"; +import { Disconnect } from "./OutgoingPackets/Messages/Disconnect"; +import { PowerOff } from "./OutgoingPackets/Messages/PowerOff"; +import { ConnectRequest } from "./OutgoingPackets/Simple/ConnectRequest"; +import { SGCrypto } from "./SGCrypto"; +import { TurnOn } from "./TurnOn"; + +export declare interface Xbox { + on(event: "disconnected", listener: () => void): this; + once(event: "disconnected", listener: () => void): this; + on(event: "connected", listener: () => void): this; + once(event: "connected", listener: () => void): this; + on(event: "disposed", listener: () => void): this; + once(event: "disposed", listener: () => void): this; +} + +export class Xbox extends EventEmitter { + readonly Buttons = Controller.Buttons; + readonly ip: string; + readonly liveId: string; + private readonly log: Debugger; + private readonly crypto: SGCrypto; + private readonly socket: DedicatedSocket; + private readonly inputChannel: InputChannel; + private readonly heartBeat: HeartBeat; + private connectInterval?: NodeJS.Timeout; + private connected = false; + private autoReconnectEnabled = true; + private disposed = false; + + get isConnected() { + return this.connected; + } + + get isAutoReconnectEnabled() { + return this.autoReconnectEnabled; + } + + get isDisposed() { + return this.disposed; + } + + readonly controller = new Controller(); + + constructor( + discoveryResponse: DiscoveryResponse, + private readonly discoverySocket: DiscoverySocket + ) { + super(); + this.log = debug(`XboxConsole:${discoveryResponse.ip}`); + this.crypto = new SGCrypto(discoveryResponse.certificate); + this.socket = new DedicatedSocket( + discoveryResponse.ip, + discoveryResponse.port, + this.crypto + ); + + this.liveId = this.crypto.liveId; + this.ip = discoveryResponse.ip; + + this.inputChannel = new InputChannel( + this.crypto, + this.socket, + this.controller, + this.ip + ); + + this.heartBeat = new HeartBeat(this.socket, this.crypto); + + this.heartBeat.on("dead", () => { + this.log("Connection has died..."); + this.connected = false; + this.disconnect(); + this.startConnectionInterval(); + this.emit("disconnected"); + }); + + this.heartBeat.on("alive", () => { + this.log("Connection established"); + this.connected = true; + this.emit("connected"); + }); + + this.socket.on("console_status", (consoleStatus) => { + // TODO keep track of the console status, maybe re-emit the event? + }); + + this.socket.on("connect_response", () => { + this.stopConnectionInterval(); + }); + } + + private startConnectionInterval() { + if (this.connectInterval) return; + this.connectInterval = setInterval(() => { + if (this.autoReconnectEnabled) { + this.socket.sendPacket(new ConnectRequest(this.crypto)); + } else { + this.stopConnectionInterval(); + } + }, 1000); + } + + private stopConnectionInterval() { + if (!this.connectInterval) return; + clearInterval(this.connectInterval); + this.connectInterval = undefined; + } + + async connect(timeout = 10000) { + if (this.connected) return; + + this.startConnectionInterval(); + + return Promise.race([ + new Promise((_resolve, reject) => + setTimeout(() => reject(new Error("Connection timed out")), timeout) + ), + new Promise((resolve) => { + // TODO this feels memory leaky. + this.heartBeat.once("alive", resolve); + }), + ]); + } + + disconnect() { + this.stopConnectionInterval(); + this.heartBeat.stop(); + this.inputChannel.stop(); + this.socket.sendPacket(new Disconnect(this.crypto)); + this.connected = false; + } + + setAutoReconnect(autoReconnectEnabled: boolean) { + this.autoReconnectEnabled = autoReconnectEnabled; + } + + powerOff() { + this.socket.sendPacket(new PowerOff(this.crypto)); + } + + async powerOn(timeout = 15000) { + await TurnOn(this.crypto.liveId, this.ip, this.discoverySocket, timeout); + } + + // TODO this is meant to be called when the user is done with the xboxConsole. + dispose() { + if (this.disposed) return; + // TODO make sure we don't have anything else still running. + + this.stopConnectionInterval(); + this.socket.removeAllListeners(); + this.disconnect(); + this.socket.dispose(); + this.disposed = true; + // Make sure to emit disposed before removing all listeners + this.emit("disposed"); + this.removeAllListeners(); + this.log("Disposed"); + } +} diff --git a/src/channelmanager.js b/src/channelmanager.js deleted file mode 100644 index 1bbf292..0000000 --- a/src/channelmanager.js +++ /dev/null @@ -1,87 +0,0 @@ -const Packer = require('./packet/packer'); - -module.exports = function(service_udid, name) -{ - var Debug = require('debug')('smartglass:channelmanager:'+name) - - return { - _channel_status: false, - _channel_name: name, - - _channel_server_id: 0, - _channel_client_id: 0, - _udid: service_udid, - - _smartglass: false, - _xbox: false, - - getStatus: function(){ - return this._channel_status - }, - - getConsole: function(){ - return this._smartglass._console - }, - - getChannel: function(){ - return this._channel_server_id - }, - - getSmartglass: function(){ - return this._smartglass - }, - - open: function(smartglass, channel_id){ - return new Promise(function(resolve, reject) { - Debug('Opening channel #'+channel_id); - - this._channel_client_id = channel_id - this._smartglass = smartglass - - // @TODO: Find a better way to check - this._smartglass.on('_on_console_status', function(message, xbox, remote, client_smartglass){ - if(this._channel_status == false){ - Debug('Request open channel: '+this._channel_name); - - this._xbox = xbox; - - var channel_request = Packer('message.start_channel_request') - channel_request.set('channel_request_id', this._channel_client_id); - channel_request.set('title_id', 0); - channel_request.set('service', Buffer.from(this._udid, 'hex')); - channel_request.set('activity_id', 0); - Debug('+ Send channel request on channel #'+this._channel_client_id); - - // xbox.get_requestnum() - this._smartglass._console.get_requestnum() - var channel_message = channel_request.pack(xbox) - - this._smartglass.on('_on_start_channel_response', function(response_message, response_xbox, response_remote){ - // console.log('Got channel response!', this._channel_client_id, response_message) - - if(response_message.packet_decoded.protected_payload.channel_request_id == this._channel_client_id) - { - if(response_message.packet_decoded.protected_payload.result == 0) - { - Debug('Channel ready: '+this._channel_name); - this._channel_status = true - this._channel_server_id = response_message.packet_decoded.protected_payload.target_channel_id - - resolve(this) - } else { - reject('Could not open channel: '+this._channel_name); - } - } - }.bind(this)); - - this._smartglass._send(channel_message); - } - }.bind(this)) - }.bind(this)) - }, - - send: function(packet){ - this._smartglass._send(packet) - } - } -} diff --git a/src/channels/systeminput.js b/src/channels/systeminput.js deleted file mode 100644 index 1d51163..0000000 --- a/src/channels/systeminput.js +++ /dev/null @@ -1,91 +0,0 @@ -var Debug = require('debug')('smartglass:channel_system_input') -const Packer = require('../packet/packer'); -const ChannelManager = require('../channelmanager'); - -module.exports = function() -{ - var channel_manager = new ChannelManager('fa20b8ca66fb46e0adb60b978a59d35f', 'SystemInput') - - return { - _channel_manager: channel_manager, - - _button_map: { - a: 16, - b: 32, - x: 64, - y: 128, - up: 256, - left: 1024, - right: 2048, - down: 512, - nexus: 2, - view: 4, - menu: 8, - }, - - load: function(smartglass, manager_id){ - this._channel_manager.open(smartglass, manager_id).then(function(channel){ - Debug('Channel is open.') - }, function(error){ - Debug('ChannelManager open() Error:', error) - }) - }, - - sendCommand: function(button){ - // Send - return new Promise(function(resolve, reject) { - if(this._channel_manager.getStatus() == true){ - Debug('Send button: '+button); - - if(this._button_map[button] != undefined){ - var timestamp_now = new Date().getTime() - - var gamepad = Packer('message.gamepad') - gamepad.set('timestamp', Buffer.from('000'+timestamp_now.toString(), 'hex')) - gamepad.set('buttons', this._button_map[button]); - gamepad.setChannel(this._channel_manager.getChannel()) - - this._channel_manager.getConsole().get_requestnum() - var message = gamepad.pack(this._channel_manager.getConsole()) - this._channel_manager.send(message); - - setTimeout(function(){ - var timestamp = new Date().getTime() - - var gamepad_unpress = Packer('message.gamepad') - gamepad_unpress.set('timestamp', Buffer.from('000'+timestamp.toString(), 'hex')) - gamepad_unpress.set('buttons', 0); - gamepad_unpress.setChannel(this._channel_manager.getChannel()) - - this._channel_manager.getConsole().get_requestnum() - var message = gamepad_unpress.pack(this._channel_manager.getConsole()) - - this._channel_manager.send(message); - resolve({ - status: 'ok_gamepad_send', - params: { - button: button - } - }) - - }.bind(this), 100) - - } else { - Debug('Failed to send button. Reason: Unknown '+button); - - reject({ - status: 'error_channel_disconnected', - error: 'Channel not ready: SystemInput', - buttons: _button_map - }) - } - } else { - reject({ - status: 'error_channel_disconnected', - error: 'Channel not ready: SystemInput' - }) - } - }.bind(this)) - } - } -} diff --git a/src/channels/systemmedia.js b/src/channels/systemmedia.js deleted file mode 100644 index a752bb2..0000000 --- a/src/channels/systemmedia.js +++ /dev/null @@ -1,81 +0,0 @@ -var Debug = require('debug')('smartglass:channel_system_media') -const Packer = require('../packet/packer'); -const ChannelManager = require('../channelmanager'); - -module.exports = function() -{ - var channel_manager = new ChannelManager('48a9ca24eb6d4e128c43d57469edd3cd', 'SystemMedia') - - return { - _channel_manager: channel_manager, - - _media_request_id: 1, - _media_state: { - title_id: 0 - }, - - _media_commands: { - play: 2, - pause: 4, - playpause: 8, - stop: 16, - record: 32, - next_track: 64, - prev_track: 128, - fast_forward: 256, - rewind: 512, - channel_up: 1024, - channel_down: 2048, - back: 4096, - view: 8192, - menu: 16384, - seek: 32786, // Not implemented yet - }, - - load: function(smartglass, manager_id){ - this._channel_manager.open(smartglass, manager_id).then(function(channel){ - Debug('Channel is open.') - }, function(error){ - Debug('ChannelManager open() Error:', error) - }) - }, - - sendCommand: function(button){ - return new Promise(function(resolve, reject) { - if(this._channel_manager.getStatus() == true){ - Debug('Send media command: '+button); - - var media_command = Packer('message.media_command') - var request_id = "0000000000000000" - request_id = (request_id+this._media_request_id).slice(-request_id.length); - media_command.set('request_id', Buffer.from(request_id, 'hex')); - media_command.set('title_id', this._media_state.title_id); - media_command.set('command', this._media_commands[button]); - this._media_request_id++ - - media_command.setChannel(this._channel_manager.getChannel()) - this._channel_manager.getConsole().get_requestnum() - var message = media_command.pack(this._channel_manager.getConsole()) - - this._channel_manager.send(message); - - resolve({ - status: 'ok_media_send', - params: { - button: button - } - }) - } else { - reject({ - status: 'error_channel_disconnected', - error: 'Channel not ready: TvRemote' - }) - } - }.bind(this)) - }, - - getState: function(){ - return this._media_state - } - } -} diff --git a/src/channels/tvremote.js b/src/channels/tvremote.js deleted file mode 100644 index 827be64..0000000 --- a/src/channels/tvremote.js +++ /dev/null @@ -1,250 +0,0 @@ -var Debug = require('debug')('smartglass:channel_tv_remote') -const Packer = require('../packet/packer'); -const ChannelManager = require('../channelmanager'); - -module.exports = function() -{ - var channel_manager = new ChannelManager('d451e3b360bb4c71b3dbf994b1aca3a7', 'TvRemote') - - return { - _channel_manager: channel_manager, - _message_num: 0, - - _configuration: {}, - _headend_info: {}, - _live_tv: {}, - _tuner_lineups: {}, - _appchannel_lineups: {}, - - load: function(smartglass, manager_id){ - this._channel_manager.open(smartglass, manager_id).then(function(channel){ - Debug('Channel is open.') - }, function(error){ - Debug('ChannelManager open() Error:', error) - }) - - smartglass.on('_on_json', function(message, xbox, remote, client_smartglass){ - var response = JSON.parse(message.packet_decoded.protected_payload.json) - - if(response.response == "Error"){ - console.log('Got Error:', response) - } else { - if(response.response == 'GetConfiguration'){ - Debug('Got TvRemote Configuration') - this._configuration = response.params - - } else if(response.response == 'GetHeadendInfo') { - Debug('Got Headend Configuration') - this._headend_info = response.params - - } else if(response.response == 'GetLiveTVInfo') { - Debug('Got live tv Info') - this._live_tv = response.params - - } else if(response.response == 'GetTunerLineups') { - Debug('Got live tv Info') - this._tuner_lineups = response.params - - } else if(response.response == 'GetAppChannelLineups') { - Debug('Got live tv Info') - this._appchannel_lineups = response.params - } - // else { - // Debug('UNKNOWN JSON RESPONSE:', response) - // } - - } - - }.bind(this)) - }, - - getConfiguration: function(){ - return new Promise(function(resolve, reject) { - if(this._channel_manager.getStatus() == true){ - Debug('Get configuration'); - - this._message_num++ - var msgId = '2ed6c0fd.'+this._message_num; - - var json_request = { - msgid: msgId, - request: "GetConfiguration", - params: null - } - - var json_packet = this._createJsonPacket(json_request); - this._channel_manager.send(json_packet); - - setTimeout(function(){ - resolve(this._configuration) - }.bind(this), 1000) - } else { - reject({ - status: 'error_channel_disconnected', - error: 'Channel not ready: TvRemote' - }) - } - }.bind(this)) - }, - - getHeadendInfo: function(){ - return new Promise(function(resolve, reject) { - if(this._channel_manager.getStatus() == true){ - Debug('Get headend info'); - - this._message_num++ - var msgId = '2ed6c0fd.'+this._message_num; - - var json_request = { - msgid: msgId, - request: "GetHeadendInfo", - params: null - } - - var json_packet = this._createJsonPacket(json_request); - this._channel_manager.send(json_packet); - - setTimeout(function(){ - resolve(this._headend_info) - }.bind(this), 1000) - } else { - reject({ - status: 'error_channel_disconnected', - error: 'Channel not ready: TvRemote' - }) - } - }.bind(this)) - }, - - getLiveTVInfo: function(){ - return new Promise(function(resolve, reject) { - if(this._channel_manager.getStatus() == true){ - Debug('Get live tv info'); - - this._message_num++ - var msgId = '2ed6c0fd.'+this._message_num; - - var json_request = { - msgid: msgId, - request: "GetLiveTVInfo", - params: null - } - - var json_packet = this._createJsonPacket(json_request); - this._channel_manager.send(json_packet); - - setTimeout(function(){ - resolve(this._live_tv) - }.bind(this), 1000) - } else { - reject({ - status: 'error_channel_disconnected', - error: 'Channel not ready: TvRemote' - }) - } - }.bind(this)) - }, - - getTunerLineups: function(){ - return new Promise(function(resolve, reject) { - if(this._channel_manager.getStatus() == true){ - Debug('Get tuner lineups'); - - this._message_num++ - var msgId = '2ed6c0fd.'+this._message_num; - - var json_request = { - msgid: msgId, - request: "GetTunerLineups", - params: null - } - - var json_packet = this._createJsonPacket(json_request); - this._channel_manager.send(json_packet); - - setTimeout(function(){ - resolve(this._tuner_lineups) - }.bind(this), 1000) - } else { - reject({ - status: 'error_channel_disconnected', - error: 'Channel not ready: TvRemote' - }) - } - }.bind(this)) - }, - - getAppChannelLineups: function(){ - return new Promise(function(resolve, reject) { - if(this._channel_manager.getStatus() == true){ - Debug('Get appchannel lineups'); - - this._message_num++ - var msgId = '2ed6c0fd.'+this._message_num; - - var json_request = { - msgid: msgId, - request: "GetAppChannelLineups", - params: null - } - - var json_packet = this._createJsonPacket(json_request); - this._channel_manager.send(json_packet); - - setTimeout(function(){ - resolve(this._appchannel_lineups) - }.bind(this), 1000) - } else { - reject({ - status: 'error_channel_disconnected', - error: 'Channel not ready: TvRemote' - }) - } - }.bind(this)) - }, - - sendIrCommand: function(button_id, device_id = null){ - return new Promise(function(resolve, reject) { - if(this._channel_manager.getStatus() == true){ - Debug('Send button: '+button_id); - - this._message_num++ - var msgId = '2ed6c0fd.'+this._message_num; - - var json_request = { - msgid: msgId, - request:"SendKey", - params: { - button_id: button_id, - device_id: device_id - } - } - - var json_packet = this._createJsonPacket(json_request); - this._channel_manager.send(json_packet); - - resolve({ - status: 'ok_tvremote_send', - params: json_request.params - }) - } else { - reject({ - status: 'error_channel_disconnected', - error: 'Channel not ready: TvRemote' - }) - } - }.bind(this)) - }, - - _createJsonPacket: function(json){ - - var config_request = Packer('message.json') - config_request.set('json', JSON.stringify(json)); - this._channel_manager.getConsole().get_requestnum() - - config_request.setChannel(this._channel_manager.getChannel()) - - return config_request.pack(this._channel_manager.getConsole()) - } - } -} diff --git a/src/events.js b/src/events.js deleted file mode 100644 index cff9b6c..0000000 --- a/src/events.js +++ /dev/null @@ -1,180 +0,0 @@ -const EventEmitter = require('events'); -const Packer = require('./packet/packer') -var Debug = require('debug')('smartglass:events') - - -module.exports = function(){ - const smartglassEmitter = new EventEmitter(); - - smartglassEmitter.on('newListener', function(event, listener){ - Debug('+ New listener: '+event+'()'); - }) - - smartglassEmitter.on('receive', function(message, xbox, remote, smartglass){ - - message = Packer(message); - if(message.structure == false) - return; - - var response = message.unpack(xbox); - - var type = response.name; - var func = ''; - - if(response.packet_decoded.type != 'd00d') - { - func = '_on_' + type.toLowerCase(); - Debug('Received message. Call: '+func+'()'); - } else { - if(response.packet_decoded.target_participant_id != xbox._participantid){ - Debug('[smartglass.js:_receive] Participantid does not match. Ignoring packet.') - return; - } - - func = '_on_' + message.structure.packet_decoded.name.toLowerCase(); - Debug('Received message. Call: '+func+'()'); - - if(response.packet_decoded.flags.need_ack == true){ - Debug('Packet needs to be acknowledged. Sending response'); - //Debug(response.packet_decoded) - - var ack = Packer('message.acknowledge') - ack.set('low_watermark', response.packet_decoded.sequence_number) - // xbox._request_num = response.packet_decoded.sequence_number+1 - ack.structure.structure.processed_list.value.push({id: response.packet_decoded.sequence_number}) - smartglass._console.get_requestnum() - var ack_message = ack.pack(smartglass._console) - - try { - smartglass._send(ack_message); - } - catch(error) { - Debug('error', error) - } - - } - } - - if(func == '_on_json') - { - // console.log('ON JSON') - var json_message = JSON.parse(response.packet_decoded.protected_payload.json) - // console.log(json_message); - - // Check if JSON is fragmented - if(json_message.datagram_id != undefined){ - Debug('_on_json is fragmented #'+json_message.datagram_id) - if(xbox._fragments[json_message.datagram_id] == undefined){ - // Prepare buffer for JSON - xbox._fragments[json_message.datagram_id] = { - - getValue: function(){ - var buffer = Buffer.from(''); - - for(let partial in this.partials){ - buffer = Buffer.concat([ - buffer, - Buffer.from(this.partials[partial]) - ]) - } - - var buffer_decoded = Buffer(buffer.toString(), 'base64') - return buffer_decoded - }, - isValid: function(){ - var json = this.getValue() - // console.log('fragment', fragment.toString()) - // var json = Buffer(fragment.toString(), 'base64') - // console.log('valid check: ', json.toString()) - - try { - JSON.parse(json.toString()); - } catch (e) { - return false; - } - - return true - }, - partials: {} - } - } - - xbox._fragments[json_message.datagram_id].partials[json_message.fragment_offset] = json_message.fragment_data - - if(xbox._fragments[json_message.datagram_id].isValid() == true){ - Debug('_on_json: Completed fragmented packet') - var json_response = response - json_response.packet_decoded.protected_payload.json = xbox._fragments[json_message.datagram_id].getValue().toString() - - smartglassEmitter.emit('_on_json', json_response, xbox, remote, smartglass) - - xbox._fragments[json_message.datagram_id] = undefined - } - - func = '_on_json_fragment' - } - } - - Debug('Emit event:', func) - smartglassEmitter.emit(func, response, xbox, remote, smartglass) - }) - - smartglassEmitter.on('_on_discovery_response', function(message, xbox, remote){}); - - smartglassEmitter.on('_on_connect_response', function(message, xbox, remote, smartglass){ - - if(smartglass._connection_status == true){ - Debug('Ignore connect_response packet. Already connected...') - return; - } - - var participantId = message.packet_decoded.protected_payload.participant_id; - xbox.set_participantid(participantId); - - var connectionResult = message.packet_decoded.protected_payload.connect_result; - if(connectionResult == '0') - { - smartglass._connection_status = true; - - var local_join = Packer('message.local_join'); - var join_message = local_join.pack(xbox); - - smartglass._send(join_message); - - smartglass._interval_timeout = setInterval(function(){ - var seconds_ago = (Math.floor(Date.now() / 1000))-this._last_received_time - - if(seconds_ago == 4){ - Debug('Check timeout: Last packet was '+((Math.floor(Date.now() / 1000))-this._last_received_time+' seconds ago')) - - xbox.get_requestnum() - var ack = Packer('message.acknowledge') - ack.set('low_watermark', xbox._request_num) - var ack_message = ack.pack(xbox) - - this._send(ack_message); - } - - if(seconds_ago > 8){ - Debug('Connection timeout after 8 sec. Call: _on_timeout()') - smartglass._events.emit('_on_timeout', message, xbox, remote, this) - - smartglass._closeClient() - return; - } - }.bind(smartglass, message, xbox, remote), 1000) - } - }); - - - smartglassEmitter.on('_on_console_status', function(message, xbox, remote, smartglass){ - if(message.packet_decoded.protected_payload.apps[0] != undefined){ - if(smartglass._current_app != message.packet_decoded.protected_payload.apps[0].aum_id){ - smartglass._current_app = message.packet_decoded.protected_payload.apps[0].aum_id - // console.log('Current active app:', smartglass._current_app) - } - } - }); - - return smartglassEmitter -}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..5ab5b77 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,85 @@ +import { EventEmitter } from "events"; +import { DiscoverySocket } from "./DiscoverySocket"; +import { TurnOn } from "./TurnOn"; +import { Xbox } from "./Xbox"; + +declare interface XboxSmartGlass { + on(event: "discovery", listener: (xbox: Xbox) => void): this; + once(event: "discovery", listener: (xbox: Xbox) => void): this; + on(event: "disposed", listener: () => void): this; + once(event: "disposed", listener: () => void): this; +} + +class XboxSmartGlass extends EventEmitter { + private readonly discoverySocket = new DiscoverySocket(); + private disposed = false; + private consoles: { [key: string]: Xbox } = {}; + private isDiscovering = false; + + getXboxConsoles() { + return Object.values(this.consoles); + } + + startDiscovery() { + this.isDiscovering = true; + this.discoverySocket.startDiscovery(); + } + + stopDiscovery() { + this.isDiscovering = false; + this.discoverySocket.stopDiscovery(); + } + + dispose() { + if (this.disposed) return; + this.getXboxConsoles().forEach((xbox) => xbox.dispose()); + this.discoverySocket.dispose(); + this.disposed = true; + this.emit("disposed"); + this.removeAllListeners(); + } + + async powerOn(liveId: string, ip: string, timeout?: number) { + if (this.consoles[ip]) { + await this.consoles[ip].powerOn(timeout); + return this.consoles[ip]; + } else { + await TurnOn(liveId, ip, this.discoverySocket, timeout); + if (!this.consoles[ip]) { + throw new Error("An unknown error ocurred"); + } + + return this.consoles[ip]; + } + } + + constructor() { + super(); + + this.discoverySocket.on("discovery", (discoveryResponse) => { + if (!this.consoles[discoveryResponse.ip]) { + const newXbox = new Xbox(discoveryResponse, this.discoverySocket); + this.consoles[discoveryResponse.ip] = newXbox; + + // Setup a listener for when the xbox is disposed so we can + // remove our reference to it. + newXbox.on("disposed", () => { + delete this.consoles[discoveryResponse.ip]; + }); + } + + if (this.isDiscovering) { + this.emit("discovery", this.consoles[discoveryResponse.ip]); + } + }); + } +} + +const xboxSmartGlass = new XboxSmartGlass(); + +// This ensures that we dispose of everything when the process is killed. +// I've found that during development too many unclosed channels can cause +// the Xbox to stop responding to things like controller input. +process.on("SIGINT", () => xboxSmartGlass.dispose()); + +export default xboxSmartGlass; diff --git a/src/packet/message.js b/src/packet/message.js deleted file mode 100644 index 5bf98be..0000000 --- a/src/packet/message.js +++ /dev/null @@ -1,534 +0,0 @@ -var PacketStructure = require('./structure'); -var Packer = require('./packer'); -var hexToBin = require('hex-to-binary'); -var Debug = require('debug')('smartglass:packet_message') - -module.exports = function(type, packet_data = false){ - var Playback_Status = { - 0: 'Closed', - 1: 'Changing', - 2: 'Stopped', - 3: 'Playing', - 4: 'Paused' - } - - var Media_Types = { - 0: 'No Media', - 1: 'Music', - 2: 'Video', - 3: 'Image', - 4: 'Conversation', - 5: 'Game' - } - - var Sound_Status = { - 0: 'Muted', - 1: 'Low', - 2: 'Full' - } - - var Type = { - uInt32: function(value){ - return { - value: value, - pack: function(packet_structure){ - return packet_structure.writeUInt32(this.value); - }, - unpack: function(packet_structure){ - this.value = packet_structure.readUInt32(); - return this.value - } - } - }, - sInt32: function(value){ - return { - value: value, - pack: function(packet_structure){ - return packet_structure.writeInt32(this.value); - }, - unpack: function(packet_structure){ - this.value = packet_structure.readInt32(); - return this.value - } - } - }, - uInt16: function(value){ - return { - value: value, - pack: function(packet_structure){ - return packet_structure.writeUInt16(this.value); - }, - unpack: function(packet_structure){ - this.value = packet_structure.readUInt16(); - return this.value - } - } - }, - bytes: function(length, value){ - return { - value: value, - length: length, - pack: function(packet_structure){ - return packet_structure.writeBytes(this.value); - }, - unpack: function(packet_structure){ - this.value = packet_structure.readBytes(length); - return this.value - } - } - }, - sgString: function(value){ - return { - value: value, - pack: function(packet_structure){ - return packet_structure.writeSGString(this.value); - }, - unpack: function(packet_structure){ - this.value = packet_structure.readSGString().toString(); - return this.value - } - } - }, - flags: function(length, value){ - - return { - value: value, - length: length, - pack: function(packet_structure){ - return packet_structure.writeBytes(setFlags(this.value)); - }, - unpack: function(packet_structure){ - this.value = readFlags(packet_structure.readBytes(this.length)); - return this.value - } - } - }, - sgArray: function(structure, value){ - return { - value: value, - structure: structure, - pack: function(packet_structure){ - // @Todo - - packet_structure.writeUInt16(this.value.length); - - var array_structure = Packet[this.structure]; - for(var index in this.value) - { - for(var name in array_structure){ - array_structure[name].value = this.value[index][name] - packet_structure = array_structure[name].pack(packet_structure) - } - } - - return packet_structure; - }, - unpack: function(packet_structure){ - var array_count = packet_structure.readUInt16(); - var array = [] - - for(var i = 0; i < array_count; i++) { - var array_structure = Packet[this.structure]; - var item = {} - - for(var name in array_structure){ - item[name] = array_structure[name].unpack(packet_structure) - } - - array.push(item) - } - - this.value = array - return this.value; - } - } - }, - sgList: function(structure, value){ - return { - value: value, - structure: structure, - pack: function(packet_structure){ - - packet_structure.writeUInt32(this.value.length); - - var array_structure = Packet[this.structure]; - for(var index in this.value) - { - for(name in array_structure){ - array_structure[name].value = this.value[index][name] - packet_structure = array_structure[name].pack(packet_structure) - } - } - - return packet_structure; - }, - unpack: function(packet_structure){ - var array_count = packet_structure.readUInt32(); - var array = [] - - for(var i = 0; i < array_count; i++) { - var array_structure = Packet[this.structure]; - var item = {} - - for(name in array_structure){ - item[name] = array_structure[name].unpack(packet_structure) - } - - array.push(item) - } - - this.value = array - return this.value; - } - } - }, - mapper: function(map, item){ - return { - item: item, - value: false, - pack: function(packet_structure){ - return item.pack(packet_structure); - }, - unpack: function(packet_structure){ - this.value = item.unpack(packet_structure); - return map[this.value] - } - } - } - } - - var Packet = { - console_status: { - live_tv_provider: Type.uInt32('0'), - major_version: Type.uInt32('0'), - minor_version: Type.uInt32('0'), - build_number: Type.uInt32('0'), - locale: Type.sgString('en-US'), - apps: Type.sgArray('_active_apps') - }, - _active_apps: { - title_id: Type.uInt32('0'), - flags: Type.bytes(2), - product_id: Type.bytes(16, ''), - sandbox_id: Type.bytes(16, ''), - aum_id: Type.sgString('') - }, - power_off: { - liveid: Type.sgString(''), - }, - acknowledge: { - low_watermark: Type.uInt32('0'), - processed_list: Type.sgList('_acknowledge_list', []), - rejected_list: Type.sgList('_acknowledge_list', []), - }, - _acknowledge_list: { - id: Type.uInt32('0'), - }, - game_dvr_record: { - start_time_delta: Type.sInt32('0'), - end_time_delta: Type.sInt32('0'), - }, - start_channel_request: { - channel_request_id: Type.uInt32('0'), - title_id: Type.uInt32('0'), - service: Type.bytes(16, ''), - activity_id: Type.uInt32('0'), - }, - start_channel_response: { - channel_request_id: Type.uInt32('0'), - target_channel_id: Type.bytes(8, ''), - result: Type.uInt32('0'), - }, - gamepad: { - timestamp: Type.bytes(8, ''), - buttons: Type.uInt16('0'), - left_trigger: Type.uInt32('0'), - right_trigger: Type.uInt32('0'), - left_thumbstick_x: Type.uInt32('0'), - left_thumbstick_y: Type.uInt32('0'), - right_thumbstick_x: Type.uInt32('0'), - right_thumbstick_y: Type.uInt32('0'), - }, - media_state: { - title_id: Type.uInt32('0'), - aum_id: Type.sgString(), - asset_id: Type.sgString(), - media_type: Type.mapper(Media_Types, Type.uInt16('0')), - sound_level: Type.mapper(Sound_Status, Type.uInt16('0')), - enabled_commands: Type.uInt32('0'), - playback_status: Type.mapper(Playback_Status, Type.uInt16('0')), - rate: Type.uInt32('0'), - position: Type.bytes(8, ''), - media_start: Type.bytes(8, ''), - media_end: Type.bytes(8, ''), - min_seek: Type.bytes(8, ''), - max_seek: Type.bytes(8, ''), - metadata: Type.sgArray('_media_state_list', []), - }, - _media_state_list: { - name: Type.sgString(), - value: Type.sgString(), - }, - media_command: { - request_id: Type.bytes(8, ''), - title_id: Type.uInt32('0'), - command: Type.uInt32('0'), - }, - local_join: { - client_type: Type.uInt16('3'), - native_width: Type.uInt16('1080'), - native_height: Type.uInt16('1920'), - dpi_x: Type.uInt16('96'), - dpi_y: Type.uInt16('96'), - device_capabilities: Type.bytes(8, Buffer.from('ffffffffffffffff', 'hex')), - client_version: Type.uInt32('15'), - os_major_version: Type.uInt32('6'), - os_minor_version: Type.uInt32('2'), - display_name: Type.sgString('Xbox-Smartglass-Node'), - }, - json: { - json: Type.sgString('{}') - }, - disconnect: { - reason: Type.uInt32('1'), - error_code: Type.uInt32('0') - }, - }; - - function getMsgType(type) - { - var message_types = { - 0x1: "acknowledge", - 0x2: "Group", - 0x3: "local_join", - 0x5: "StopActivity", - 0x19: "AuxilaryStream", - 0x1A: "ActiveSurfaceChange", - 0x1B: "Navigate", - 0x1C: "json", - 0x1D: "Tunnel", - 0x1E: "console_status", - 0x1F: "TitleTextConfiguration", - 0x20: "TitleTextInput", - 0x21: "TitleTextSelection", - 0x22: "MirroringRequest", - 0x23: "TitleLaunch", - 0x26: "start_channel_request", - 0x27: "start_channel_response", - 0x28: "StopChannel", - 0x29: "System", - 0x2A: "disconnect", - 0x2E: "TitleTouch", - 0x2F: "Accelerometer", - 0x30: "Gyrometer", - 0x31: "Inclinometer", - 0x32: "Compass", - 0x33: "Orientation", - 0x36: "PairedIdentityStateChanged", - 0x37: "Unsnap", - 0x38: "game_dvr_record", - 0x39: "power_off", - 0xF00: "MediaControllerRemoved", - 0xF01: "media_command", - 0xF02: "media_command_result", - 0xF03: "media_state", - 0xF0A: "gamepad", - 0xF2B: "SystemTextConfiguration", - 0xF2C: "SystemTextInput", - 0xF2E: "SystemTouch", - 0xF34: "SystemTextAck", - 0xF35: "SystemTextDone" - } - - return message_types[type]; - } - - function readFlags(flags) - { - flags = hexToBin(flags.toString('hex')); - - var need_ack = false - var is_fragment = false; - - if(flags.slice(2, 3) == 1) - need_ack = true; - - if(flags.slice(3, 4) == 1) - is_fragment = true; - - - var type = getMsgType(parseInt(flags.slice(4, 16), 2)) - - return { - 'version': parseInt(flags.slice(0, 2), 2).toString(), - 'need_ack': need_ack, - 'is_fragment': is_fragment, - 'type': type - } - } - - function setFlags(type) - { - var message_flags = { - acknowledge: Buffer.from('8001', 'hex'), - 0x2: "Group", - local_join: Buffer.from('2003', 'hex'), - 0x5: "StopActivity", - 0x19: "AuxilaryStream", - 0x1A: "ActiveSurfaceChange", - 0x1B: "Navigate", - json: Buffer.from('a01c', 'hex'), - 0x1D: "Tunnel", - console_status: Buffer.from('a01e', 'hex'), - 0x1F: "TitleTextConfiguration", - 0x20: "TitleTextInput", - 0x21: "TitleTextSelection", - 0x22: "MirroringRequest", - 0x23: "TitleLaunch", - start_channel_request: Buffer.from('a026', 'hex'), - start_channel_response: Buffer.from('a027', 'hex'), - 0x28: "StopChannel", - 0x29: "System", - disconnect: Buffer.from('802a', 'hex'), - 0x2E: "TitleTouch", - 0x2F: "Accelerometer", - 0x30: "Gyrometer", - 0x31: "Inclinometer", - 0x32: "Compass", - 0x33: "Orientation", - 0x36: "PairedIdentityStateChanged", - 0x37: "Unsnap", - game_dvr_record: Buffer.from('a038', 'hex'), - power_off: Buffer.from('a039', 'hex'), - 0xF00: "MediaControllerRemoved", - media_command: Buffer.from('af01', 'hex'), - media_command_result: Buffer.from('af02', 'hex'), - media_state: Buffer.from('af03', 'hex'), - gamepad: Buffer.from('8f0a', 'hex'), - 0xF2B: "SystemTextConfiguration", - 0xF2C: "SystemTextInput", - 0xF2E: "SystemTouch", - 0xF34: "SystemTextAck", - 0xF35: "SystemTextDone" - } - - return message_flags[type] - } - - var structure = Packet[type]; - - return { - type: 'message', - name: type, - structure: structure, - packet_data: packet_data, - packet_decoded: false, - - channel_id: Buffer.from('\x00\x00\x00\x00\x00\x00\x00\x00'), - - setChannel: function(channel){ - Debug('Set channel to: '+channel.toString('hex')) - this.channel_id = Buffer.from(channel) - }, - - set: function(key, value, subkey = false){ - Debug('['+this.name+']', 'Set:', key, '=', value) - if(subkey == false){ - this.structure[key].value = value - } else { - this.structure[subkey][key].value = value - } - }, - - unpack: function(device = undefined){ - var payload = PacketStructure(this.packet_data) - - var packet = { - type: payload.readBytes(2).toString('hex'), - payload_length: payload.readUInt16(), - sequence_number: payload.readUInt32(), - target_participant_id: payload.readUInt32(), - source_participant_id: payload.readUInt32(), - flags: readFlags(payload.readBytes(2)), - channel_id: payload.readBytes(8), - protected_payload: payload.readBytes() - } - - this.setChannel(packet.channel_id); - - packet.name = packet.flags.type - this.name = packet.flags.type - //console.log('packet type:', packet) - packet.protected_payload = Buffer.from(packet.protected_payload.slice(0, -32)); - packet.signature = packet.protected_payload.slice(-32) - - Debug('Unpacking message:', this.name); - - // Lets decrypt the data when the payload is encrypted - if(packet.protected_payload != undefined){ - var key = device._crypto._encrypt(this.packet_data.slice(0, 16), device._crypto.getIv()); - - var decrypted_payload = device._crypto._decrypt(packet.protected_payload, key); - packet.decrypted_payload = PacketStructure(decrypted_payload).toBuffer() - decrypted_payload = PacketStructure(decrypted_payload) - - this.structure = Packet[packet.name] - var protected_structure = Packet[packet.name] - packet['protected_payload'] = {} - - for(name in protected_structure){ - packet.protected_payload[name] = protected_structure[name].unpack(decrypted_payload) - } - } - - this.packet_decoded = packet; - - return this; - }, - - pack: function(device){ - Debug('Packing message:', this.name); - - var payload = PacketStructure() - - for(name in this.structure){ - this.structure[name].pack(payload) - } - - var header = PacketStructure() - header.writeBytes(Buffer.from('d00d', 'hex')) - header.writeUInt16(payload.toBuffer().length) - header.writeUInt32(device._request_num) - header.writeUInt32(device._target_participant_id) - header.writeUInt32(device._source_participant_id) - header.writeBytes(setFlags(this.name)) - header.writeBytes(this.channel_id) - - if(payload.toBuffer().length % 16 > 0) - { - var padStart = payload.toBuffer().length % 16; - var padTotal = (16-padStart); - for(var paddingnum = (padStart+1); paddingnum <= 16; paddingnum++) - { - payload.writeUInt8(padTotal); - } - } - - var key = device._crypto._encrypt(header.toBuffer().slice(0, 16), device._crypto.getIv()); - var encrypted_payload = device._crypto._encrypt(payload.toBuffer(), device._crypto.getEncryptionKey(), key); - - var packet = Buffer.concat([ - header.toBuffer(), - encrypted_payload - ]); - - var protected_payload_hash = device._crypto._sign(packet); - packet = Buffer.concat([ - packet, - Buffer.from(protected_payload_hash) - ]); - - return packet; - } - } -} diff --git a/src/packet/packer.js b/src/packet/packer.js deleted file mode 100644 index d535b4f..0000000 --- a/src/packet/packer.js +++ /dev/null @@ -1,55 +0,0 @@ -var SimplePacket = require('./simple'); -var MessagePacket = require('./message'); -var Debug = require('debug')('smartglass:packer') - -module.exports = function(type) -{ - var Types = { - d00d: 'message', - cc00: 'simple.connect_request', - cc01: 'simple.connect_response', - dd00: 'simple.discovery_request', - dd01: 'simple.discovery_response', - dd02: 'simple.poweron', - } - - var loadPacketStructure = function(type, value = false){ - if(type.slice(0, 6) == 'simple'){ - return SimplePacket(type.slice(7), value); - } else if(type.slice(0, 7) == 'message'){ - return MessagePacket(type.slice(8), value); - } else { - Debug('[packet/packer.js] Packet format not found: ', type.toString('hex')); - return false - } - } - - var packet_type = type.slice(0,2).toString('hex') - var structure = '' - - if(packet_type in Types){ - // We got a packet that we need to unpack - var packet_value = type; - type = Types[packet_type]; - structure = loadPacketStructure(type, packet_value) - } else { - structure = loadPacketStructure(type) - } - - return { - type: type, - structure: structure, - set: function(key, value, protected_payload = false){ - this.structure.set(key, value, protected_payload) - }, - pack: function(device = undefined){ - return structure.pack(device) - }, - unpack: function(device = undefined){ - return structure.unpack(device) - }, - setChannel: function(channel){ - this.structure.setChannel(channel) - } - } -} diff --git a/src/packet/simple.js b/src/packet/simple.js deleted file mode 100644 index ec1490e..0000000 --- a/src/packet/simple.js +++ /dev/null @@ -1,301 +0,0 @@ -var PacketStructure = require('./structure'); -var Packer = require('./packer'); -var Debug = require('debug')('smartglass:packet_simple') - -module.exports = function(packet_format, packet_data = false){ - var Type = { - uInt32: function(value){ - return { - value: value, - pack: function(packet_structure){ - return packet_structure.writeUInt32(this.value); - }, - unpack: function(packet_structure){ - return packet_structure.readUInt32(); - } - } - }, - uInt16: function(value){ - return { - value: value, - pack: function(packet_structure){ - return packet_structure.writeUInt16(this.value); - }, - unpack: function(packet_structure){ - return packet_structure.readUInt16(); - } - } - }, - bytes: function(length, value){ - return { - value: value, - length: length, - pack: function(packet_structure){ - return packet_structure.writeBytes(this.value); - }, - unpack: function(packet_structure){ - return packet_structure.readBytes(length); - } - } - }, - sgString: function(value){ - return { - value: value, - pack: function(packet_structure){ - return packet_structure.writeSGString(this.value); - }, - unpack: function(packet_structure){ - return packet_structure.readSGString().toString(); - } - } - } - } - - var Packet = { - poweron: { - liveid: Type.sgString(), - }, - discovery_request: { - flags: Type.uInt32('0'), - client_type: Type.uInt16('3'), - min_version: Type.uInt16('0'), - max_version: Type.uInt16('2') - }, - discovery_response: { - flags: Type.uInt32('0'), - client_type: Type.uInt16('0'), - name: Type.sgString(), - uuid: Type.sgString(), - last_error: Type.uInt32('0'), - certificate_length: Type.uInt16('0'), - certificate: Type.bytes(), - }, - connect_request: { - uuid: Type.bytes(16, ''), - public_key_type: Type.uInt16('0'), - public_key: Type.bytes(64, ''), - iv: Type.bytes(16, ''), - protected_payload: Type.bytes() - }, - connect_request_protected: { - userhash: Type.sgString(''), - jwt: Type.sgString(''), - connect_request_num: Type.uInt32('0'), - connect_request_group_start: Type.uInt32('0'), - connect_request_group_end: Type.uInt32('1') - }, - connect_response: { - iv: Type.bytes(16, ''), - protected_payload: Type.bytes() - }, - connect_response_protected: { - connect_result: Type.uInt16('1'), - pairing_state: Type.uInt16('2'), - participant_id: Type.uInt32('0'), - }, - }; - - var structure = Packet[packet_format]; - - // Load protected payload PacketStructure - if(structure.protected_payload != undefined){ - var protected_payload = PacketStructure(); - var protected_structure = Packet[packet_format+'_protected']; - //structure.protected_payload = protected_structure - var structure_protected = protected_structure - } - - return { - type: 'simple', - name: packet_format, - structure: structure, - structure_protected: structure_protected || false, - packet_data: packet_data, - packet_decoded: false, - - set: function(key, value, is_protected = false){ - if(is_protected == false){ - this.structure[key].value = value - - if(this.structure[key].length != undefined) - this.structure[key].length = value.length - } else { - this.structure_protected[key].value = value - - if(this.structure_protected[key].length != undefined) - this.structure_protected[key].length = value.length - } - }, - - unpack: function(device = undefined){ - var payload = PacketStructure(this.packet_data) - - var packet = { - type: payload.readBytes(2).toString('hex'), - payload_length: payload.readUInt16(), - version: payload.readUInt16() - } - - if(packet.version != '0' && packet.version != '2'){ - packet.protected_payload_length = packet.version - packet.version = payload.readUInt16() - } - - for(name in this.structure){ - packet[name] = this.structure[name].unpack(payload) - this.set(name, packet[name]) - } - - if(packet.type == 'dd02'){ - this.name = 'poweron' - } - - Debug('Unpacking message:', this.name); - Debug('payload:', this.packet_data.toString('hex')); - - // Lets decrypt the data when the payload is encrypted - if(packet.protected_payload != undefined){ - - packet.protected_payload = packet.protected_payload.slice(0, -32); - packet.signature = packet.protected_payload.slice(-32) - - var decrypted_payload = device._crypto._decrypt(packet.protected_payload, packet.iv).slice(0, packet.protected_payload_length); - decrypted_payload = PacketStructure(decrypted_payload) - - - var protected_structure = Packet[packet_format+'_protected']; - packet.protected_payload = {} - - for(name in protected_structure){ - packet.protected_payload[name] = protected_structure[name].unpack(decrypted_payload) - this.set('protected_payload', packet.protected_payload) - } - } - - this.packet_decoded = packet; - - return this; - }, - - pack: function(device = false){ - Debug('Packing message:', this.name); - var payload = PacketStructure() - - for(name in this.structure){ - if(name != 'protected_payload'){ - this.structure[name].pack(payload) - - } else { - var protected_structure = this.structure_protected - - for(var name_struct in protected_structure){ - - if(this.structure.protected_payload.value != undefined){ - protected_structure[name_struct].value = this.structure.protected_payload.value[name_struct] - } - - protected_structure[name_struct].pack(protected_payload) - } - - var protected_payload_length = protected_payload.toBuffer().length - - if(protected_payload.toBuffer().length % 16 > 0) - { - var padStart = protected_payload.toBuffer().length % 16; - var padTotal = (16-padStart); - for(var paddingnum = (padStart+1); paddingnum <= 16; paddingnum++) - { - protected_payload.writeUInt8(padTotal); - } - } - - var protected_payload_length_real = protected_payload.toBuffer().length - var encrypted_payload = device._crypto._encrypt(protected_payload.toBuffer(), device._crypto.getEncryptionKey(), this.structure.iv.value); - payload.writeBytes(encrypted_payload) - } - } - - var packet = ''; - - if(this.name == 'poweron'){ - packet = this._pack(Buffer.from('DD02', 'hex'), payload.toBuffer(), '') - - } else if(this.name == 'discovery_request'){ - packet = this._pack(Buffer.from('DD00', 'hex'), payload.toBuffer(), Buffer.from('0000', 'hex')) - - } else if(this.name == 'discovery_response'){ - packet = this._pack(Buffer.from('DD01', 'hex'), payload.toBuffer(), '2') - - } else if(this.name == 'connect_request'){ - - packet = this._pack(Buffer.from('CC00', 'hex'), payload.toBuffer(), Buffer.from('0002', 'hex'), protected_payload_length, protected_payload_length_real) - - // Sign protected payload - var protected_payload_hash = device._crypto._sign(packet); - packet = Buffer.concat([ - packet, - Buffer.from(protected_payload_hash) - ]); - - } else if(this.name == 'connect_response'){ - packet = this._pack(Buffer.from('CC01', 'hex'), payload.toBuffer(), '2') - - // } else if(this.name == 'connect_request_protected'){ - // // Pad packet - // if(payload.toBuffer().length > 16) - // { - // var padStart = payload.toBuffer().length % 16; - // var padTotal = (16-padStart); - // for(var paddingnum = (padStart+1); paddingnum <= 16; paddingnum++) - // { - // payload.writeUInt8(padTotal); - // - // } - // } - // - // var encrypted_payload = device._crypto._encrypt(payload.toBuffer(), device._crypto.getIv()); - // encrypted_payload = PacketStructure(encrypted_payload) - // - // packet = encrypted_payload.toBuffer(); - } else { - packet = payload.toBuffer(); - } - - return packet; - }, - - _pack: function(type, payload, version, protected_payload_length = false, protected_payload_length_real = 0) - { - var payload_length = PacketStructure(); - - if(protected_payload_length !== false) - { - payload_length.writeUInt16(payload.length-protected_payload_length_real); - payload_length = payload_length.toBuffer(); - - var protected_length = PacketStructure(); - protected_length.writeUInt16(protected_payload_length); - protected_length = protected_length.toBuffer(); - - return Buffer.concat([ - type, - payload_length, - protected_length, - version, - payload - ]); - - } else { - payload_length.writeUInt16(payload.length); - payload_length = payload_length.toBuffer(); - - return Buffer.concat([ - type, - payload_length, - Buffer.from('\x00' + String.fromCharCode(version)), - payload - ]); - } - }, - } -} diff --git a/src/packet/structure.js b/src/packet/structure.js deleted file mode 100644 index be4ea63..0000000 --- a/src/packet/structure.js +++ /dev/null @@ -1,154 +0,0 @@ -module.exports = function(packet) -{ - if(packet == undefined) - packet = Buffer.from(''); - - return { - _packet: packet, - _totalLength: packet.length, - _offset: 0, - - setOffset: function(offset) - { - this._offset = offset; - }, - - getOffset: function() - { - return this._offset; - }, - - writeSGString: function(data) - { - var lengthBuffer = Buffer.allocUnsafe(2); - lengthBuffer.writeUInt16BE(data.length, 0); - - var dataBuffer = Buffer.from(data + '\x00'); - - this._add(Buffer.concat([ - lengthBuffer, - dataBuffer - ])); - - return this; - }, - - readSGString: function() - { - var dataLength = this.readUInt16(); - var data = this._packet.slice(this._offset, this._offset+dataLength); - - this._offset = (this._offset+1+dataLength); - - return data; - }, - - writeBytes: function(data, type) - { - var dataBuffer = Buffer.from(data, type); - - this._add(dataBuffer); - return this; - }, - - readBytes: function(count = false) - { - var data = ''; - - if(count == false){ - data = this._packet.slice(this._offset); - this._offset = (this._totalLength); - } else { - data = this._packet.slice(this._offset, this._offset+count); - this._offset = (this._offset+count); - } - - return data; - }, - - writeUInt8: function(data) - { - var tempBuffer = Buffer.allocUnsafe(1); - tempBuffer.writeUInt8(data, 0); - this._add(tempBuffer); - return this; - }, - - readUInt8: function() - { - var data = this._packet.readUInt8(this._offset); - this._offset = (this._offset+1); - - return data; - }, - - writeUInt16: function(data) - { - var tempBuffer = Buffer.allocUnsafe(2); - tempBuffer.writeUInt16BE(data, 0); - this._add(tempBuffer); - return this; - }, - - readUInt16: function() - { - var data = this._packet.readUInt16BE(this._offset); - this._offset = (this._offset+2); - - return data; - }, - - writeUInt32: function(data) - { - var tempBuffer = Buffer.allocUnsafe(4); - tempBuffer.writeUInt32BE(data, 0); - this._add(tempBuffer); - return this; - }, - - readUInt32: function() - { - var data = this._packet.readUInt32BE(this._offset); - this._offset = (this._offset+4); - - return data; - }, - - writeInt32: function(data) - { - var tempBuffer = Buffer.allocUnsafe(4); - tempBuffer.writeInt32BE(data, 0); - this._add(tempBuffer); - return this; - }, - - readInt32: function() - { - var data = this._packet.readInt32BE(this._offset); - this._offset = (this._offset+4); - - return data; - }, - - readUInt64: function() - { - var data = this.readBytes(8) - - return data - }, - - toBuffer: function() - { - return this._packet; - }, - - /* Private functions */ - _add(data) - { - this._packet = Buffer.concat([ - this._packet, - data - ]); - }, - }; -} diff --git a/src/sgcrypto.js b/src/sgcrypto.js deleted file mode 100644 index 977db41..0000000 --- a/src/sgcrypto.js +++ /dev/null @@ -1,172 +0,0 @@ -const crypto = require('crypto'); - -module.exports = function() -{ - return { - pubkey: Buffer.from('', 'hex'), - secret: Buffer.from('', 'hex'), - encryptionkey: false, - iv: false, - hash_key: false, - - load: function(pubkey, secret) - { - if(pubkey != undefined && secret != undefined) - { - this.pubkey = Buffer.from(pubkey); - this.secret = Buffer.from(secret); - } - - var data = { - 'aes_key': Buffer.from(this.secret.slice(0, 16)), - 'aes_iv': Buffer.from(this.secret.slice(16, 32)), - 'hmac_key': Buffer.from(this.secret.slice(32)) - }; - - this.iv = data.aes_iv; - this.hash_key = data.hmac_key; - this.encryptionkey = data.aes_key; - }, - - getSecret: function() - { - return this.secret; - }, - - getHmac: function() - { - if(this.encryptionkey == false) - this.load(); - - return this.hash_key; - }, - - signPublicKey: function(public_key) - { - var sha512 = crypto.createHash("sha512"); - - var EC = require('elliptic').ec; - var ec = new EC('p256'); - - // Generate keys - var key1 = ec.genKeyPair(); - var key2 = ec.keyFromPublic(public_key, 'hex') - //var public_key_client = key2 - - var shared1 = key1.derive(key2.getPublic()); - var derived_secret = Buffer.from(shared1.toString(16), 'hex') - - var public_key_client = key1.getPublic('hex') - - var pre_salt = Buffer.from('d637f1aae2f0418c', 'hex') - var post_salt = Buffer.from('a8f81a574e228ab7', 'hex') - derived_secret = Buffer.from(pre_salt.toString('hex')+derived_secret.toString('hex')+post_salt.toString('hex'), 'hex') - // Hash shared secret - var sha = sha512.update(derived_secret); - derived_secret = sha.digest(); - - return { - public_key: public_key_client.toString('hex').slice(2), - secret: derived_secret.toString('hex') - } - }, - - getPublicKey: function() - { - return this.pubkey; - }, - - getEncryptionKey: function() - { - if(this.encryptionkey == false) - this.load(); - - return this.encryptionkey; - }, - - getIv: function() - { - if(this.iv == false) - this.load(); - - return this.iv; - }, - - getHashKey: function() - { - if(this.hash_key == false) - this.load(); - - return this.hash_key; - }, - - _encrypt(data, key = false, iv = false) - { - data = Buffer.from(data); - - if(iv == false) - iv = Buffer.from('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'); - - if(key == false){ - key = this.getEncryptionKey() - } - - var cipher = crypto.createCipheriv('aes-128-cbc', key, iv); - - cipher.setAutoPadding(false); - var encryptedPayload = cipher.update(data, 'binary', 'binary'); - encryptedPayload += cipher.final('binary'); - - return Buffer.from(encryptedPayload, 'binary'); - }, - - _decrypt(data, iv, key = false) - { - data = this._addPadding(data); - - if(key == false){ - key = this.getEncryptionKey() - } - - if(iv == false) - iv = Buffer.from('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'); - - var cipher = crypto.createDecipheriv('aes-128-cbc', key, iv); - cipher.setAutoPadding(false); - - var decryptedPayload = cipher.update(data, 'binary', 'binary'); - decryptedPayload += cipher.final('binary'); - - - return this._removePadding(Buffer.from(decryptedPayload, 'binary')); - }, - - _sign: function(data) - { - var hashHmac = crypto.createHmac('sha256', this.getHashKey()); - hashHmac.update(data, 'binary', 'binary'); - var protectedPayloadHash = hashHmac.digest('binary'); - - return Buffer.from(protectedPayloadHash, 'binary'); - }, - - _removePadding(payload) - { - var length = Buffer.from(payload.slice(-1)); - length = length.readUInt8(0); - - if(length > 0 && length < 16) - { - return Buffer.from(payload.slice(0, payload.length-length)); - } else { - return payload; - } - }, - - _addPadding(payload) - { - return payload; - } - } - -} diff --git a/src/smartglass.js b/src/smartglass.js deleted file mode 100644 index 94ae1a0..0000000 --- a/src/smartglass.js +++ /dev/null @@ -1,360 +0,0 @@ -const dgram = require('dgram'); -const Packer = require('./packet/packer'); -const Xbox = require('./xbox'); -const Events = require('./events'); - -module.exports = function() -{ - var id = Math.floor(Math.random() * (999 - 1)) + 1; - var Debug = require('debug')('smartglass:client-'+id) - - var events = Events() - - return { - _client_id: id, - _console: false, - _socket: false, - _events: events, - - _last_received_time: false, - _is_broadcast: false, - _ip: false, - _interval_timeout: false, - - _managers: {}, - _managers_num: 0, - - _connection_status: false, - _current_app: false, - - discovery: function(ip) - { - if(ip == undefined){ - this._ip = '255.255.255.255' - this._is_broadcast = true - } else { - this._ip = ip - } - - return new Promise(function(resolve, reject) { - this._getSocket() - - Debug('['+this._client_id+'] Crafting discovery_request packet'); - var discovery_packet = Packer('simple.discovery_request') - var message = discovery_packet.pack() - - var consoles_found = [] - - this._events.on('_on_discovery_response', function(message, xbox, remote){ - consoles_found.push({ - message: message.packet_decoded, - remote: remote - }) - - if(this._is_broadcast == false){ - Debug('Console found, clear timeout because we query an ip (direct)') - clearTimeout(this._interval_timeout) - resolve(consoles_found) - this._closeClient(); - } - - }.bind(this)); - - this._send(message); - - this._interval_timeout = setTimeout(function(){ - Debug('Discovery timeout after 2 sec (broadcast)') - this._closeClient(); - - resolve(consoles_found) - }.bind(this), 2000); - }.bind(this)) - }, - - getActiveApp: function() - { - return this._current_app - }, - - isConnected: function() - { - return this._connection_status - }, - - powerOn: function(options) - { - return new Promise(function(resolve, reject) { - this._getSocket(); - - if(options.tries == undefined){ - options.tries = 5; - } - - this._ip = options.ip - - var poweron_packet = Packer('simple.poweron') - poweron_packet.set('liveid', options.live_id) - var message = poweron_packet.pack() - - var try_num = 0; - var sendBoot = function(client, callback) - { - client._send(message); - - try_num = try_num+1; - if(try_num <= options.tries) - { - setTimeout(sendBoot, 1000, client); - } else { - client._closeClient(); - - client.discovery(options.ip).then(function(consoles){ - if(consoles.length > 0){ - resolve({ - status: 'success' - }) - } else { - reject({ - status: 'error_discovery', - error: 'Console was not found on network. Probably failed' - }) - } - }, function(error){ - reject({ - status: 'error_discovery', - error: 'Console was not found on network. Probably failed' - }) - }) - } - } - setTimeout(sendBoot, 1000, this); - }.bind(this)) - }, - - powerOff: function() - { - return new Promise(function(resolve, reject) { - if(this.isConnected() == true){ - Debug('['+this._client_id+'] Sending power off command to: '+this._console._liveid) - - this._console.get_requestnum() - var poweroff = Packer('message.power_off'); - poweroff.set('liveid', this._console._liveid) - var message = poweroff.pack(this._console); - - this._send(message); - - setTimeout(function(){ - this.disconnect() - resolve(true) - }.bind(this), 1000); - - } else { - reject({ - status: 'error_not_connected', - error: 'Console is not connected' - }) - } - }.bind(this)) - }, - - connect: function(ip, userhash, xsts_token) - { - this._ip = ip - - return new Promise(function(resolve, reject) { - this.discovery(this._ip).then(function(consoles){ - if(consoles.length > 0){ - Debug('['+this._client_id+'] Console is online. Lets connect...') - // clearTimeout(this._interval_timeout) - - this._getSocket(); - - var xbox = Xbox(consoles[0].remote.address, consoles[0].message.certificate); - var message = xbox.connect(userhash, xsts_token); - - this._send(message); - - this._console = xbox - - this._events.on('_on_connect_response', function(message, xbox, remote, smartglass){ - if(message.packet_decoded.protected_payload.connect_result == '0'){ - Debug('['+this._client_id+'] Console is connected') - this._connection_status = true - resolve() - } else { - this._connection_status = false - - var errorTable = { - 0: 'Success', - 1: 'Pending login. Reconnect to complete', - 2: 'Unknown error', - 3: 'No anonymous connections', - 4: 'Device limit exceeded', - 5: 'Smartglass is disabled on the Xbox console', - 6: 'User authentication failed', - 7: 'Sign-in failed', - 8: 'Sign-in timeout', - 9: 'Sign-in required' - } - - Debug('['+this._client_id+'] Error during connect, xbox returned result:', errorTable[message.packet_decoded.protected_payload.connect_result]) - this._closeClient() - - reject({ - 'error': 'connection_rejected', - 'message': errorTable[message.packet_decoded.protected_payload.connect_result], - 'details': message.packet_decoded.protected_payload - }) - } - }.bind(this)) - - this._events.on('_on_timeout', function(message, xbox, remote, smartglass){ - Debug('['+this._client_id+'] Client timeout...') - this._connection_status = false - - reject({ - 'error': 'connection_timeout', - 'message': 'No response from the xbox' - }) - }.bind(this)) - } else { - Debug('['+this._client_id+'] Device is unavailable...') - this._connection_status = false - - reject({ - 'error': 'device_unavailable', - 'message': 'Xbox is unavailable on '+ip - }) - } - }.bind(this), function(error){ - reject(error) - }) - }.bind(this)) - }, - - on: function(name, callback) - { - this._events.on(name, callback) - }, - - disconnect: function() - { - var xbox = this._console; - - xbox.get_requestnum() - - var disconnect = Packer('message.disconnect') - disconnect.set('reason', 4) - disconnect.set('error_code', 0) - var disconnect_message = disconnect.pack(xbox) - - this._send(disconnect_message); - - this._closeClient() - }, - - recordGameDvr: function() - { - return new Promise(function(resolve, reject) { - if(this.isConnected() == true){ - if(this._console._is_authenticated == true){ - Debug('['+this._client_id+'] Sending record game dvr command') - - this._console.get_requestnum() - var game_dvr_record = Packer('message.game_dvr_record') - game_dvr_record.set('start_time_delta', -60) // Needs to be signed int - game_dvr_record.set('end_time_delta', 0) - var message = game_dvr_record.pack(this._console) - - this._send(message); - - resolve(true) - } else { - reject({ - status: 'error_not_authenticated', - error: 'Game DVR record function requires an authenticated user' - }) - } - } else { - reject({ - status: 'error_not_connected', - error: 'Console is not connected' - }) - } - }.bind(this)) - }, - - addManager: function(name, manager) - { - Debug('Loaded manager: '+name + '('+this._managers_num+')') - this._managers[name] = manager - this._managers[name].load(this, this._managers_num) - this._managers_num++ - }, - - getManager: function(name) - { - if(this._managers[name] != undefined) - return this._managers[name] - else - return false - }, - - _getSocket: function() - { - Debug('['+this._client_id+'] Get active socket'); - - this._socket = dgram.createSocket('udp4'); - this._socket.bind(); - - this._socket.on('listening', function(message, remote){ - if(this._is_broadcast == true) - this._socket.setBroadcast(true); - }.bind(this)) - - this._socket.on('error', function(error){ - Debug('Socket Error:') - Debug(error) - }.bind(this)) - - this._socket.on('message', function(message, remote){ - this._last_received_time = Math.floor(Date.now() / 1000) - var xbox = this._console - this._events.emit('receive', message, xbox, remote, this); - }.bind(this)); - - this._socket.on('close', function() { - Debug('['+this._client_id+'] UDP socket closed.'); - }.bind(this)); - - return this._socket; - }, - - _closeClient: function() - { - Debug('['+this._client_id+'] Client closed'); - this._connection_status = false - - clearInterval(this._interval_timeout) - if(this._socket != false){ - this._socket.close(); - this._socket = false - } - - }, - - _send: function(message, ip) - { - if(ip == undefined){ - ip = this._ip - } - - if(this._socket != false) - this._socket.send(message, 0, message.length, 5050, ip, function(err, bytes) { - Debug('['+this._client_id+'] Sending packet to client: '+this._ip+':'+5050); - Debug(message.toString('hex')) - }.bind(this)); - }, - } -} diff --git a/src/xbox.js b/src/xbox.js deleted file mode 100644 index cded5ab..0000000 --- a/src/xbox.js +++ /dev/null @@ -1,131 +0,0 @@ -var Packer = require('./packet/packer'); -const SGCrypto = require('./sgcrypto.js'); -const uuidParse = require('uuid-parse'); -var uuid = require('uuid'); -const os = require('os'); -const EOL = os.EOL; -const crypto = require('crypto'); -var jsrsasign = require('jsrsasign'); -var Debug = require('debug')('smartglass:xbox') - -module.exports = function(ip, certificate) -{ - return { - _ip: ip, - _certificate: certificate, - - _iv: false, - _liveid: false, - _is_authenticated: false, - _participantid: false, - - _connection_status: false, - _request_num: 1, - _target_participant_id: 0, - _source_participant_id: 0, - - _fragments: {}, - - _crypto: false, - - getIp: function() - { - return this._ip - }, - - getCertificate: function() - { - return this._certificate - }, - - getLiveid: function() - { - return this._liveid; - }, - - setLiveid: function(liveid) - { - this._liveid = liveid; - }, - - get_requestnum: function() - { - var num = this._request_num; - - this._request_num++; - - Debug('this._request_num set to '+this._request_num) - return num; - }, - - set_participantid: function(participantId) - { - this._participantid = participantId; - this._source_participant_id = participantId; - }, - - connect: function(uhs, xsts_token) - { - // // Set liveid - var pem = '-----BEGIN CERTIFICATE-----'+EOL+this.getCertificate().toString('base64').match(/.{0,64}/g).join('\n')+'-----END CERTIFICATE-----'; - var deviceCert = new jsrsasign.X509(); - deviceCert.readCertPEM(pem); - - // var hSerial = deviceCert.getSerialNumberHex(); // '009e755e" hexadecimal string - // var sIssuer = deviceCert.getIssuerString(); // '/C=US/O=z2' - // var sSubject = deviceCert.getSubjectString(); // '/C=US/O=z2' - // var sNotBefore = deviceCert.getNotBefore(); // '100513235959Z' - // var sNotAfter = deviceCert.getNotAfter(); // '200513235959Z' - - this.setLiveid(deviceCert.getSubjectString().slice(4)) - - // Set uuid - var uuid4 = Buffer.from(uuidParse.parse(uuid.v4())); - - // Create public key - var ecKey = jsrsasign.X509.getPublicKeyFromCertPEM(pem); - - Debug('Signing public key: '+ecKey.pubKeyHex); - - - this._crypto = new SGCrypto(); - var object = this._crypto.signPublicKey(ecKey.pubKeyHex) - - Debug('Crypto output:', object); - - - // Load crypto data - this.loadCrypto(object.public_key, object.secret); - - Debug('Sending connect_request to xbox'); - var discovery_request = Packer('simple.connect_request'); - discovery_request.set('uuid', uuid4); - discovery_request.set('public_key', this._crypto.getPublicKey()); - discovery_request.set('iv', this._crypto.getIv()); - - if(uhs != undefined && xsts_token != undefined){ - Debug('- Connecting using token:', uhs+':'+xsts_token); - discovery_request.set('userhash', uhs, true); - discovery_request.set('jwt', xsts_token, true); - - this._is_authenticated = true - } else { - Debug('- Connecting using anonymous login'); - this._is_authenticated = false - } - - var message = discovery_request.pack(this); - - return message - }, - - loadCrypto: function(public_key, shared_secret) - { - Debug('Loading crypto:'); - Debug('- Public key:', public_key); - Debug('- Shared secret:', shared_secret); - this._crypto = new SGCrypto(); - this._crypto.load(Buffer.from(public_key, 'hex'), Buffer.from(shared_secret, 'hex')) - } - }; -} diff --git a/test/Controller.spec.ts b/test/Controller.spec.ts new file mode 100644 index 0000000..232b973 --- /dev/null +++ b/test/Controller.spec.ts @@ -0,0 +1,92 @@ +import { expect } from "chai"; +import { Controller, ControllerButtons } from "../src/Controller"; +import "mocha"; + +// Returns the numeric value for the given button. +const getButtonValue = (button: ControllerButtons) => { + return Math.pow(2, 15 - button); +}; + +describe("Controller", () => { + let controller: Controller; + beforeEach(() => { + controller = new Controller(); + }); + + describe("Buttons", () => { + it("should handle calls to press", () => { + controller.pressButton(ControllerButtons.X); + + expect(controller.getState().buttons).to.equal( + getButtonValue(ControllerButtons.X) + ); + }); + + it("should handle calls to release", () => { + controller.pressButton(ControllerButtons.X); + controller.releaseButton(ControllerButtons.X); + + expect(controller.getState().buttons).to.equal(0); + }); + + it("should handle releasing an unpressed button", () => { + controller.pressButton(ControllerButtons.X); + controller.releaseButton(ControllerButtons.A); + + expect(controller.getState().buttons).to.equal( + getButtonValue(ControllerButtons.X) + ); + }); + + it("should handle pressing multiple buttons", () => { + controller.pressButton(ControllerButtons.X); + controller.pressButton(ControllerButtons.A); + + expect(controller.getState().buttons).to.equal( + getButtonValue(ControllerButtons.X) + + getButtonValue(ControllerButtons.A) + ); + }); + + it("should handle multiple calls to press", () => { + controller.pressButton(Controller.Buttons.A); + controller.pressButton(Controller.Buttons.A); + + expect(controller.getState().buttons).to.equal( + getButtonValue(ControllerButtons.A) + ); + }); + + it("should handle releasing a press after the provided time", async () => { + controller.pressButton(ControllerButtons.A, 200); + + expect(controller.getState().buttons).to.equal( + getButtonValue(ControllerButtons.A) + ); + + await new Promise((resolve) => setTimeout(resolve, 100)); + + // Still pressed! + expect(controller.getState().buttons).to.equal( + getButtonValue(ControllerButtons.A) + ); + + await new Promise((resolve) => setTimeout(resolve, 110)); + + // No longer pressed! + expect(controller.getState().buttons).to.equal(0); + }); + + it("should handle releasing all buttons", () => { + controller.pressButton(ControllerButtons.A); + controller.pressButton(ControllerButtons.Nexus); + controller.pressButton(ControllerButtons.Menu); + + expect(controller.getState().buttons).to.be.greaterThan(0); + + controller.releaseAllButtons(); + + expect(controller.getState().buttons).to.equal(0); + }); + }); +}); diff --git a/test/Flags.spec.ts b/test/Flags.spec.ts new file mode 100644 index 0000000..4188227 --- /dev/null +++ b/test/Flags.spec.ts @@ -0,0 +1,179 @@ +import { expect } from "chai"; +import { Flags } from "../src/Flags"; +import "mocha"; + +describe("Flags", () => { + describe("Generic Tests", () => { + it("should set the needsAck flag", () => { + const flag = new Flags({ + version: 0, + messageType: 0, + needsAck: true, + isFragment: false, + }); + + expect(flag.toBinary()).to.equal("0010000000000000"); + }); + + it("should set the isFragment flag", () => { + const flag = new Flags({ + version: 0, + messageType: 0, + needsAck: false, + isFragment: true, + }); + + expect(flag.toBinary()).to.equal("0001000000000000"); + }); + + it("should set the isFragment and needsAck flag", () => { + const flag = new Flags({ + version: 0, + messageType: 0, + needsAck: true, + isFragment: true, + }); + + expect(flag.toBinary()).to.equal("0011000000000000"); + }); + + it("should set the version flag", () => { + const flag = new Flags({ + version: 3, + messageType: 0, + needsAck: false, + isFragment: false, + }); + + expect(flag.toBinary()).to.equal("1100000000000000"); + }); + + it("should throw if version flag is out of range", () => { + expect( + () => + new Flags({ + version: 4, + messageType: 0, + needsAck: false, + isFragment: false, + }) + ).to.throw(); + + expect( + () => + new Flags({ + version: -1, + messageType: 0, + needsAck: false, + isFragment: false, + }) + ).to.throw(); + }); + + it("should set the messageType flag", () => { + let flag = new Flags({ + version: 0, + messageType: 4095, + needsAck: false, + isFragment: false, + }); + + expect(flag.toBinary()).to.equal("0000111111111111"); + + flag = new Flags({ + version: 0, + messageType: 1, + needsAck: false, + isFragment: false, + }); + + expect(flag.toBinary()).to.equal("0000000000000001"); + }); + + it("should throw if messageType is out of range", () => { + expect( + () => + new Flags({ + version: 0, + messageType: 4096, + needsAck: false, + isFragment: false, + }) + ).to.throw(); + + expect( + () => + new Flags({ + version: 0, + messageType: -1, + needsAck: false, + isFragment: false, + }) + ).to.throw(); + }); + + it("should throw when parsing an invalid buffer", () => { + expect(() => Flags.parse(Buffer.from([]))).to.throw(); + }); + }); + + describe("Local Join Flags", () => { + const LocalJoinFlagBuffer = Buffer.from("a003", "hex"); + const LocalJoinFlagBytes = "1010000000000011"; + const version = 2; + const messageType = 3; + const needsAck = true; + const isFragment = false; + + it("should parse from a buffer", () => { + const localJoinFlag = Flags.parse(LocalJoinFlagBuffer); + expect(localJoinFlag.toBinary()).to.equal(LocalJoinFlagBytes); + expect(localJoinFlag.toBuffer()).to.deep.equal(LocalJoinFlagBuffer); + expect(localJoinFlag.version).to.equal(version); + expect(localJoinFlag.messageType).to.equal(messageType); + expect(localJoinFlag.needsAck).to.equal(needsAck); + expect(localJoinFlag.isFragment).to.equal(isFragment); + }); + + it("should correctly create a new instance", () => { + const localJoinFlag = new Flags({ + version, + messageType, + needsAck, + isFragment, + }); + expect(localJoinFlag.toBinary()).to.equal(LocalJoinFlagBytes); + expect(localJoinFlag.toBuffer()).to.deep.equal(LocalJoinFlagBuffer); + }); + }); + + describe("Acknowledge Flags", () => { + const AcknowledgeFlagBuffer = Buffer.from("8001", "hex"); + const AcknowledgeFlagBytes = "1000000000000001"; + const version = 2; + const messageType = 1; + const needsAck = false; + const isFragment = false; + + it("should parse from a buffer", () => { + const acknowledgeFlag = Flags.parse(AcknowledgeFlagBuffer); + expect(acknowledgeFlag.toBinary()).to.equal(AcknowledgeFlagBytes); + expect(acknowledgeFlag.toBuffer()).to.deep.equal(AcknowledgeFlagBuffer); + expect(acknowledgeFlag.version).to.equal(version); + expect(acknowledgeFlag.messageType).to.equal(messageType); + expect(acknowledgeFlag.needsAck).to.equal(needsAck); + expect(acknowledgeFlag.isFragment).to.equal(isFragment); + }); + + it("should correctly create a new instance", () => { + const acknowledgeFlag = new Flags({ + version, + messageType, + needsAck, + isFragment, + }); + expect(acknowledgeFlag.toBinary()).to.equal(AcknowledgeFlagBytes); + expect(acknowledgeFlag.toBuffer()).to.deep.equal(AcknowledgeFlagBuffer); + }); + }); +}); diff --git a/tests/data/packets/acknowledge b/tests/data/packets/acknowledge deleted file mode 100644 index 56d213e..0000000 Binary files a/tests/data/packets/acknowledge and /dev/null differ diff --git a/tests/data/packets/connect_request b/tests/data/packets/connect_request deleted file mode 100644 index ee6e9b9..0000000 Binary files a/tests/data/packets/connect_request and /dev/null differ diff --git a/tests/data/packets/connect_response b/tests/data/packets/connect_response deleted file mode 100644 index 0bbebd7..0000000 Binary files a/tests/data/packets/connect_response and /dev/null differ diff --git a/tests/data/packets/console_status b/tests/data/packets/console_status deleted file mode 100644 index f7f4e42..0000000 Binary files a/tests/data/packets/console_status and /dev/null differ diff --git a/tests/data/packets/disconnect b/tests/data/packets/disconnect deleted file mode 100644 index 86f9da0..0000000 Binary files a/tests/data/packets/disconnect and /dev/null differ diff --git a/tests/data/packets/discovery_request b/tests/data/packets/discovery_request deleted file mode 100644 index 0547bde..0000000 Binary files a/tests/data/packets/discovery_request and /dev/null differ diff --git a/tests/data/packets/discovery_response b/tests/data/packets/discovery_response deleted file mode 100644 index 2f5cf46..0000000 Binary files a/tests/data/packets/discovery_response and /dev/null differ diff --git a/tests/data/packets/gamedvr_record b/tests/data/packets/gamedvr_record deleted file mode 100644 index 17c69c6..0000000 Binary files a/tests/data/packets/gamedvr_record and /dev/null differ diff --git a/tests/data/packets/gamepad b/tests/data/packets/gamepad deleted file mode 100644 index 654b6fb..0000000 Binary files a/tests/data/packets/gamepad and /dev/null differ diff --git a/tests/data/packets/json b/tests/data/packets/json deleted file mode 100644 index bf9f590..0000000 Binary files a/tests/data/packets/json and /dev/null differ diff --git a/tests/data/packets/local_join b/tests/data/packets/local_join deleted file mode 100644 index 49e0afd..0000000 Binary files a/tests/data/packets/local_join and /dev/null differ diff --git a/tests/data/packets/media_command b/tests/data/packets/media_command deleted file mode 100644 index 32a34a3..0000000 Binary files a/tests/data/packets/media_command and /dev/null differ diff --git a/tests/data/packets/media_state b/tests/data/packets/media_state deleted file mode 100644 index a9d7467..0000000 Binary files a/tests/data/packets/media_state and /dev/null differ diff --git a/tests/data/packets/poweroff b/tests/data/packets/poweroff deleted file mode 100644 index 33a20f8..0000000 Binary files a/tests/data/packets/poweroff and /dev/null differ diff --git a/tests/data/packets/poweron b/tests/data/packets/poweron deleted file mode 100644 index 2b3a6eb..0000000 Binary files a/tests/data/packets/poweron and /dev/null differ diff --git a/tests/data/packets/start_channel_request b/tests/data/packets/start_channel_request deleted file mode 100644 index c3467d7..0000000 Binary files a/tests/data/packets/start_channel_request and /dev/null differ diff --git a/tests/data/packets/start_channel_response b/tests/data/packets/start_channel_response deleted file mode 100644 index 857c67d..0000000 Binary files a/tests/data/packets/start_channel_response and /dev/null differ diff --git a/tests/data/selfsigned_cert.crt b/tests/data/selfsigned_cert.crt deleted file mode 100644 index 866cb8e..0000000 --- a/tests/data/selfsigned_cert.crt +++ /dev/null @@ -1,11 +0,0 @@ -MIICAzCB7KADAgECAgEBMA0GCSqGSIb3DQEBCwUAMA8xDTALBgNVBAMMBFJ1c3Qw -HhcNMTcwOTMwMTUyNTQyWhcNMTgwOTMwMTUyNTQyWjAWMRQwEgYDVQQDDAtGRkZG -RkZGRkZGRjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBgV1Tgt95vXkqjYNC+8 -cX6s72olj3eSeeVGNXPga/hMaoj6yQSHC/Oib4VuZfSDGVxDI+70egSPI6Ax2mvQ -kp2jLzAtMAsGA1UdDwQEAwIDCDATBgNVHSUEDDAKBggrBgEFBQcDAjAJBgNVHSME -AjAAMA0GCSqGSIb3DQEBCwUAA4IBAQADWPJ8/Pe15pV6r4GCxjeg13vmVHaFm8eU -yc/ZZjDfaQcF2KgJNF0t7Kkvs4v5j9DDLjNLWMt4TxBPHzf6hhrv0Zdqo181i6uO -SluFNg28SdK9uCpZypVXka9hYbdvOLePLUKeCml9k43sFM+NIEr/2oYsTvWg7Bix -CKhb3RCdp3Z3P/8fWtRvTVjuyOpVpO2XPmsbsDQc5IfNeclVr9KE84rxnpC0CI1u -DhuY8iDq20FIBXUY95rljdfcBctgHx/odxtMZdLDRuZTcVgJ/gLxT8h6MgVtYc5i -Kw9MD/iLQ7QpmVr5R0W9KdMKOPOKR3GK4DH5ta40k2GLXjsSTEQO diff --git a/tests/events.js b/tests/events.js deleted file mode 100644 index 65fcad5..0000000 --- a/tests/events.js +++ /dev/null @@ -1,9 +0,0 @@ -var assert = require('assert'); -var fs = require('fs'); -const SGEvents = require('../src/events'); - -describe('smartglassEmitter', function(){ - it('should create a new smartglassEmitter instance', function(){ - var smartglassEmitter = SGEvents() - }); -}) diff --git a/tests/packet_packer.js b/tests/packet_packer.js deleted file mode 100644 index ca55f0f..0000000 --- a/tests/packet_packer.js +++ /dev/null @@ -1,16 +0,0 @@ -var assert = require('assert'); -var fs = require('fs'); -const Packer = require('../src/packet/packer'); - -describe('packet/packer', function(){ - - it('should return false on the structure if a packet could not be identified', function(){ - var packer_data = Packer('{}') - assert.deepStrictEqual(packer_data.structure, false) - }); - - it('should not return false on the structure if a packet could be identified', function(){ - var packer_data = Packer(Buffer.from('dd01', 'hex')) - assert.notDeepStrictEqual(packer_data.structure, false) - }); -}) diff --git a/tests/packet_packer_message.js b/tests/packet_packer_message.js deleted file mode 100644 index 60cf5f4..0000000 --- a/tests/packet_packer_message.js +++ /dev/null @@ -1,332 +0,0 @@ -var assert = require('assert'); -var fs = require('fs'); -const Packer = require('../src/packet/packer'); -var Xbox = require('../src/xbox'); - -var secret = Buffer.from('82bba514e6d19521114940bd65121af2'+'34c53654a8e67add7710b3725db44f77'+'30ed8e3da7015a09fe0f08e9bef3853c0506327eb77c9951769d923d863a2f5e', 'hex'); -var certificate = Buffer.from('041db1e7943878b28c773228ebdcfb05b985be4a386a55f50066231360785f61b60038caf182d712d86c8a28a0e7e2733a0391b1169ef2905e4e21555b432b262d', 'hex'); - -var packets = [ - {'message.console_status': 'tests/data/packets/console_status'}, - {'message.power_off': 'tests/data/packets/poweroff'}, - {'message.acknowledgement': 'tests/data/packets/acknowledge'}, - {'message.local_join': 'tests/data/packets/local_join'}, - {'message.disconnect': 'tests/data/packets/disconnect'}, - {'message.start_channel_request': 'tests/data/packets/start_channel_request'}, - {'message.start_channel_response': 'tests/data/packets/disconnect'}, - {'message.gamepad': 'tests/data/packets/gamepad'}, - {'message.media_state': 'tests/data/packets/media_state'}, - {'message.media_command': 'tests/data/packets/media_command'}, - {'message.json': 'tests/data/packets/json'}, - {'message.game_dvr_record': 'tests/data/packets/gamedvr_record'} -] - -var device = Xbox('127.0.0.1', certificate); -device.loadCrypto(certificate.toString('hex'), secret.toString('hex')); - -describe('packet/packer/message', function(){ - - it('should unpack a console_status packet', function(){ - var data_packet = fs.readFileSync('tests/data/packets/console_status') - - var poweron_request = Packer(data_packet) - var message = poweron_request.unpack(device) - - assert.deepStrictEqual(message.type, 'message') - assert.deepStrictEqual(message.packet_decoded.sequence_number, 5) - assert.deepStrictEqual(message.packet_decoded.source_participant_id, 0) - assert.deepStrictEqual(message.packet_decoded.target_participant_id, 31) - - assert.deepStrictEqual(message.packet_decoded.flags.version, '2') - assert.deepStrictEqual(message.packet_decoded.flags.need_ack, true) - assert.deepStrictEqual(message.packet_decoded.flags.is_fragment, false) - assert.deepStrictEqual(message.packet_decoded.flags.type, 'console_status') - assert.deepStrictEqual(message.packet_decoded.channel_id, Buffer.from('\x00\x00\x00\x00\x00\x00\x00\x00')) - assert.deepStrictEqual(message.packet_decoded.protected_payload.live_tv_provider, 0) - assert.deepStrictEqual(message.packet_decoded.protected_payload.major_version, 10) - assert.deepStrictEqual(message.packet_decoded.protected_payload.minor_version, 0) - assert.deepStrictEqual(message.packet_decoded.protected_payload.build_number, 14393) - assert.deepStrictEqual(message.packet_decoded.protected_payload.locale, 'en-US') - - assert.deepStrictEqual(message.packet_decoded.protected_payload.apps[0].title_id, 714681658) - assert.deepStrictEqual(message.packet_decoded.protected_payload.apps[0].flags, Buffer.from('8003', 'hex')) - assert.deepStrictEqual(message.packet_decoded.protected_payload.apps[0].product_id, Buffer.from('00000000000000000000000000000000', 'hex')) - assert.deepStrictEqual(message.packet_decoded.protected_payload.apps[0].sandbox_id, Buffer.from('00000000000000000000000000000000', 'hex')) - assert.deepStrictEqual(message.packet_decoded.protected_payload.apps[0].aum_id, 'Xbox.Home_8wekyb3d8bbwe!Xbox.Home.Application') - }); - - it('should unpack a poweroff packet', function(){ - var data_packet = fs.readFileSync('tests/data/packets/poweroff') - - var poweroff_request = Packer(data_packet) - var message = poweroff_request.unpack(device) - - assert.deepStrictEqual(message.type, 'message') - assert.deepStrictEqual(message.packet_decoded.sequence_number, 1882) - assert.deepStrictEqual(message.packet_decoded.source_participant_id, 2) - assert.deepStrictEqual(message.packet_decoded.target_participant_id, 0) - - assert.deepStrictEqual(message.packet_decoded.flags.version, '2') - assert.deepStrictEqual(message.packet_decoded.flags.need_ack, true) - assert.deepStrictEqual(message.packet_decoded.flags.is_fragment, false) - assert.deepStrictEqual(message.packet_decoded.flags.type, 'power_off') - assert.deepStrictEqual(message.packet_decoded.channel_id, Buffer.from('\x00\x00\x00\x00\x00\x00\x00\x00')) - - assert.deepStrictEqual(message.packet_decoded.protected_payload.liveid, 'FD00112233FFEE66') - }); - - it('should unpack an acknowledge packet', function(){ - var data_packet = fs.readFileSync('tests/data/packets/acknowledge') - - var acknowledge = Packer(data_packet) - var message = acknowledge.unpack(device) - - assert.deepStrictEqual(message.type, 'message') - assert.deepStrictEqual(message.packet_decoded.sequence_number, 1) - assert.deepStrictEqual(message.packet_decoded.source_participant_id, 0) - assert.deepStrictEqual(message.packet_decoded.target_participant_id, 31) - - assert.deepStrictEqual(message.packet_decoded.flags.version, '2') - assert.deepStrictEqual(message.packet_decoded.flags.need_ack, false) - assert.deepStrictEqual(message.packet_decoded.flags.is_fragment, false) - assert.deepStrictEqual(message.packet_decoded.flags.type, 'acknowledge') - assert.deepStrictEqual(message.packet_decoded.channel_id, Buffer.from('\x10\x00\x00\x00\x00\x00\x00\x00')) - - assert.deepStrictEqual(message.packet_decoded.protected_payload.low_watermark, 0) - assert.deepStrictEqual(message.packet_decoded.protected_payload.processed_list.length, 1) - assert.deepStrictEqual(message.packet_decoded.protected_payload.rejected_list.length, 0) - assert.deepStrictEqual(message.packet_decoded.protected_payload.processed_list[0].id, 1) - }); - - it('should unpack a local_join packet', function(){ - var data_packet = fs.readFileSync('tests/data/packets/local_join') - - var local_join = Packer(data_packet) - var message = local_join.unpack(device) - - assert.deepStrictEqual(message.type, 'message') - assert.deepStrictEqual(message.packet_decoded.sequence_number, 1) - assert.deepStrictEqual(message.packet_decoded.source_participant_id, 31) - assert.deepStrictEqual(message.packet_decoded.target_participant_id, 0) - - assert.deepStrictEqual(message.packet_decoded.flags.version, '0') - assert.deepStrictEqual(message.packet_decoded.flags.need_ack, true) - assert.deepStrictEqual(message.packet_decoded.flags.is_fragment, false) - assert.deepStrictEqual(message.packet_decoded.flags.type, 'local_join') - assert.deepStrictEqual(message.packet_decoded.channel_id, Buffer.from('\x00\x00\x00\x00\x00\x00\x00\x00')) - - assert.deepStrictEqual(message.packet_decoded.protected_payload.client_type, 8) - assert.deepStrictEqual(message.packet_decoded.protected_payload.native_width, 600) - assert.deepStrictEqual(message.packet_decoded.protected_payload.native_height, 1024) - assert.deepStrictEqual(message.packet_decoded.protected_payload.dpi_x, 160) - assert.deepStrictEqual(message.packet_decoded.protected_payload.dpi_y, 160) - assert.deepStrictEqual(message.packet_decoded.protected_payload.device_capabilities, Buffer.from('ffffffffffffffff', 'hex')) - assert.deepStrictEqual(message.packet_decoded.protected_payload.client_version, 133713371) - assert.deepStrictEqual(message.packet_decoded.protected_payload.os_major_version, 42) - assert.deepStrictEqual(message.packet_decoded.protected_payload.os_minor_version, 0) - assert.deepStrictEqual(message.packet_decoded.protected_payload.display_name, 'package.name.here') - }); - - it('should unpack a disconnect packet', function(){ - var data_packet = fs.readFileSync('tests/data/packets/disconnect') - - var poweroff_request = Packer(data_packet) - var message = poweroff_request.unpack(device) - - assert.deepStrictEqual(message.type, 'message') - assert.deepStrictEqual(message.packet_decoded.sequence_number, 57) - assert.deepStrictEqual(message.packet_decoded.source_participant_id, 31) - assert.deepStrictEqual(message.packet_decoded.target_participant_id, 0) - - assert.deepStrictEqual(message.packet_decoded.flags.version, '2') - assert.deepStrictEqual(message.packet_decoded.flags.need_ack, false) - assert.deepStrictEqual(message.packet_decoded.flags.is_fragment, false) - assert.deepStrictEqual(message.packet_decoded.flags.type, 'disconnect') - assert.deepStrictEqual(message.packet_decoded.channel_id, Buffer.from('\x00\x00\x00\x00\x00\x00\x00\x00')) - - assert.deepStrictEqual(message.packet_decoded.protected_payload.reason, 0) - assert.deepStrictEqual(message.packet_decoded.protected_payload.error_code, 0) - }); - - it('should unpack a start_channel_request packet', function(){ - var data_packet = fs.readFileSync('tests/data/packets/start_channel_request') - - var poweroff_request = Packer(data_packet) - var message = poweroff_request.unpack(device) - - assert.deepStrictEqual(message.type, 'message') - assert.deepStrictEqual(message.packet_decoded.sequence_number, 2) - assert.deepStrictEqual(message.packet_decoded.source_participant_id, 31) - assert.deepStrictEqual(message.packet_decoded.target_participant_id, 0) - - assert.deepStrictEqual(message.packet_decoded.flags.version, '2') - assert.deepStrictEqual(message.packet_decoded.flags.need_ack, true) - assert.deepStrictEqual(message.packet_decoded.flags.is_fragment, false) - assert.deepStrictEqual(message.packet_decoded.flags.type, 'start_channel_request') - assert.deepStrictEqual(message.packet_decoded.channel_id, Buffer.from('\x00\x00\x00\x00\x00\x00\x00\x00')) - - assert.deepStrictEqual(message.packet_decoded.protected_payload.channel_request_id, 1) - assert.deepStrictEqual(message.packet_decoded.protected_payload.title_id, 0) - assert.deepStrictEqual(message.packet_decoded.protected_payload.service, Buffer.from('fa20b8ca66fb46e0adb60b978a59d35f', 'hex')) // SystemInput - assert.deepStrictEqual(message.packet_decoded.protected_payload.activity_id, 0) - }); - - it('should unpack a start_channel_response packet', function(){ - var data_packet = fs.readFileSync('tests/data/packets/start_channel_response') - - var poweroff_request = Packer(data_packet) - var message = poweroff_request.unpack(device) - - assert.deepStrictEqual(message.type, 'message') - assert.deepStrictEqual(message.packet_decoded.sequence_number, 6) - assert.deepStrictEqual(message.packet_decoded.source_participant_id, 0) - assert.deepStrictEqual(message.packet_decoded.target_participant_id, 31) - - assert.deepStrictEqual(message.packet_decoded.flags.version, '2') - assert.deepStrictEqual(message.packet_decoded.flags.need_ack, true) - assert.deepStrictEqual(message.packet_decoded.flags.is_fragment, false) - assert.deepStrictEqual(message.packet_decoded.flags.type, 'start_channel_response') - assert.deepStrictEqual(message.packet_decoded.channel_id, Buffer.from('\x00\x00\x00\x00\x00\x00\x00\x00')) - - assert.deepStrictEqual(message.packet_decoded.protected_payload.channel_request_id, 1) - assert.deepStrictEqual(message.packet_decoded.protected_payload.target_channel_id, Buffer.from('0000000000000094', 'hex')) - assert.deepStrictEqual(message.packet_decoded.protected_payload.result, 0) - }); - - it('should unpack a gamepad packet', function(){ - var data_packet = fs.readFileSync('tests/data/packets/gamepad') - - var poweroff_request = Packer(data_packet) - var message = poweroff_request.unpack(device) - - assert.deepStrictEqual(message.type, 'message') - assert.deepStrictEqual(message.packet_decoded.sequence_number, 79) - assert.deepStrictEqual(message.packet_decoded.source_participant_id, 41) - assert.deepStrictEqual(message.packet_decoded.target_participant_id, 0) - - assert.deepStrictEqual(message.packet_decoded.flags.version, '2') - assert.deepStrictEqual(message.packet_decoded.flags.need_ack, false) - assert.deepStrictEqual(message.packet_decoded.flags.is_fragment, false) - assert.deepStrictEqual(message.packet_decoded.flags.type, 'gamepad') - assert.deepStrictEqual(message.packet_decoded.channel_id, Buffer.from('00000000000000b4', 'hex')) - - assert.deepStrictEqual(message.packet_decoded.protected_payload.timestamp, Buffer.from('0000000000000000', 'hex')) - assert.deepStrictEqual(message.packet_decoded.protected_payload.buttons, 32) - assert.deepStrictEqual(message.packet_decoded.protected_payload.left_trigger, 0) - assert.deepStrictEqual(message.packet_decoded.protected_payload.right_trigger, 0) - assert.deepStrictEqual(message.packet_decoded.protected_payload.left_thumbstick_x, 0) - assert.deepStrictEqual(message.packet_decoded.protected_payload.left_thumbstick_y, 0) - assert.deepStrictEqual(message.packet_decoded.protected_payload.right_thumbstick_x, 0) - assert.deepStrictEqual(message.packet_decoded.protected_payload.right_thumbstick_y, 0) - }); - - it('should unpack a media_state packet', function(){ - var data_packet = fs.readFileSync('tests/data/packets/media_state') - - var poweroff_request = Packer(data_packet) - var message = poweroff_request.unpack(device) - - assert.deepStrictEqual(message.type, 'message') - assert.deepStrictEqual(message.packet_decoded.sequence_number, 158) - assert.deepStrictEqual(message.packet_decoded.source_participant_id, 0) - assert.deepStrictEqual(message.packet_decoded.target_participant_id, 32) - - assert.deepStrictEqual(message.packet_decoded.flags.version, '2') - assert.deepStrictEqual(message.packet_decoded.flags.need_ack, true) - assert.deepStrictEqual(message.packet_decoded.flags.is_fragment, false) - assert.deepStrictEqual(message.packet_decoded.flags.type, 'media_state') - assert.deepStrictEqual(message.packet_decoded.channel_id, Buffer.from('0000000000000099', 'hex')) - - assert.deepStrictEqual(message.packet_decoded.protected_payload.title_id, 274278798) - assert.deepStrictEqual(message.packet_decoded.protected_payload.aum_id, 'AIVDE_s9eep9cpjhg6g!App') - assert.deepStrictEqual(message.packet_decoded.protected_payload.asset_id, '') - assert.deepStrictEqual(message.packet_decoded.protected_payload.media_type, 'No Media') - assert.deepStrictEqual(message.packet_decoded.protected_payload.sound_level, 'Full') - assert.deepStrictEqual(message.packet_decoded.protected_payload.enabled_commands, 33758) - assert.deepStrictEqual(message.packet_decoded.protected_payload.playback_status, 'Stopped') - assert.deepStrictEqual(message.packet_decoded.protected_payload.rate, 0) - }); - - it('should unpack a media_command packet', function(){ - var data_packet = fs.readFileSync('tests/data/packets/media_command') - - var poweroff_request = Packer(data_packet) - var message = poweroff_request.unpack(device) - - assert.deepStrictEqual(message.type, 'message') - assert.deepStrictEqual(message.packet_decoded.sequence_number, 597) - assert.deepStrictEqual(message.packet_decoded.source_participant_id, 32) - assert.deepStrictEqual(message.packet_decoded.target_participant_id, 0) - - assert.deepStrictEqual(message.packet_decoded.flags.version, '2') - assert.deepStrictEqual(message.packet_decoded.flags.need_ack, true) - assert.deepStrictEqual(message.packet_decoded.flags.is_fragment, false) - assert.deepStrictEqual(message.packet_decoded.flags.type, 'media_command') - assert.deepStrictEqual(message.packet_decoded.channel_id, Buffer.from('0000000000000099', 'hex')) - - assert.deepStrictEqual(message.packet_decoded.protected_payload.request_id, Buffer.from('0000000000000000', 'hex')) - assert.deepStrictEqual(message.packet_decoded.protected_payload.title_id, 274278798) - assert.deepStrictEqual(message.packet_decoded.protected_payload.command, 256) - assert.deepStrictEqual(message.packet_decoded.protected_payload.seek_position, undefined) // Should be tested when implemented - }); - - it('should unpack a json packet', function(){ - var data_packet = fs.readFileSync('tests/data/packets/json') - - var poweroff_request = Packer(data_packet) - var message = poweroff_request.unpack(device) - - assert.deepStrictEqual(message.type, 'message') - assert.deepStrictEqual(message.packet_decoded.sequence_number, 11) - assert.deepStrictEqual(message.packet_decoded.source_participant_id, 31) - assert.deepStrictEqual(message.packet_decoded.target_participant_id, 0) - - assert.deepStrictEqual(message.packet_decoded.flags.version, '2') - assert.deepStrictEqual(message.packet_decoded.flags.need_ack, true) - assert.deepStrictEqual(message.packet_decoded.flags.is_fragment, false) - assert.deepStrictEqual(message.packet_decoded.flags.type, 'json') - assert.deepStrictEqual(message.packet_decoded.channel_id, Buffer.from('0000000000000097', 'hex')) - - assert.deepStrictEqual(message.packet_decoded.protected_payload.json, '{"msgid":"2ed6c0fd.2","request":"GetConfiguration"}') - }); - - it('should unpack a game_dvr_record packet', function(){ - var data_packet = fs.readFileSync('tests/data/packets/gamedvr_record') - - var game_dvr = Packer(data_packet) - var message = game_dvr.unpack(device) - - assert.deepStrictEqual(message.type, 'message') - assert.deepStrictEqual(message.packet_decoded.sequence_number, 70) - assert.deepStrictEqual(message.packet_decoded.source_participant_id, 1) - assert.deepStrictEqual(message.packet_decoded.target_participant_id, 0) - - assert.deepStrictEqual(message.packet_decoded.protected_payload.start_time_delta, -60) - assert.deepStrictEqual(message.packet_decoded.protected_payload.end_time_delta, 0) - }); - - describe('should repack messages correctly', function(){ - packets.forEach(function(element, packetType){ - for (var name in element) break; - - it('should repack a valid '+name+' packet', function(){ - var data_packet = fs.readFileSync(element[name]) - // console.log('d_packet', data_packet.toString('hex')); - - var response = Packer(data_packet) - var message = response.unpack(device) - // console.log('d_packet message:', message.packet_decoded.decrypted_payload.toString('hex')); - // console.log(message); - - device._request_num = message.packet_decoded.sequence_number - device._target_participant_id = message.packet_decoded.target_participant_id - device._source_participant_id = message.packet_decoded.source_participant_id - - var repacked = message.pack(device) - // console.log('repacked', repacked.toString('hex')); - - assert.deepStrictEqual(data_packet, Buffer.from(repacked)) - }); - }) - }); -}) diff --git a/tests/packet_packer_simple.js b/tests/packet_packer_simple.js deleted file mode 100644 index 340cb03..0000000 --- a/tests/packet_packer_simple.js +++ /dev/null @@ -1,119 +0,0 @@ -var assert = require('assert'); -var fs = require('fs'); -const Packer = require('../src/packet/packer'); -var Xbox = require('../src/xbox'); - -var secret = Buffer.from('82bba514e6d19521114940bd65121af2'+'34c53654a8e67add7710b3725db44f77'+'30ed8e3da7015a09fe0f08e9bef3853c0506327eb77c9951769d923d863a2f5e', 'hex'); -var certificate = Buffer.from('041db1e7943878b28c773228ebdcfb05b985be4a386a55f50066231360785f61b60038caf182d712d86c8a28a0e7e2733a0391b1169ef2905e4e21555b432b262d', 'hex'); - -var simple_packets = [ - {'simple.discovery_request': 'tests/data/packets/discovery_request'}, - {'simple.discovery_response': 'tests/data/packets/discovery_response'}, - {'simple.connect_request': 'tests/data/packets/connect_request'}, - // {'simple.connect_response': 'tests/data/packets/connect_response'}, - {'simple.poweron': 'tests/data/packets/poweron'} -] - -var device = Xbox('127.0.0.1', certificate); -device.loadCrypto(certificate.toString('hex'), secret.toString('hex')); - -describe('packet/packer', function(){ - - it('should unpack a poweron packet', function(){ - var data_packet = fs.readFileSync('tests/data/packets/poweron') - - var poweron_request = Packer(data_packet) - var message = poweron_request.unpack() - - assert.deepStrictEqual(message.type, 'simple') - assert.deepStrictEqual(message.name, 'poweron') - assert.deepStrictEqual(message.packet_decoded.liveid, 'FD00112233FFEE66') - }); - - it('should unpack a discovery_request packet', function(){ - var data_packet = fs.readFileSync('tests/data/packets/discovery_request') - - var discovery_request = Packer(data_packet) - var message = discovery_request.unpack() - - assert.deepStrictEqual(message.type, 'simple') - assert.deepStrictEqual(message.name, 'discovery_request') - assert.deepStrictEqual(message.packet_decoded.flags, 0) - assert.deepStrictEqual(message.packet_decoded.client_type, 8) - assert.deepStrictEqual(message.packet_decoded.min_version, 0) - assert.deepStrictEqual(message.packet_decoded.max_version, 2) - }); - - it('should unpack a discovery_response packet', function(){ - var data_packet = fs.readFileSync('tests/data/packets/discovery_response') - - var discovery_response = Packer(data_packet) - var message = discovery_response.unpack() - - assert.deepStrictEqual(message.type, 'simple') - assert.deepStrictEqual(message.name, 'discovery_response') - assert.deepStrictEqual(message.packet_decoded.flags, 2) - assert.deepStrictEqual(message.packet_decoded.client_type, 1) - assert.deepStrictEqual(message.packet_decoded.name, 'XboxOne') - assert.deepStrictEqual(message.packet_decoded.uuid, 'DE305D54-75B4-431B-ADB2-EB6B9E546014') - assert.deepStrictEqual(message.packet_decoded.last_error, 0) - assert.deepStrictEqual(message.packet_decoded.certificate_length, 519) - assert.deepStrictEqual(message.packet_decoded.certificate.length, 519) - }); - - it('should unpack a connect_request packet', function(){ - var data_packet = fs.readFileSync('tests/data/packets/connect_request') - - var connect_request = Packer(data_packet) - var message = connect_request.unpack(device) - - assert.deepStrictEqual(message.type, 'simple') - assert.deepStrictEqual(message.name, 'connect_request') - assert.deepStrictEqual(message.packet_decoded.payload_length, 98) - assert.deepStrictEqual(message.packet_decoded.protected_payload_length, 47) - assert.deepStrictEqual(message.packet_decoded.uuid, Buffer.from('de305d5475b4431badb2eb6b9e546014', 'hex')) - assert.deepStrictEqual(message.packet_decoded.public_key_type, 0) - //assert.deepStrictEqual(message.packet_decoded.public_key, "\xFF".repeat(64)) - assert.deepStrictEqual(message.packet_decoded.protected_payload.userhash, 'deadbeefdeadbeefde') - assert.deepStrictEqual(message.packet_decoded.protected_payload.jwt, 'dummy_token') - assert.deepStrictEqual(message.packet_decoded.protected_payload.connect_request_num, 0) - assert.deepStrictEqual(message.packet_decoded.protected_payload.connect_request_group_start, 0) - assert.deepStrictEqual(message.packet_decoded.protected_payload.connect_request_group_end, 2) - }); - - it('should unpack a connect_response packet', function(){ - var data_packet = fs.readFileSync('tests/data/packets/connect_response') - - var connect_response = Packer(data_packet) - var message = connect_response.unpack(device) - - assert.deepStrictEqual(message.type, 'simple') - assert.deepStrictEqual(message.name, 'connect_response') - assert.deepStrictEqual(message.packet_decoded.payload_length, 16) - assert.deepStrictEqual(message.packet_decoded.protected_payload_length, 8) - assert.deepStrictEqual(message.packet_decoded.iv, Buffer.from('c6373202bdfd1167cf9693491d22322a', 'hex')) - assert.deepStrictEqual(message.packet_decoded.protected_payload.connect_result, 0) - assert.deepStrictEqual(message.packet_decoded.protected_payload.pairing_state, 0) - assert.deepStrictEqual(message.packet_decoded.protected_payload.participant_id, 31) - }); - - describe('should repack messages correctly', function(){ - simple_packets.forEach(function(element, index){ - for (var name in element) break; - - it('should repack a valid '+name+' packet', function(){ - var data_packet = fs.readFileSync(element[name]) - // console.log('d_packet', data_packet.toString('hex')); - - var response = Packer(data_packet) - var message = response.unpack(device) - //console.log('d_packet message:', message.packet_decoded.decrypted_payload.toString('hex')); - - var repacked = message.pack(device) - // console.log('repacked', repacked.toString('hex')); - - assert.deepStrictEqual(data_packet, Buffer.from(repacked)) - }); - }) - }); -}) diff --git a/tests/packet_structure.js b/tests/packet_structure.js deleted file mode 100644 index c86c172..0000000 --- a/tests/packet_structure.js +++ /dev/null @@ -1,105 +0,0 @@ -var assert = require('assert'); -var PacketStructure = require('../src/packet/structure'); - -describe('packet/structure', function(){ - it('obj._packet should be empty on new instance without parameters', function(){ - var packet = PacketStructure(); - assert.deepStrictEqual(packet.toBuffer(), Buffer.from('')); - }); - it('obj._packet should be not empty on new instance with parameters', function(){ - var packet = PacketStructure(Buffer.from('0x0001')); - assert.deepStrictEqual(packet.toBuffer(), Buffer.from('0x0001')); - }); - - - describe('test write and read types', function(){ - - it('should write UInt8 to the packet and check packet', function(){ - var lPacket = PacketStructure(); - lPacket.writeUInt8(10); - - assert.deepStrictEqual(lPacket.toBuffer(), Buffer.from('\x0a')); - }); - - it('should write UInt16 to the packet and check packet', function(){ - var lPacket = PacketStructure(); - lPacket.writeUInt16(10); - - assert.deepStrictEqual(lPacket.toBuffer(), Buffer.from('\x00\x0a')); - }); - - it('should write UInt32 to the packet and check packet', function(){ - var lPacket = PacketStructure(); - lPacket.writeUInt32(10); - - assert.deepStrictEqual(lPacket.toBuffer(), Buffer.from('\x00\x00\x00\x0a')); - }); - - it('should write SGString to the packet and check packet', function(){ - var lPacket = PacketStructure(); - lPacket.writeSGString('test'); - - assert.deepStrictEqual(lPacket.toBuffer(), Buffer.from('\x00\x04\x74\x65\x73\x74\x00')); - }); - - - - it('should read UInt8 from the packet and check value and offset', function(){ - var lPacket = PacketStructure(); - lPacket.writeUInt8(10); - - var uint16 = lPacket.readUInt8(10); - assert.deepStrictEqual(lPacket.toBuffer(), Buffer.from('\x0a')); - assert.deepStrictEqual(lPacket.getOffset(), 1); - }); - - it('should read UInt16 from the packet and check value and offset', function(){ - var lPacket = PacketStructure(); - lPacket.writeUInt16(10); - - var uint16 = lPacket.readUInt16(10); - assert.deepStrictEqual(lPacket.toBuffer(), Buffer.from('\x00\x0a')); - assert.deepStrictEqual(lPacket.getOffset(), 2); - }); - - it('should read UInt32 from the packet and check value and offset', function(){ - var lPacket = PacketStructure(); - lPacket.writeUInt32(10); - - var uint32 = lPacket.readUInt32(10); - assert.deepStrictEqual(lPacket.toBuffer(), Buffer.from('\x00\x00\x00\x0a')); - assert.deepStrictEqual(lPacket.getOffset(), 4); - }); - - it('should read UInt64 from the packet and check value and offset', function(){ - var lPacket = PacketStructure(Buffer.from('\x67\xa1\x60\x60\x01\x00\x00\x00')); - - var uint64 = lPacket.readUInt64(); // 5911912807 - assert.deepStrictEqual(lPacket.toBuffer(), Buffer.from('\x67\xa1\x60\x60\x01\x00\x00\x00')); - // assert.deepStrictEqual(uint64, 5911912807); - assert.deepStrictEqual(lPacket.getOffset(), 8); - }); - - it('should read SGString from the packet and check value and offset', function(){ - var lPacket = PacketStructure(); - lPacket.writeSGString('test'); - - var sgstring = lPacket.readSGString('test'); - assert.deepStrictEqual(lPacket.toBuffer(), Buffer.from('\x00\x04\x74\x65\x73\x74\x00')); - assert.deepStrictEqual(sgstring, Buffer.from('test')); - assert.deepStrictEqual(lPacket.getOffset(), 7); - }); - - - it('should set offset', function(){ - var lPacket = PacketStructure(); - lPacket.writeSGString('testtesttesttest'); - - assert.deepStrictEqual(lPacket.getOffset(), 0); - lPacket.setOffset(7) - assert.deepStrictEqual(lPacket.getOffset(), 7); - lPacket.setOffset(0) - assert.deepStrictEqual(lPacket.getOffset(), 0); - }); - }); -}) diff --git a/tests/sgcrypto.js b/tests/sgcrypto.js deleted file mode 100644 index 645d18e..0000000 --- a/tests/sgcrypto.js +++ /dev/null @@ -1,98 +0,0 @@ -var assert = require('assert'); -var sgCrypto = require('../src/sgcrypto'); - -describe('sgcrypto', function(){ - it('should not generate random iv without a secret', function(){ - var clientCrypto = sgCrypto(); - var rand_iv = clientCrypto.getIv(); - - assert.deepStrictEqual(rand_iv.length, 0); - }); - - it('should return an encryption key', function(){ - var clientCrypto = sgCrypto(); - clientCrypto.load('pubkey', '0123456789012345678901234567890123456789012345678901234567890123'); - var encryptionKey = clientCrypto.getEncryptionKey(); - - assert.deepStrictEqual(encryptionKey, Buffer.from('0123456789012345')); - }); - - it('should return a secret', function(){ - var clientCrypto = sgCrypto(); - clientCrypto.load('pubkey', '0123456789012345678901234567890123456789012345678901234567890123'); - var secret = clientCrypto.getSecret(); - - assert.deepStrictEqual(secret, Buffer.from('0123456789012345678901234567890123456789012345678901234567890123')); - }); - - it('should return a hmac', function(){ - var clientCrypto = sgCrypto(); - clientCrypto.load('pubkey', '0123456789012345678901234567890123456789012345678901234567890123'); - var hmac = clientCrypto.getHmac(); - - assert.deepStrictEqual(hmac, Buffer.from('23456789012345678901234567890123')); - }); - - it('should generate a static iv with a secret', function(){ - var clientCrypto = sgCrypto(); - clientCrypto.load('pubkey', '0123456789012345678901234567890123456789012345678901234567890123'); - - var static_iv = clientCrypto.getIv(); - - assert.deepStrictEqual(static_iv.length, 16); - assert.deepStrictEqual(static_iv, Buffer.from('6789012345678901')); - }); - - it('should generate a static iv with a secret using a seed', function(){ - var clientCrypto = sgCrypto(); - clientCrypto.load('pubkey', '0123456789012345678901234567890123456789012345678901234567890123'); - - var static_iv = clientCrypto.getIv(123456); - - assert.deepStrictEqual(static_iv.length, 16); - assert.deepStrictEqual(static_iv, Buffer.from('6789012345678901')); - }); - - it('should encrypt a string', function(){ - var clientCrypto = sgCrypto(); - clientCrypto.load('pubkey', '0123456789012345678901234567890123456789012345678901234567890123'); - - var key = clientCrypto.getIv(); - var encoded_string = clientCrypto._encrypt(Buffer.from('Test String\x00\x00\x00\x00\x00'), key); - assert.deepStrictEqual(key, Buffer.from('6789012345678901')); - assert.deepStrictEqual(encoded_string, Buffer.from('0a558e2b483d9c4ccc24296c9ac8a85d', 'hex')); - }); - - it('should decode an encrypted string', function(){ - var clientCrypto = sgCrypto(); - clientCrypto.load('pubkey', '0123456789012345678901234567890123456789012345678901234567890123'); - - var key = clientCrypto.getIv(); - var encoded_string = clientCrypto._encrypt(Buffer.from('Test String\x00\x00\x00\x00\x00'), key); - var decoded_string = clientCrypto._decrypt(encoded_string, false, key); - assert.deepStrictEqual(decoded_string, Buffer.from('Test String\x00\x00\x00\x00\x00')); - }); - - it('should encrypt a string with iv', function(){ - var clientCrypto = sgCrypto(); - clientCrypto.load('pubkey', '0123456789012345678901234567890123456789012345678901234567890123'); - - var iv = clientCrypto.getIv(); - var key = Buffer.from('000102030405060708090A0B0C0D0E0F', 'hex'); - - var encoded_string = clientCrypto._encrypt(Buffer.from('Test String\x00\x00\x00\x00\x00'), key, iv); - assert.deepStrictEqual(iv, Buffer.from('6789012345678901')); - assert.deepStrictEqual(encoded_string, Buffer.from('ac641fbc44858dbb6869dfeca062f05c', 'hex')); - }); - - it('should decode an encrypted string with iv', function(){ - var clientCrypto = sgCrypto(); - clientCrypto.load('pubkey', '0123456789012345678901234567890123456789012345678901234567890123'); - - var key = clientCrypto.getIv(); - var iv = Buffer.from('000102030405060708090A0B0C0D0E0F', 'hex'); - var encoded_string = clientCrypto._encrypt(Buffer.from('Test String\x00\x00\x00\x00\x00'), key, iv); - var decoded_string = clientCrypto._decrypt(encoded_string, iv, key); - assert.deepStrictEqual(decoded_string, Buffer.from('Test String\x00\x00\x00\x00\x00')); - }); -}); diff --git a/tests/xbox.js b/tests/xbox.js deleted file mode 100644 index 12e3faf..0000000 --- a/tests/xbox.js +++ /dev/null @@ -1,64 +0,0 @@ -var assert = require('assert'); -var fs = require('fs'); -const Xbox = require('../src/xbox'); - -// var public_key = Buffer.from('041db1e7943878b28c773228ebdcfb05b985be4a386a55f50066231360785f61b60038caf182d712d86c8a28a0e7e2733a0391b1169ef2905e4e21555b432b262d', 'hex'); -var certificate = fs.readFileSync('tests/data/selfsigned_cert.crt') - -describe('xbox', function(){ - it('should create an Xbox object using a public key', function(){ - var xbox = Xbox('127.0.0.1', certificate) - - assert.deepStrictEqual(xbox.getIp(), '127.0.0.1'); - assert.deepStrictEqual(xbox.getCertificate(), certificate); - assert.deepStrictEqual(xbox.getLiveid(), false); - }); - - it('should create a new sgCrypto object using connect()', function(){ - certificate_b64 = certificate.toString().replace(/(\n|\r)+$/, '') - - var xbox = Xbox('127.0.0.1', Buffer.from(certificate_b64, 'base64')) - var connect_request = xbox.connect() - - assert.deepStrictEqual(Buffer.from(connect_request).slice(0, 2), Buffer.from('cc00', 'hex')) - assert.deepStrictEqual(xbox.getLiveid(), 'FFFFFFFFFFF'); - assert.deepStrictEqual(xbox._request_num, 1); - assert.notDeepStrictEqual(xbox._crypto, false); - }); - - it('should create a new sgCrypto object using connect() using credentials', function(){ - certificate_b64 = certificate.toString().replace(/(\n|\r)+$/, '') - - var xbox = Xbox('127.0.0.1', Buffer.from(certificate_b64, 'base64')) - var connect_request = xbox.connect('userhash', 'xsts_token') - - assert.deepStrictEqual(Buffer.from(connect_request).slice(0, 2), Buffer.from('cc00', 'hex')) - assert.deepStrictEqual(xbox.getLiveid(), 'FFFFFFFFFFF'); - assert.deepStrictEqual(xbox._request_num, 1); - assert.notDeepStrictEqual(xbox._crypto, false); - }); - - it('should create an Xbox object and return a value when calling get_requestnum()', function(){ - var xbox = Xbox('127.0.0.1', certificate) - var request_num = xbox.get_requestnum() - - assert.deepStrictEqual(xbox.getIp(), '127.0.0.1'); - assert.deepStrictEqual(xbox.getCertificate(), certificate); - assert.deepStrictEqual(xbox.getLiveid(), false); - - assert.deepStrictEqual(request_num, 1); - - }); - - it('should create an Xbox object and set participant id when using set_participantid()', function(){ - var xbox = Xbox('127.0.0.1', certificate) - xbox.set_participantid(1001) - - assert.deepStrictEqual(xbox.getIp(), '127.0.0.1'); - assert.deepStrictEqual(xbox.getCertificate(), certificate); - assert.deepStrictEqual(xbox.getLiveid(), false); - - assert.deepStrictEqual(xbox._participantid, 1001); - assert.deepStrictEqual(xbox._source_participant_id, 1001); - }); -})