From 071b5316795e9d0bea62abd1ed1e69b08d99af28 Mon Sep 17 00:00:00 2001 From: modeco80 Date: Tue, 2 Apr 2024 07:43:54 -0400 Subject: [PATCH] initial commit --- .editorconfig | 8 + .gitignore | 7 + .prettierignore | 3 + .prettierrc.json | 20 + LICENSE | 20 + README.md | 29 + backend/package.json | 24 + backend/src/ExtendableTimer.ts | 46 + backend/src/SlotQemuDefs.ts | 70 + backend/src/SocketComputerServer.ts | 395 ++++ backend/src/index.ts | 8 + backend/tsconfig.json | 11 + package.json | 22 + qemu/package.json | 19 + qemu/src/QemuDisplay.ts | 143 ++ qemu/src/QemuUtil.ts | 33 + qemu/src/QemuVM.ts | 286 +++ qemu/src/QmpClient.ts | 135 ++ qemu/src/index.ts | 3 + qemu/src/rfb/LICENSE | 21 + qemu/src/rfb/README.md | 10 + qemu/src/rfb/client.ts | 885 +++++++++ qemu/src/rfb/constants.ts | 46 + qemu/src/rfb/decoders/copyrect.ts | 29 + qemu/src/rfb/decoders/hextile.ts | 241 +++ qemu/src/rfb/decoders/raw.ts | 54 + qemu/src/rfb/decoders/zrle.ts | 357 ++++ qemu/src/rfb/socketbuffer.ts | 132 ++ qemu/tsconfig.json | 10 + shared/package.json | 18 + shared/src/Protocol.ts | 422 ++++ shared/src/Struct.ts | 162 ++ shared/src/UsernameValidator.ts | 9 + shared/src/index.ts | 2 + shared/test/Protocol.test.ts | 77 + shared/tsconfig.json | 8 + tsconfig-base.json | 13 + tsconfig.json | 27 + webapp/package.json | 20 + webapp/src/css/main.css | 107 ++ webapp/src/index.html | 34 + webapp/src/index.ts | 124 ++ webapp/static/macbook.png | Bin 0 -> 124659 bytes webapp/tsconfig.json | 19 + yarn.lock | 2765 +++++++++++++++++++++++++++ 45 files changed, 6874 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .prettierignore create mode 100644 .prettierrc.json create mode 100644 LICENSE create mode 100644 README.md create mode 100644 backend/package.json create mode 100644 backend/src/ExtendableTimer.ts create mode 100644 backend/src/SlotQemuDefs.ts create mode 100644 backend/src/SocketComputerServer.ts create mode 100644 backend/src/index.ts create mode 100644 backend/tsconfig.json create mode 100644 package.json create mode 100644 qemu/package.json create mode 100644 qemu/src/QemuDisplay.ts create mode 100644 qemu/src/QemuUtil.ts create mode 100644 qemu/src/QemuVM.ts create mode 100644 qemu/src/QmpClient.ts create mode 100644 qemu/src/index.ts create mode 100644 qemu/src/rfb/LICENSE create mode 100644 qemu/src/rfb/README.md create mode 100644 qemu/src/rfb/client.ts create mode 100644 qemu/src/rfb/constants.ts create mode 100644 qemu/src/rfb/decoders/copyrect.ts create mode 100644 qemu/src/rfb/decoders/hextile.ts create mode 100644 qemu/src/rfb/decoders/raw.ts create mode 100644 qemu/src/rfb/decoders/zrle.ts create mode 100644 qemu/src/rfb/socketbuffer.ts create mode 100644 qemu/tsconfig.json create mode 100644 shared/package.json create mode 100644 shared/src/Protocol.ts create mode 100644 shared/src/Struct.ts create mode 100644 shared/src/UsernameValidator.ts create mode 100644 shared/src/index.ts create mode 100644 shared/test/Protocol.test.ts create mode 100644 shared/tsconfig.json create mode 100644 tsconfig-base.json create mode 100644 tsconfig.json create mode 100644 webapp/package.json create mode 100644 webapp/src/css/main.css create mode 100644 webapp/src/index.html create mode 100644 webapp/src/index.ts create mode 100644 webapp/static/macbook.png create mode 100644 webapp/tsconfig.json create mode 100644 yarn.lock diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..722b702 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +indent_style = tab +# if this is changed please change it in the .clang-format so that nothing explodes +indent_size = 4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..407e7b0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +**/dist +/package-lock.json +# why don't you put this in the webapp/ project root? +/.parcel-cache +# nvm it does now ok +/webapp/.parcel-cache diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..f0d4067 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +dist +*.md +*.json diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..f28fe87 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,20 @@ +{ + "arrowParens": "always", + "bracketSameLine": false, + "bracketSpacing": true, + "embeddedLanguageFormatting": "auto", + "htmlWhitespaceSensitivity": "css", + "insertPragma": false, + "jsxSingleQuote": true, + "printWidth": 200, + "proseWrap": "preserve", + "quoteProps": "consistent", + "requirePragma": false, + "semi": true, + "singleAttributePerLine": false, + "singleQuote": true, + "tabWidth": 4, + "trailingComma": "none", + "useTabs": true, + "vueIndentScriptAndStyle": false + } diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1a99ce8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright 2023 Lily Tsuru/modeco80 +Please see qemu/src/rfb/LICENSE for additional terms. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..aad4ffe --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# Socket.Computer + +socket.computer, except not powered by socket.io anymore, and with many less bugs. This monorepo builds + +- The backend +- A QEMU VM runner package (feel free to steal it) +- Shared components +- The CrustTest webapp (TODO) + + + +## Building + +```bash +$ yarn +$ yarn build:service # Build the service +$ yarn build:frontend # Build the webapp +``` + +## Hosting + +TODO + +- Edit `webapp/src/index.ts` to point the websocket URL to an appopiate place +- Build the service and the webapp (tip, see the above section) +- copy `webapp/dist` (excl. `.map` files) to an applicable webroot +- Run the backend, optionally with systemd service things (MAKE SURE TO SET NODE_ENV TO PRODUCTION.) (also proxy it for wss please) + +... profit? diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..2e6c17d --- /dev/null +++ b/backend/package.json @@ -0,0 +1,24 @@ +{ + "name": "@socketcomputer/backend", + "version": "1.0.0", + "private": "true", + "description": "socket 2.0 backend", + "type": "module", + "scripts": { + "build": "tsc" + }, + "author": "modeco80", + "license": "MIT", + "dependencies": { + "@fastify/websocket": "^10.0.1", + "@julusian/jpeg-turbo": "^2.1.0", + "@socketcomputer/qemu": "*", + "@socketcomputer/shared": "*", + "fastify": "^4.26.2", + "mnemonist": "^0.39.8", + "canvas": "^2.11.2" + }, + "devDependencies": { + "@types/ws": "^8.5.10" + } +} diff --git a/backend/src/ExtendableTimer.ts b/backend/src/ExtendableTimer.ts new file mode 100644 index 0000000..77453c8 --- /dev/null +++ b/backend/src/ExtendableTimer.ts @@ -0,0 +1,46 @@ +import { EventEmitter } from 'node:events'; + +// TODO: Second granualarity. (nvm this is fine for socket2..) + +const kMinute = 60 * 1000; + +export class ExtendableTimer extends EventEmitter { + private timeout: NodeJS.Timeout; + private iterationcount: number = 0; + private time: number; + + constructor(baseTimeMin: number) { + super(); + this.time = baseTimeMin; + } + + private Arm() { + this.timeout = setTimeout(() => { + this.iterationcount--; + if (this.iterationcount == 1) { + this.emit('expiry-near'); + } else if (this.iterationcount == 0) { + return this.emit('expired'); + } + this.Arm(); + }, kMinute); + } + + Start() { + this.iterationcount = this.time; + clearTimeout(this.timeout); + this.Arm(); + } + + Stop() { + this.iterationcount = 0; + clearTimeout(this.timeout); + this.emit('expired'); + } + + Extend() { + this.iterationcount = this.time; + clearTimeout(this.timeout); + this.Arm(); + } +} diff --git a/backend/src/SlotQemuDefs.ts b/backend/src/SlotQemuDefs.ts new file mode 100644 index 0000000..4b5914d --- /dev/null +++ b/backend/src/SlotQemuDefs.ts @@ -0,0 +1,70 @@ +// QEMU definitions. These define the base QEMU command lines, +// which are standardized across all crusttest slots. +// (This file has been bastardized for socket2) + +import { QemuVmDefinition } from '@socketcomputer/qemu'; + +const kQemuPath = '/srv/collabvm/qemu/bin/qemu-system-x86_64'; + +// the aio model qemu will use. if possible, leave this at 'io_uring'. +const kQemuAio = 'io_uring'; + +/// Creates a base definition for a VM with PC chipset. +export function Slot_PCDef( + // RAM + ramSize: string, + + // Network + netdevOption: string = '-netdev user,id=vm.wan', + netAdapterModel: string = 'rtl8139', + netMac: string = 'c7:4e:c0:5f:2c:7c', + + // HDA + hdaIsSsd: boolean = true, + hdImagePath: string, + hdImageFormat: string +): QemuVmDefinition { + let qCommand = [kQemuPath]; + + let pushOption = (opt: string) => { + qCommand.push(opt.substring(0, opt.indexOf(' '))); + qCommand.push(opt.substring(opt.indexOf(' ') + 1)); + }; + + // boilerplate/chipset + qCommand.push('-nodefaults'); + pushOption('-machine pc-i440fx-2.10,accel=kvm,kernel_irqchip=on,hpet=off,acpi=on,usb=on'); + pushOption('-rtc base=localtime,clock=vm'); + + // CPU/RAM + pushOption(`-cpu pentium3`); + pushOption(`-m ${ramSize}`); // unlike LVM we don't really want to prealloc per se.. + + pushOption(`${netdevOption}`); + pushOption(`-device ${netAdapterModel},id=vm.netadp,netdev=vm.wan,mac=${netMac}`); + + pushOption( + `-drive if=none,file=${hdImagePath},cache=writeback,discard=unmap,format=${hdImageFormat},aio=${kQemuAio},id=vm.hda_drive,bps=65000000,bps_max=65000000,iops=1500,iops_max=2000` + ); + + // prettier-ignore + if (hdaIsSsd) + pushOption(`-device ide-hd,id=vm.hda,rotation_rate=1,drive=vm.hda_drive`); + else + pushOption(`-device ide-hd,id=vm.hda,drive=vm.hda_drive`); + + // CD drive + pushOption(`-drive if=none,media=cdrom,aio=${kQemuAio},id=vm.cd`); + pushOption(`-device ide-cd,drive=vm.cd,id=vm.cd_drive`); + + pushOption('-audio driver=none,model=ac97'); + + // VGA + usb tablet + pushOption('-device VGA,id=vm.vga'); + pushOption('-device usb-tablet'); + + return { + id: "socketvm1", + command: qCommand + }; +} diff --git a/backend/src/SocketComputerServer.ts b/backend/src/SocketComputerServer.ts new file mode 100644 index 0000000..9a737cf --- /dev/null +++ b/backend/src/SocketComputerServer.ts @@ -0,0 +1,395 @@ +import { QemuVmDefinition, QemuDisplay, QemuVM, VMState, setSnapshot, GenMacAddress } from '@socketcomputer/qemu'; +import { Slot_PCDef } from './SlotQemuDefs.js'; +import { ExtendableTimer } from './ExtendableTimer.js'; +import { EventEmitter } from 'node:events'; +import * as Shared from '@socketcomputer/shared'; + +import { Canvas } from 'canvas'; + +import { FastifyInstance, fastify } from 'fastify'; +import * as fastifyWebsocket from '@fastify/websocket'; + +import { WebSocket } from 'ws'; + +import Queue from 'mnemonist/queue.js'; + +class VMUser { + public connection: WebSocket; + public username: string; + private vm: VirtualMachine; + + constructor(connection: WebSocket, slot: VirtualMachine) { + this.connection = connection; + this.vm = slot; + + this.vm.AddUser(this); + + this.connection.on('message', async (data, isBinary) => { + if (!isBinary) this.connection.close(1000); + + await this.vm.OnWSMessage(this, data as Buffer); + }); + + this.connection.on('close', async () => { + console.log('closed'); + await this.vm.RemUser(this); + }); + } + + async SendMessage(messageGenerator: (encoder: Shared.MessageEncoder) => ArrayBuffer) { + await this.SendBuffer(messageGenerator(new Shared.MessageEncoder())); + } + + async SendBuffer(buffer: ArrayBuffer): Promise { + return new Promise((res, rej) => { + if (this.connection.readyState !== WebSocket.CLOSED) { + this.connection.send(buffer, (err) => { + res(); + }); + } + }); + } + + static GenerateName() { + return `guest${Math.floor(Math.random() * (99999 - 10000) + 10000)}`; + } +} + +const kTurnTimeSeconds = 18; + +type userAndTime = { + user: VMUser; + // waiting time if this user is not the front. + time: number; +}; + +// the turn queue. yes this is mostly stolen from cvmts but I make it cleaner by Seperate!!!!!!! +class TurnQueue extends EventEmitter { + private queue: Queue = new Queue(); + private turnTime = kTurnTimeSeconds; + private interval: NodeJS.Timeout = null; + + constructor() { + super(); + } + + public CurrentUser(): VMUser { + return this.queue.peek(); + } + + public TryEnqueue(user: VMUser) { + // Already the current user + if (this.CurrentUser() == user) return; + + // Already in the queue + if (this.queue.toArray().indexOf(user) !== -1) return; + + this.queue.enqueue(user); + if (this.queue.size == 1) this.nextTurn(); + } + + private turnInterval() { + this.turnTime--; + if (this.turnTime < 1) { + this.queue.dequeue(); + this.nextTurn(); + } + } + + private nextTurn() { + clearInterval(this.interval); + if (this.queue.size === 0) { + } else { + this.turnTime = kTurnTimeSeconds; + this.interval = setInterval(() => this.turnInterval(), 1000); + } + + if (this.queue.size == 1) this.emit('turnQueue', [{ user: this.CurrentUser(), time: kTurnTimeSeconds * 1000 }]); + + // removes the front of the quuee + let arr = this.queue.toArray().slice(1); + + let arr2: Array = arr.map((u, index) => { + return { + user: u, + time: this.turnTime * 1000 + (index - 1) * (kTurnTimeSeconds * 1000) + }; + }, this); + + this.emit('turnQueue', arr2); + } +} + +// A slot. +class VirtualMachine extends EventEmitter { + private vm: QemuVM; + private display: QemuDisplay; + + private timer: ExtendableTimer = null; + private users: Array = []; + private queue: TurnQueue = new TurnQueue(); + + constructor(vm: QemuVM) { + super(); + + this.vm = vm; + this.timer = new ExtendableTimer(2); + + this.timer.on('expired', async () => { + // bye bye! + console.log(`[VM] VM expired, resetting..`); + await this.vm.Stop(); + }); + + this.timer.on('expiry-near', async () => { + console.log(`[VM] about to expire!`); + }); + + + this.vm.on('statechange', async (state: VMState) => { + if (state == VMState.Started) { + this.display = this.vm.GetDisplay(); + await this.VMRunning(); + } else if (state == VMState.Stopped) { + this.display = null; + await this.VMStopped(); + } + }); + + this.queue.on('turnQueue', (arr: Array) => { + // TODO! SERIALIZE TURN QUEUE! + console.log("Turn queue", arr); + + for (let entry of arr) { + entry.user.SendMessage((encoder: Shared.MessageEncoder) => { + // painnnnnnnnnnnnnnnnnnn fuck i should just make a dynamic buffer system lol + encoder.Init(4 + arr.length * Shared.kMaxUserNameLength); + + // pain ? + encoder.SetTurnSrvMessage( + entry.time, + arr.map((entry: userAndTime) => { + return entry.user.username; + }) + ); + + return encoder.Finish(); + }); + } + }); + } + + async Start() { + await this.vm.Start(); + } + + async AddUser(user: VMUser) { + user.username = VMUser.GenerateName(); + + console.log(user.username, 'joined.'); + + // send bullshit + + await this.sendFullScreen(user); + + // send an adduser for all users + for (let user of this.users) { + user.SendMessage((encoder: Shared.MessageEncoder) => { + encoder.Init(4 + Shared.kMaxUserNameLength); + encoder.SetAddUserMessage(user.username); + return encoder.Finish(); + }); + } + + // officially add the user + this.users.push(user); + + // hello! + await this.BroadcastMessage((encoder: Shared.MessageEncoder) => { + encoder.Init(4 + Shared.kMaxUserNameLength); + encoder.SetAddUserMessage(user.username); + return encoder.Finish(); + }); + } + + async RemUser(user: VMUser) { + // TODO: erase from turn queue (once we have it) wired up + + this.users.splice(this.users.indexOf(user), 1); + + // bye-bye! + await this.BroadcastMessage((encoder: Shared.MessageEncoder) => { + encoder.Init(4 + Shared.kMaxUserNameLength); + encoder.SetRemUserMessage(user.username); + return encoder.Finish(); + }); + } + + async OnWSMessage(user: VMUser, message: Buffer) { + try { + this.OnDecodedMessage(user, await Shared.MessageDecoder.ReadMessage(message, false)); + } catch (err) { + // get out + user.connection.close(); + return; + } + } + + private async OnDecodedMessage(user: VMUser, message: Shared.DeserializedMessage) { + switch (message.type) { + case Shared.MessageType.Chat: + console.log(`${user.username} > ${(message as Shared.ChatMessage).message}`); + break; + + case Shared.MessageType.Turn: + this.queue.TryEnqueue(user); + break; + + case Shared.MessageType.Mouse: + if(user != this.queue.CurrentUser()) + return; + if(this.display == null) return; + this.display.MouseEvent((message as Shared.MouseMessage).x, (message as Shared.MouseMessage).y, (message as Shared.MouseMessage).buttons); + break; + + case Shared.MessageType.Key: + if(user != this.queue.CurrentUser()) + return; + + if(this.display == null) return; + break; + + // ignore unhandlable messages (we won't get any invalid ones because they will cause a throw) + default: + break; + } + } + + private async BroadcastMessage(messageGenerator: (encoder: Shared.MessageEncoder) => ArrayBuffer) { + let buffer = messageGenerator(new Shared.MessageEncoder()); + for (let user of this.users) { + await user.SendBuffer(buffer); + } + } + + private async InsertCD(isoPath: string) { + await this.vm.ChangeRemovableMedia('vm.cd', isoPath); + } + + private async VMRunning() { + + let self = this; + + // Hook up the display + this.display.on('resize', async (width, height) => { + if(self.display == null) + return; + + await self.BroadcastMessage((encoder: Shared.MessageEncoder) => { + encoder.Init(4); + encoder.SetDisplaySizeMessage(width, height); + return encoder.Finish(); + }); + + // sexy cream! + + + let canvas = self.display.GetCanvas(); + + if(canvas == null) + return; + + let buffer = canvas.toBuffer('image/jpeg', { quality: 0.75 }); + + await this.BroadcastMessage((encoder: Shared.MessageEncoder) => { + encoder.Init(buffer.length + 256); + encoder.SetDisplayRectMessage(0, 0, buffer); + return encoder.Finish(); + }); + }); + + this.display.on('rect', async (x, y, rect: ImageData) => { + let canvas = new Canvas(rect.width, rect.height); + canvas.getContext('2d').putImageData(rect, 0, 0); + + let buffer = canvas.toBuffer('image/jpeg', { quality: 0.75 }); + + await this.BroadcastMessage((encoder: Shared.MessageEncoder) => { + encoder.Init(buffer.length + 256); + encoder.SetDisplayRectMessage(x, y, buffer); + return encoder.Finish(); + }); + }); + + this.timer.Start(); + } + + private async sendFullScreen(user: VMUser) { + if (this.display == null) return; + + let buffer = this.display.GetCanvas().toBuffer('image/jpeg', { quality: 0.75 }); + + await user.SendMessage((encoder: Shared.MessageEncoder) => { + encoder.Init(8); + encoder.SetDisplaySizeMessage(this.display.Size().width, this.display.Size().height); + return encoder.Finish(); + }); + + await user.SendMessage((encoder: Shared.MessageEncoder) => { + encoder.Init(buffer.length + 256); + encoder.SetDisplayRectMessage(0, 0, buffer); + return encoder.Finish(); + }); + } + + private async VMStopped() { + await this.vm.Start(); + } +} + +export class SocketComputerServer { + private vm: VirtualMachine = null; + private fastify: FastifyInstance = fastify({ + exposeHeadRoutes: false + }); + + Init() { + this.fastify.register(fastifyWebsocket.default); + this.fastify.register(async (app, _) => this.CTRoutes(app), {}); + } + + async Listen() { + try { + console.log('Backend starting...'); + + // create teh VM!!!! + await this.CreateVM(); + + await this.fastify.listen({ + host: '127.0.0.1', + port: 4050 + }); + } catch (err) { + return; + } + } + + async CreateVM() { + let diskpath = '/srv/collabvm/vms/socket1/socket1.qcow2'; + let slotDef: QemuVmDefinition = Slot_PCDef('2G', '-netdev user,id=vm.wan', 'rtl8139', await GenMacAddress(), true, diskpath, 'qcow2'); + + setSnapshot(true); + + // create the slot for real! + this.vm = new VirtualMachine(new QemuVM(slotDef)); + await this.vm.Start(); // boot it up + } + + CTRoutes(app: FastifyInstance) { + let self = this; + + app.get('/', { websocket: true }, (connection: fastifyWebsocket.WebSocket) => { + new VMUser(connection, self.vm); + }); + } +} diff --git a/backend/src/index.ts b/backend/src/index.ts new file mode 100644 index 0000000..7ddee94 --- /dev/null +++ b/backend/src/index.ts @@ -0,0 +1,8 @@ +import { SocketComputerServer } from './SocketComputerServer.js'; + +(async () => { + let backend = new SocketComputerServer(); + backend.Init(); + + await backend.Listen(); +})(); diff --git a/backend/tsconfig.json b/backend/tsconfig.json new file mode 100644 index 0000000..41f06ef --- /dev/null +++ b/backend/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../tsconfig-base.json", + "compilerOptions": { + "composite": true, + "outDir": "./dist", + "rootDir": "." + }, + "references": [ + { "path": "../shared" } + ] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0881384 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "socketcomputer-repo", + "private": "true", + "workspaces": [ + "shared", + "backend", + "qemu", + "webapp" + ], + "scripts": { + "build:frontend": "npm -w shared run build && npm -w webapp run build", + "build:service": "npm -w shared run build && npm -w qemu run build && npm -w backend run build" + }, + "dependencies": { + "canvas": "^2.11.2" + }, + "devDependencies": { + "@types/node": "^20.12.2", + "prettier": "^3.2.5", + "typescript": "^5.4.3" + } +} diff --git a/qemu/package.json b/qemu/package.json new file mode 100644 index 0000000..1703cec --- /dev/null +++ b/qemu/package.json @@ -0,0 +1,19 @@ +{ + "name": "@socketcomputer/qemu", + "version": "1.0.0", + "private": "true", + "description": "QEMU runtime for socketcomputer backend", + "main": "dist/src/index.js", + "type": "module", + "scripts": { + "build": "tsc" + }, + "author": "", + "license": "MIT", + "dependencies": { + + "canvas": "^2.11.2", + "execa": "^8.0.1", + "split": "^1.0.1" + } +} diff --git a/qemu/src/QemuDisplay.ts b/qemu/src/QemuDisplay.ts new file mode 100644 index 0000000..c1504e8 --- /dev/null +++ b/qemu/src/QemuDisplay.ts @@ -0,0 +1,143 @@ +import { VncClient } from './rfb/client.js'; +import { EventEmitter } from 'node:events'; +import { Canvas, CanvasRenderingContext2D, createImageData } from 'canvas'; + +const kQemuFps = 30; + +export type VncRect = { + x: number; + y: number; + width: number; + height: number; +}; + +// events: +// +// 'resize' -> (w, h) -> done when resize occurs +// 'rect' -> (x, y, ImageData) -> framebuffer +// 'frame' -> () -> done at end of frame + +export class QemuDisplay extends EventEmitter { + private displayVnc = new VncClient({ + debug: false, + fps: kQemuFps, + + encodings: [ + VncClient.consts.encodings.raw, + + //VncClient.consts.encodings.pseudoQemuAudio, + VncClient.consts.encodings.pseudoDesktopSize + // For now? + //VncClient.consts.encodings.pseudoCursor + ] + }); + + private displayCanvas: Canvas = new Canvas(640, 480); + private displayCtx: CanvasRenderingContext2D = this.displayCanvas.getContext('2d'); + + private vncShouldReconnect: boolean = false; + private vncSocketPath: string; + + constructor(socketPath: string) { + super(); + + this.vncSocketPath = socketPath; + + this.displayVnc.on('connectTimeout', () => { + this.Reconnect(); + }); + + this.displayVnc.on('authError', () => { + this.Reconnect(); + }); + + this.displayVnc.on('disconnect', () => { + this.Reconnect(); + }); + + this.displayVnc.on('closed', () => { + this.Reconnect(); + }); + + this.displayVnc.on('firstFrameUpdate', () => { + // apparently this library is this good. + // at least it's better than the two others which exist. + this.displayVnc.changeFps(kQemuFps); + this.emit('connected'); + this.displayCanvas.width = this.displayVnc.clientWidth; + this.displayCanvas.height = this.displayVnc.clientHeight; + this.emit('resize', this.displayVnc.clientWidth, this.displayVnc.clientHeight); + this.emit('rect', 0, 0, this.displayCtx.getImageData(0, 0, this.displayVnc.clientWidth, this.displayVnc.clientHeight)); + this.emit('frame'); + }); + + this.displayVnc.on('desktopSizeChanged', ({ width, height }) => { + this.emit('resize', width, height); + this.displayCanvas.width = width; + this.displayCanvas.height = height; + }); + + let rects: VncRect[] = []; + + this.displayVnc.on('rectUpdateProcessed', (rect) => { + rects.push(rect); + }); + + this.displayVnc.on('frameUpdated', (fb) => { + this.displayCtx.putImageData(createImageData(new Uint8ClampedArray(fb.buffer), this.displayVnc.clientWidth, this.displayVnc.clientHeight), 0, 0); + + // TODO: optimize the rects a bit. using guacamole's cheap method + // of just flushing the whole screen if the area of all the updated rects gets too big + // might just work. + for (const rect of rects) { + this.emit('rect', rect.x, rect.y, this.displayCtx.getImageData(rect.x, rect.y, rect.width, rect.height)); + } + + rects = []; + + this.emit('frame'); + }); + } + + private Reconnect() { + if (this.displayVnc.connected) return; + + if (!this.vncShouldReconnect) return; + + // TODO: this should also give up after a max tries count + // if we fail after max tries, emit a event + + this.displayVnc.connect({ + path: this.vncSocketPath + }); + } + + Connect() { + this.vncShouldReconnect = true; + this.Reconnect(); + } + + Disconnect() { + this.vncShouldReconnect = false; + this.displayVnc.disconnect(); + } + + GetCanvas() { + return this.displayCanvas; + } + + Size() { + return { + width: this.displayVnc.clientWidth, + height: this.displayVnc.clientHeight + }; + } + + MouseEvent(x, y, buttons) { + this.displayVnc.sendPointerEvent(x, y, buttons); + } + + KeyboardEvent(keysym, pressed) { + this.displayVnc.sendKeyEvent(keysym, pressed); + } +} diff --git a/qemu/src/QemuUtil.ts b/qemu/src/QemuUtil.ts new file mode 100644 index 0000000..9f0dd65 --- /dev/null +++ b/qemu/src/QemuUtil.ts @@ -0,0 +1,33 @@ +// QEMU utility functions +// most of these are just for randomly generated/temporary files + +import { execa } from 'execa'; +import * as crypto from 'node:crypto'; + +/// Temporary path base for hard drive images. +const kVmHdaTmpPathBase = `/mnt/vmi/tmp/crusttest-hda`; + +// Generates a random unicast/local MAC address. +export async function GenMacAddress(): Promise { + return new Promise((res, rej) => { + crypto.randomBytes(6, (err, buf) => { + if (err) rej(err); + + // Modify byte 0 to make this MAC address proper + let rawByte0 = buf.readUInt8(0); + rawByte0 &= ~0b00000011; // keep most of the bits set from what we got, except for the Unicast and Local bits + rawByte0 |= 1 << 1; // Always set the Local bit. Leave the Unicast bit unset. + buf.writeUInt8(rawByte0); + + // this makes me wanna cry but it seems to working + res( + buf + .toString('hex') + .split(/(.{2})/) + .filter((o) => o) + .join(':') + ); + }); + }); +} + diff --git a/qemu/src/QemuVM.ts b/qemu/src/QemuVM.ts new file mode 100644 index 0000000..4691303 --- /dev/null +++ b/qemu/src/QemuVM.ts @@ -0,0 +1,286 @@ +import { execa, ExecaChildProcess } from 'execa'; +import { EventEmitter } from 'events'; +import QmpClient from './QmpClient.js'; +import { QemuDisplay } from './QemuDisplay.js'; +import { unlink } from 'node:fs/promises'; + +export enum VMState { + Stopped, + Starting, + Started, + Stopping +} + +export type QemuVmDefinition = { + id: string; + command: string[]; +}; + +/// Temporary path base (for UNIX sockets/etc.) +const kVmTmpPathBase = `/tmp`; + +/// The max amount of times QMP connection is allowed to fail before +/// the VM is forcefully stopped. +const kMaxFailCount = 5; + +let gVMShouldSnapshot = false; + +async function Sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +export function setSnapshot(val: boolean) { + gVMShouldSnapshot = val; +} + +export class QemuVM extends EventEmitter { + private state = VMState.Stopped; + + private qmpInstance: QmpClient | null = null; + private qmpConnected = false; + private qmpFailCount = 0; + + private qemuProcess: ExecaChildProcess | null = null; + private qemuRunning = false; + + private display: QemuDisplay | null = null; + + private definition: QemuVmDefinition; + private addedCommandShit = false; + + constructor(def: QemuVmDefinition) { + super(); + this.definition = def; + } + + async Start() { + // Don't start while either trying to start or starting. + if (this.state == VMState.Started || this.state == VMState.Starting) return; + + + var cmd = this.definition.command; + + // build additional command line statements to enable qmp/vnc over unix sockets + if(!this.addedCommandShit) { + cmd.push('-no-shutdown'); + if(gVMShouldSnapshot) + cmd.push('-snapshot'); + cmd.push(`-qmp`); + cmd.push(`unix:${this.GetQmpPath()},server,nowait`); + cmd.push(`-vnc`); + cmd.push(`unix:${this.GetVncPath()}`); + this.addedCommandShit = true; + } + + this.VMLog(`Starting QEMU with command \"${cmd.join(' ')}\"`); + await this.StartQemu(cmd); + } + + async Stop() { + // This is called in certain lifecycle places where we can't safely assert state yet + //this.AssertState(VMState.Started, 'cannot use QemuVM#Stop on a non-started VM'); + + // Start indicating we're stopping, so we don't + // erroneously start trying to restart everything + // we're going to tear down in this function call. + this.SetState(VMState.Stopping); + + // Kill the QEMU process and QMP/display connections if they are running. + await this.DisconnectQmp(); + this.DisconnectDisplay(); + await this.StopQemu(); + this.SetState(VMState.Stopped); + } + + async Reset() { + this.AssertState(VMState.Started, 'cannot use QemuVM#Reset on a non-started VM'); + + // let code know the VM is going to reset + // N.B: In the crusttest world, a reset simply amounts to a + // mean cold reboot of the qemu process basically + this.emit('reset'); + await this.Stop(); + await Sleep(500); + await this.Start(); + } + + async QmpCommand(command: string, args: any | null): Promise { + return await this.qmpInstance?.Execute(command, args); + } + + async MonitorCommand(command: string) { + this.AssertState(VMState.Started, 'cannot use QemuVM#MonitorCommand on a non-started VM'); + return await this.QmpCommand('human-monitor-command', { + 'command-line': command + }); + } + + async ChangeRemovableMedia(deviceName: string, imagePath: string): Promise { + this.AssertState(VMState.Started, 'cannot use QemuVM#ChangeRemovableMedia on a non-started VM'); + // N.B: if this throws, the code which called this should handle the error accordingly + await this.QmpCommand('blockdev-change-medium', { + device: deviceName, // techinically deprecated, but I don't feel like figuring out QOM path just for a simple function + filename: imagePath + }); + } + + async EjectRemovableMedia(deviceName: string) { + this.AssertState(VMState.Started, 'cannot use QemuVM#EjectRemovableMedia on a non-started VM'); + await this.QmpCommand('eject', { + device: deviceName + }); + } + + GetDisplay() { + return this.display; + } + + /// Private fun bits :) + + private VMLog(...args: any[]) { + // TODO: hook this into a logger of some sort + console.log(`[QemuVM] [${this.definition.id}] ${args.join('')}`); + } + + private AssertState(stateShouldBe: VMState, message: string) { + if (this.state !== stateShouldBe) throw new Error(message); + } + + private SetState(state) { + this.state = state; + this.emit('statechange', this.state); + } + + private GetQmpPath() { + return `${kVmTmpPathBase}/socket2-${this.definition.id}-ctrl`; + } + + private GetVncPath() { + return `${kVmTmpPathBase}/socket2-${this.definition.id}-vnc`; + } + + private async StartQemu(split) { + let self = this; + + this.SetState(VMState.Starting); + + // Start QEMU + this.qemuProcess = execa(split[0], split.slice(1)); + + this.qemuProcess.on('spawn', async () => { + self.qemuRunning = true; + await Sleep(500); + await self.ConnectQmp(); + }); + + this.qemuProcess.on('exit', async (code) => { + self.qemuRunning = false; + console.log("qemu process go boom") + + // ? + if (self.qmpConnected) { + await self.DisconnectQmp(); + } + + self.DisconnectDisplay(); + + if (self.state != VMState.Stopping) { + if (code == 0) { + await Sleep(500); + await self.StartQemu(split); + } else { + self.VMLog('QEMU exited with a non-zero exit code. This usually means an error in the command line. Stopping VM.'); + await self.Stop(); + } + } + }); + } + + private async StopQemu() { + if (this.qemuRunning == true) this.qemuProcess?.kill('SIGKILL'); + } + + private async ConnectQmp() { + let self = this; + + if (!this.qmpConnected) { + self.qmpInstance = new QmpClient(); + + self.qmpInstance.on('close', async () => { + self.qmpConnected = false; + + // If we aren't stopping, then we do actually need to care QMP disconnected + if (self.state != VMState.Stopping) { + if (self.qmpFailCount++ < kMaxFailCount) { + this.VMLog(`Failed to connect to QMP ${self.qmpFailCount} times`); + await Sleep(500); + await self.ConnectQmp(); + } else { + this.VMLog(`Failed to connect to QMP ${self.qmpFailCount} times, giving up`); + await self.Stop(); + } + } + }); + + self.qmpInstance.on('event', async (ev) => { + switch (ev.event) { + // Handle the STOP event sent when using -no-shutdown + case 'STOP': + await self.qmpInstance.Execute('system_reset'); + break; + case 'RESET': + self.VMLog('got a reset event!'); + await self.qmpInstance.Execute('cont'); + break; + } + }); + + self.qmpInstance.on('qmp-ready', async (hadError) => { + self.VMLog('QMP ready'); + + self.display = new QemuDisplay(self.GetVncPath()); + self.display.Connect(); + + // QMP has been connected so the VM is ready to be considered started + self.qmpFailCount = 0; + self.qmpConnected = true; + self.SetState(VMState.Started); + }); + + try { + await Sleep(500); + this.qmpInstance.ConnectUNIX(this.GetQmpPath()); + } catch (err) { + // just try again + await Sleep(500); + await this.ConnectQmp(); + } + } + } + + private async DisconnectDisplay() { + try { + this.display.Disconnect(); + this.display = null; // disassociate with that display object. + + await unlink(this.GetVncPath()); + } catch (err) { + // oh well lol + } + } + + private async DisconnectQmp() { + if (this.qmpConnected) return; + if(this.qmpInstance == null) + return; + + this.qmpConnected = false; + this.qmpInstance.end(); + this.qmpInstance = null; + try { + await unlink(this.GetQmpPath()); + } catch(err) { + + } + } +} diff --git a/qemu/src/QmpClient.ts b/qemu/src/QmpClient.ts new file mode 100644 index 0000000..67766e4 --- /dev/null +++ b/qemu/src/QmpClient.ts @@ -0,0 +1,135 @@ +// This was originally based off the contents of the node-qemu-qmp package, +// but I've modified it possibly to the point where it could be treated as my own creation. + +import split from 'split'; + +import { Socket } from 'net'; + +export type QmpCallback = (err: Error | null, res: any | null) => void; + +type QmpCommandEntry = { + callback: QmpCallback | null; + id: number; +}; + +// TODO: Instead of the client "Is-A"ing a Socket, this should instead contain/store a Socket, +// (preferrably) passed by the user, to use for QMP communications. +// The client shouldn't have to know or care about the protocol, and it effectively hackily uses the fact +// Socket extends EventEmitter. + +export default class QmpClient extends Socket { + public qmpHandshakeData: any; + private commandEntries: QmpCommandEntry[] = []; + private lastID = 0; + + private ExecuteSync(command: string, args: any | null, callback: QmpCallback | null) { + let cmd: QmpCommandEntry = { + callback: callback, + id: ++this.lastID + }; + + let qmpOut: any = { + execute: command, + id: cmd.id + }; + + if (args) qmpOut['arguments'] = args; + + // Add stuff + this.commandEntries.push(cmd); + this.write(JSON.stringify(qmpOut)); + } + + // TODO: Make this function a bit more ergonomic? + async Execute(command: string, args: any | null = null): Promise { + return new Promise((res, rej) => { + this.ExecuteSync(command, args, (err, result) => { + if (err) rej(err); + res(result); + }); + }); + } + + private Handshake(callback: () => void) { + this.write( + JSON.stringify({ + execute: 'qmp_capabilities' + }) + ); + + this.once('data', (data) => { + // Once QEMU replies to us, the handshake is done. + // We do not negotiate anything special. + callback(); + }); + } + + // this can probably be made async + private ConnectImpl() { + let self = this; + + this.once('connect', () => { + this.removeAllListeners('error'); + }); + + this.once('error', (err) => { + // just rethrow lol + //throw err; + + console.log("you have pants: rules,", err); + }); + + this.once('data', (data) => { + // Handshake QMP with the server. + self.qmpHandshakeData = JSON.parse(data.toString('utf8')).QMP; + self.Handshake(() => { + // Now ready to parse QMP responses/events. + self.pipe(split(JSON.parse)) + .on('data', (json: any) => { + if (json == null) return self.end(); + + if (json.return || json.error) { + // Our handshake has a spurious return because we never assign it an ID, + // and it is gathered by this pipe for some reason I'm not quite sure about. + // So, just for safety's sake, don't process any return objects which don't have an ID attached to them. + if (json.id == null) return; + + let callbackEntry = this.commandEntries.find((entry) => entry.id === json.id); + let error: Error | null = json.error ? new Error(json.error.desc) : null; + + // we somehow didn't find a callback entry for this response. + // I don't know how. Techinically not an error..., but I guess you're not getting a reponse to whatever causes this to happen + if (callbackEntry == null) return; + + if (callbackEntry?.callback) callbackEntry.callback(error, json.return); + + // Remove the completed callback entry. + this.commandEntries.slice(this.commandEntries.indexOf(callbackEntry)); + } else if (json.event) { + this.emit('event', json); + } + }) + .on('error', () => { + // Give up. + return self.end(); + }); + this.emit('qmp-ready'); + }); + }); + + this.once('close', () => { + this.end(); + this.removeAllListeners('data'); // wow. good job bud. cool memory leak + }); + } + + Connect(host, port) { + super.connect(port, host); + this.ConnectImpl(); + } + + ConnectUNIX(path: string) { + super.connect(path); + this.ConnectImpl(); + } +} diff --git a/qemu/src/index.ts b/qemu/src/index.ts new file mode 100644 index 0000000..274f400 --- /dev/null +++ b/qemu/src/index.ts @@ -0,0 +1,3 @@ +export * from './QemuDisplay.js'; +export * from './QemuUtil.js'; +export * from './QemuVM.js'; diff --git a/qemu/src/rfb/LICENSE b/qemu/src/rfb/LICENSE new file mode 100644 index 0000000..26a16fe --- /dev/null +++ b/qemu/src/rfb/LICENSE @@ -0,0 +1,21 @@ +Copyright 2021 Filipe Calaça Barbosa +Copyright 2022 dither +Copyright 2023 Lily Tsuru/modeco80 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/qemu/src/rfb/README.md b/qemu/src/rfb/README.md new file mode 100644 index 0000000..b679a92 --- /dev/null +++ b/qemu/src/rfb/README.md @@ -0,0 +1,10 @@ +# Notice + +The source here was originally taken from a fork of [vnc-rfb-client](https://github.com/ayunami2000/vnc-rfb-client) made for the LucidVM project, available [here](https://github.com/lucidvm/rfb). + +It has been grossly modified for the usecases for the `@socketcomputer/qemu` package: + +- converted to TypeScript +- all modules rewritten to use ESM +- some noisy debug prints removed +- (some, very tiny) code cleanup diff --git a/qemu/src/rfb/client.ts b/qemu/src/rfb/client.ts new file mode 100644 index 0000000..5ae92f3 --- /dev/null +++ b/qemu/src/rfb/client.ts @@ -0,0 +1,885 @@ +import { HextileDecoder } from './decoders/hextile.js'; +import { RawDecoder } from './decoders/raw.js'; +import { ZrleDecoder } from './decoders/zrle.js'; +// import { TightDecoder } from "./decoders/tight.js"; +import { CopyRectDecoder } from './decoders/copyrect.js'; + +import { EventEmitter } from 'node:events'; + +import { consts } from './constants.js'; + +import * as net from 'node:net'; +import * as crypto from 'node:crypto'; + +import { SocketBuffer } from './socketbuffer.js'; + +export class VncClient extends EventEmitter { + // These are in no particular order. + + public debug: Boolean; + + private _connected: Boolean; + private _authenticated: Boolean; + private _version: string; + private _password: string; + + private _audioChannels: number; + private _audioFrequency: number; + + private _rects: number; + + private _decoders: any; // no real good way to type this yet. will do it later + + private _fps: number; + private _timerInterval: number; + private _timerPointer; + + public fb: Buffer; + + private _handshaked: Boolean; + private _waitingServerInit: Boolean; + private _expectingChallenge: Boolean; + private _challengeResponseSent: Boolean; + + private _set8BitColor: Boolean; + private _frameBufferReady = false; + private _firstFrameReceived = false; + private _processingFrame = false; + + private _relativePointer: Boolean; + + public bigEndianFlag: Boolean; + + public clientWidth: number; + public clientHeight: number; + public clientName: string; + + public pixelFormat: any; + + private _colorMap: any[]; + private _audioData: Buffer; + + private _cursor: any; + + public encodings: number[]; + + private _connection: net.Socket; + private _socketBuffer: SocketBuffer; + + static get consts() { + return { + encodings: consts.encodings + }; + } + + /** + * Return if client is connected + * @returns {boolean} + */ + get connected() { + return this._connected; + } + + /** + * Return if client is authenticated + * @returns {boolean} + */ + get authenticated() { + return this._authenticated; + } + + /** + * Return negotiated protocol version + * @returns {string} + */ + get protocolVersion() { + return this._version; + } + + /** + * Return the local port used by the client + * @returns {number} + */ + get localPort() { + return this._connection ? this._connection.localPort : 0; + } + + constructor(options: any = { debug: false, fps: 0, encodings: [] }) { + super(); + + this._socketBuffer = new SocketBuffer(); + + this.resetState(); + this.debug = options.debug || false; + this._fps = Number(options.fps) || 0; + // Calculate interval to meet configured FPS + this._timerInterval = this._fps > 0 ? 1000 / this._fps : 0; + + // Default encodings + this.encodings = + options.encodings && options.encodings.length + ? options.encodings + : [consts.encodings.copyRect, consts.encodings.zrle, consts.encodings.hextile, consts.encodings.raw, consts.encodings.pseudoDesktopSize]; + + this._audioChannels = options.audioChannels || 2; + this._audioFrequency = options.audioFrequency || 22050; + + this._rects = 0; + this._decoders = {}; + this._decoders[consts.encodings.raw] = new RawDecoder(); + // TODO: Implement tight encoding + // this._decoders[encodings.tight] = new tightDecoder(); + this._decoders[consts.encodings.zrle] = new ZrleDecoder(); + this._decoders[consts.encodings.copyRect] = new CopyRectDecoder(); + this._decoders[consts.encodings.hextile] = new HextileDecoder(); + + if (this._timerInterval) { + this._fbTimer(); + } + } + + /** + * Timer used to limit the rate of frame update requests according to configured FPS + * @private + */ + _fbTimer() { + this._timerPointer = setTimeout(() => { + this._fbTimer(); + if (this._firstFrameReceived && !this._processingFrame && this._fps > 0) { + this.requestFrameUpdate(); + } + }, this._timerInterval); + } + + /** + * Adjuste the configured FPS + * @param fps {number} - Number of update requests send by second + */ + changeFps(fps) { + if (!Number.isNaN(fps)) { + this._fps = Number(fps); + this._timerInterval = this._fps > 0 ? 1000 / this._fps : 0; + + if (this._timerPointer && !this._fps) { + // If FPS was zeroed stop the timer + clearTimeout(this._timerPointer); + this._timerPointer = null; + } else if (this._fps && !this._timerPointer) { + // If FPS was zero and is now set, start the timer + this._fbTimer(); + } + } else { + throw new Error('Invalid FPS. Must be a number.'); + } + } + + /** + * Starts the connection with the VNC server + * @param options + */ + connect( + options /* = { + host: '', + password: '', + path: '', + set8BitColor: false, + port: 5900 + } */ + ) { + if (options.password) { + this._password = options.password; + } + + this._set8BitColor = options.set8BitColor || false; + + if (options.path === null) { + if (!options.host) { + throw new Error('Host missing.'); + } + this._connection = net.connect(options.port || 5900, options.host); + + // disable nagle's algorithm for TCP + this._connection.setNoDelay(); + } else { + // unix socket. bodged in but oh well + this._connection = net.connect(options.path); + } + + this._connection.on('connect', () => { + this._connected = true; + this.emit('connected'); + }); + + this._connection.on('close', () => { + this.resetState(); + this.emit('closed'); + }); + + this._connection.on('timeout', () => { + this.emit('connectTimeout'); + }); + + this._connection.on('error', (err) => { + this.emit('connectError', err); + }); + + this._connection.on('data', async (data) => { + this._socketBuffer.pushData(data); + + if (!this._handshaked) { + this._handleHandshake(); + } else if (this._expectingChallenge) { + this._handleAuthChallenge(); + } else if (this._waitingServerInit) { + await this._handleServerInit(); + } else { + await this._handleData(); + } + }); + } + + /** + * Disconnect the client + */ + disconnect() { + if (this._connection) { + this._connection.end(); + this.resetState(); + this.emit('disconnected'); + } + } + + /** + * Request the server a frame update + * @param full - If the server should send all the frame buffer or just the last changes + * @param incremental - Incremental number for not full requests + * @param x - X position of the update area desired, usually 0 + * @param y - Y position of the update area desired, usually 0 + * @param width - Width of the update area desired, usually client width + * @param height - Height of the update area desired, usually client height + */ + requestFrameUpdate(full = false, incremental = 1, x = 0, y = 0, width = this.clientWidth, height = this.clientHeight) { + if ((this._frameBufferReady || full) && this._connection && !this._rects) { + // Request data + const message = Buffer.alloc(10); + message.writeUInt8(3); // Message type + message.writeUInt8(full ? 0 : incremental, 1); // Incremental + message.writeUInt16BE(x, 2); // X-Position + message.writeUInt16BE(y, 4); // Y-Position + message.writeUInt16BE(width, 6); // Width + message.writeUInt16BE(height, 8); // Height + + this._connection.write(message); + + this._frameBufferReady = true; + } + } + + /** + * Handle handshake msg + * @private + */ + _handleHandshake() { + // Handshake, negotiating protocol version + if (this._socketBuffer.toString() === consts.versionString.V3_003) { + this._log('Sending 3.3', true); + this._connection.write(consts.versionString.V3_003); + this._version = '3.3'; + } else if (this._socketBuffer.toString() === consts.versionString.V3_007) { + this._log('Sending 3.7', true); + this._connection.write(consts.versionString.V3_007); + this._version = '3.7'; + } else if (this._socketBuffer.toString() === consts.versionString.V3_008) { + this._log('Sending 3.8', true); + this._connection.write(consts.versionString.V3_008); + this._version = '3.8'; + } else { + // Negotiating auth mechanism + this._handshaked = true; + if (this._socketBuffer.includes(0x02) && this._password) { + this._log('Password provided and server support VNC auth. Choosing VNC auth.', true); + this._expectingChallenge = true; + this._connection.write(Buffer.from([0x02])); + } else if (this._socketBuffer.includes(1)) { + this._log('Password not provided or server does not support VNC auth. Trying none.', true); + this._connection.write(Buffer.from([0x01])); + if (this._version === '3.7') { + this._waitingServerInit = true; + } else { + this._expectingChallenge = true; + this._challengeResponseSent = true; + } + } else { + this._log('Connection error. Msg: ' + this._socketBuffer.toString()); + this.disconnect(); + } + } + + this._socketBuffer?.flush(false); + } + + /** + * Handle VNC auth challenge + * @private + */ + _handleAuthChallenge() { + if (this._challengeResponseSent) { + // Challenge response already sent. Checking result. + + if (this._socketBuffer.buffer[3] === 0) { + // Auth success + this._authenticated = true; + this.emit('authenticated'); + this._expectingChallenge = false; + this._sendClientInit(); + } else { + // Auth fail + this.emit('authError'); + this.resetState(); + } + } else { + const key = Buffer.alloc(8); + key.fill(0); + key.write(this._password.slice(0, 8)); + + this.reverseBits(key); + + const des1 = crypto.createCipheriv('des', key, Buffer.alloc(8)); + const des2 = crypto.createCipheriv('des', key, Buffer.alloc(8)); + + const response = Buffer.alloc(16); + + response.fill(des1.update(this._socketBuffer.buffer.slice(0, 8)), 0, 8); + response.fill(des2.update(this._socketBuffer.buffer.slice(8, 16)), 8, 16); + + this._connection.write(response); + this._challengeResponseSent = true; + } + + this._socketBuffer.flush(false); + } + + /** + * Reverse bits order of a byte + * @param buf - Buffer to be flipped + */ + reverseBits(buf) { + for (let x = 0; x < buf.length; x++) { + let newByte = 0; + newByte += buf[x] & 128 ? 1 : 0; + newByte += buf[x] & 64 ? 2 : 0; + newByte += buf[x] & 32 ? 4 : 0; + newByte += buf[x] & 16 ? 8 : 0; + newByte += buf[x] & 8 ? 16 : 0; + newByte += buf[x] & 4 ? 32 : 0; + newByte += buf[x] & 2 ? 64 : 0; + newByte += buf[x] & 1 ? 128 : 0; + buf[x] = newByte; + } + } + + /** + * Handle server init msg + * @returns {Promise} + * @private + */ + async _handleServerInit() { + this._waitingServerInit = false; + + await this._socketBuffer.waitBytes(18); + + this.clientWidth = this._socketBuffer.readUInt16BE(); + this.clientHeight = this._socketBuffer.readUInt16BE(); + this.pixelFormat.bitsPerPixel = this._socketBuffer.readUInt8(); + this.pixelFormat.depth = this._socketBuffer.readUInt8(); + this.pixelFormat.bigEndianFlag = this._socketBuffer.readUInt8(); + this.pixelFormat.trueColorFlag = this._socketBuffer.readUInt8(); + this.pixelFormat.redMax = this.bigEndianFlag ? this._socketBuffer.readUInt16BE() : this._socketBuffer.readUInt16LE(); + this.pixelFormat.greenMax = this.bigEndianFlag ? this._socketBuffer.readUInt16BE() : this._socketBuffer.readUInt16LE(); + this.pixelFormat.blueMax = this.bigEndianFlag ? this._socketBuffer.readUInt16BE() : this._socketBuffer.readUInt16LE(); + this.pixelFormat.redShift = this._socketBuffer.readInt8(); + this.pixelFormat.greenShift = this._socketBuffer.readInt8(); + this.pixelFormat.blueShift = this._socketBuffer.readInt8(); + this.updateFbSize(); + this.clientName = this._socketBuffer.buffer.slice(24).toString(); + + this._socketBuffer.flush(false); + + // FIXME: Removed because these are noise + //this._log(`Screen size: ${this.clientWidth}x${this.clientHeight}`); + //this._log(`Client name: ${this.clientName}`); + //this._log(`pixelFormat: ${JSON.stringify(this.pixelFormat)}`); + + if (this._set8BitColor) { + //this._log(`8 bit color format requested, only raw encoding is supported.`); + this._setPixelFormatToColorMap(); + } + + this._sendEncodings(); + + setTimeout(() => { + this.requestFrameUpdate(true); + }, 1000); + } + + /** + * Update the frame buffer size according to client width and height (RGBA) + */ + updateFbSize() { + this.fb = Buffer.alloc(this.clientWidth * this.clientHeight * 4); + } + + /** + * Request the server to change to 8bit color format (Color palette). Only works with Raw encoding. + * @private + */ + _setPixelFormatToColorMap() { + this._log(`Requesting PixelFormat change to ColorMap (8 bits).`); + + const message = Buffer.alloc(20); + message.writeUInt8(0); // Tipo da mensagem + message.writeUInt8(0, 1); // Padding + message.writeUInt8(0, 2); // Padding + message.writeUInt8(0, 3); // Padding + + message.writeUInt8(8, 4); // PixelFormat - BitsPerPixel + message.writeUInt8(8, 5); // PixelFormat - Depth + message.writeUInt8(0, 6); // PixelFormat - BigEndianFlag + message.writeUInt8(0, 7); // PixelFormat - TrueColorFlag + message.writeUInt16BE(255, 8); // PixelFormat - RedMax + message.writeUInt16BE(255, 10); // PixelFormat - GreenMax + message.writeUInt16BE(255, 12); // PixelFormat - BlueMax + message.writeUInt8(0, 14); // PixelFormat - RedShift + message.writeUInt8(8, 15); // PixelFormat - GreenShift + message.writeUInt8(16, 16); // PixelFormat - BlueShift + message.writeUInt8(0, 17); // PixelFormat - Padding + message.writeUInt8(0, 18); // PixelFormat - Padding + message.writeUInt8(0, 19); // PixelFormat - Padding + + // Envia um setPixelFormat trocando para mapa de cores + this._connection.write(message); + + this.pixelFormat.bitsPerPixel = 8; + this.pixelFormat.depth = 8; + } + + /** + * Send supported encodings + * @private + */ + _sendEncodings() { + //this._log('Sending encodings.'); + // If this._set8BitColor is set, only copyrect and raw encodings are supported + const message = Buffer.alloc(4 + (!this._set8BitColor ? this.encodings.length : 2) * 4); + message.writeUInt8(2); // Message type + message.writeUInt8(0, 1); // Padding + message.writeUInt16BE(!this._set8BitColor ? this.encodings.length : 2, 2); // Padding + + let offset = 4; + // If 8bits is not set, send all encodings configured + if (!this._set8BitColor) { + for (const e of this.encodings) { + message.writeInt32BE(e, offset); + offset += 4; + } + } else { + message.writeInt32BE(consts.encodings.copyRect, offset); + message.writeInt32BE(consts.encodings.raw, offset + 4); + } + + this._connection.write(message); + } + + /** + * Send client init msg + * @private + */ + _sendClientInit() { + //this._log(`Sending clientInit`); + this._waitingServerInit = true; + // Shared bit set + this._connection.write('1'); + } + + /** + * Handle data msg + * @returns {Promise} + * @private + */ + async _handleData() { + if (!this._rects) { + switch (this._socketBuffer.buffer[0]) { + case consts.serverMsgTypes.fbUpdate: + await this._handleFbUpdate(); + break; + + case consts.serverMsgTypes.setColorMap: + await this._handleSetColorMap(); + break; + + case consts.serverMsgTypes.bell: + this.emit('bell'); + this._socketBuffer.flush(); + break; + + case consts.serverMsgTypes.cutText: + await this._handleCutText(); + break; + + case consts.serverMsgTypes.qemuAudio: + await this._handleQemuAudio(); + break; + } + } + } + + /** + * Cut message (text was copied to clipboard on server) + * @returns {Promise} + * @private + */ + async _handleCutText() { + this._socketBuffer.setOffset(4); + await this._socketBuffer.waitBytes(1); + const length = this._socketBuffer.readUInt32BE(); + await this._socketBuffer.waitBytes(length); + this.emit('cutText', this._socketBuffer.readNBytesOffset(length).toString()); + this._socketBuffer.flush(); + } + + /** + * Gets the pseudocursor framebuffer + */ + _getPseudoCursor() { + if (!this._cursor.width) + return { + width: 1, + height: 1, + data: Buffer.alloc(4) + }; + const { width, height, bitmask, cursorPixels } = this._cursor; + const data = Buffer.alloc(height * width * 4); + for (var y = 0; y < height; y++) { + for (var x = 0; x < width; x++) { + const offset = (y * width + x) * 4; + const active = (bitmask[Math.floor((width + 7) / 8) * y + Math.floor(x / 8)] >> (7 - (x % 8))) & 1; + if (active) { + switch (this.pixelFormat.bitsPerPixel) { + case 8: + console.log(8); + const index = cursorPixels.readUInt8(offset); + const color = this._colorMap[index] | 0xff; + data.writeIntBE(color, offset, 4); + break; + case 32: + // TODO: compatibility with VMware actually using the alpha channel + const b = cursorPixels.readUInt8(offset); + const g = cursorPixels.readUInt8(offset + 1); + const r = cursorPixels.readUInt8(offset + 2); + data.writeUInt8(r, offset); + data.writeUInt8(g, offset + 1); + data.writeUInt8(b, offset + 2); + data.writeUInt8(0xff, offset + 3); + break; + default: + data.writeIntBE(cursorPixels.readIntBE(offset, this.pixelFormat.bitsPerPixel / 8), offset, this.pixelFormat.bitsPerPixel / 8); + break; + } + } + } + } + return { + x: this._cursor.x, + y: this._cursor.y, + width, + height, + data + }; + } + + /** + * Handle a rects of update message + */ + async _handleRect() { + this._processingFrame = true; + const sendFbUpdate = this._rects; + + while (this._rects) { + await this._socketBuffer.waitBytes(12); + const rect: any = {}; + rect.x = this._socketBuffer.readUInt16BE(); + rect.y = this._socketBuffer.readUInt16BE(); + rect.width = this._socketBuffer.readUInt16BE(); + rect.height = this._socketBuffer.readUInt16BE(); + rect.encoding = this._socketBuffer.readInt32BE(); + + if (rect.encoding === consts.encodings.pseudoQemuAudio) { + this.sendAudio(true); + this.sendAudioConfig(this._audioChannels, this._audioFrequency); //todo: future: setFrequency(...) to update mid thing + } else if (rect.encoding === consts.encodings.pseudoQemuPointerMotionChange) { + this._relativePointer = rect.x == 0; + } else if (rect.encoding === consts.encodings.pseudoCursor) { + const dataSize = rect.width * rect.height * (this.pixelFormat.bitsPerPixel / 8); + const bitmaskSize = Math.floor((rect.width + 7) / 8) * rect.height; + this._cursor.width = rect.width; + this._cursor.height = rect.height; + this._cursor.x = rect.x; + this._cursor.y = rect.y; + this._cursor.cursorPixels = this._socketBuffer.readNBytesOffset(dataSize); + this._cursor.bitmask = this._socketBuffer.readNBytesOffset(bitmaskSize); + rect.data = Buffer.concat([this._cursor.cursorPixels, this._cursor.bitmask]); + this.emit('cursorChanged', this._getPseudoCursor()); + } else if (rect.encoding === consts.encodings.pseudoDesktopSize) { + this._log('Frame Buffer size change requested by the server', true); + this.clientHeight = rect.height; + this.clientWidth = rect.width; + this.updateFbSize(); + this.emit('desktopSizeChanged', { width: this.clientWidth, height: this.clientHeight }); + } else if (this._decoders[rect.encoding]) { + await this._decoders[rect.encoding].decode( + rect, + this.fb, + this.pixelFormat.bitsPerPixel, + this._colorMap, + this.clientWidth, + this.clientHeight, + this._socketBuffer, + this.pixelFormat.depth + ); + this.emit('rectUpdateProcessed', rect); + } else { + this._log('Non supported update received. Encoding: ' + rect.encoding); + } + this._rects--; + this.emit('rectProcessed', rect); + + if (!this._rects) { + this._socketBuffer.flush(true); + } + } + + if (sendFbUpdate) { + if (!this._firstFrameReceived) { + this._firstFrameReceived = true; + this.emit('firstFrameUpdate', this.fb); + } + this._log('Frame buffer updated.', true); + this.emit('frameUpdated', this.fb); + } + + this._processingFrame = false; + + if (this._fps === 0) { + // If FPS is not set, request a new update as soon as the last received has been processed + this.requestFrameUpdate(); + } + } + + async _handleFbUpdate() { + this._socketBuffer.setOffset(2); + this._rects = this._socketBuffer.readUInt16BE(); + this._log('Frame update received. Rects: ' + this._rects, true); + await this._handleRect(); + } + + /** + * Handle setColorMap msg + * @returns {Promise} + * @private + */ + async _handleSetColorMap() { + this._socketBuffer.setOffset(2); + let firstColor = this._socketBuffer.readUInt16BE(); + const numColors = this._socketBuffer.readUInt16BE(); + + this._log(`ColorMap received. Colors: ${numColors}.`); + + await this._socketBuffer.waitBytes(numColors * 6); + + for (let x = 0; x < numColors; x++) { + this._colorMap[firstColor] = { + r: Math.floor((this._socketBuffer.readUInt16BE() / 65535) * 255), + g: Math.floor((this._socketBuffer.readUInt16BE() / 65535) * 255), + b: Math.floor((this._socketBuffer.readUInt16BE() / 65535) * 255) + }; + firstColor++; + } + + this.emit('colorMapUpdated', this._colorMap); + this._socketBuffer.flush(); + } + + async _handleQemuAudio() { + this._socketBuffer.setOffset(2); + let operation = this._socketBuffer.readUInt16BE(); + if (operation == 2) { + const length = this._socketBuffer.readUInt32BE(); + + //this._log(`Audio received. Length: ${length}.`); + + await this._socketBuffer.waitBytes(length); + + let audioBuffer = this._socketBuffer.readNBytes(length); + + this._audioData = audioBuffer; + } + + this.emit('audioStream', this._audioData); + this._socketBuffer.flush(); + } + + /** + * Reset the class state + */ + resetState() { + if (this._connection) { + this._connection.end(); + } + + if (this._timerPointer) { + clearInterval(this._timerPointer); + } + + this._timerPointer = null; + + //this._connection = null; + + this._connected = false; + this._authenticated = false; + this._version = ''; + + this._password = ''; + + this._audioChannels = 2; + this._audioFrequency = 22050; + + this._handshaked = false; + + this._expectingChallenge = false; + this._challengeResponseSent = false; + + this._frameBufferReady = false; + this._firstFrameReceived = false; + this._processingFrame = false; + + this.clientWidth = 0; + this.clientHeight = 0; + this.clientName = ''; + + this.pixelFormat = { + bitsPerPixel: 0, + depth: 0, + bigEndianFlag: 0, + trueColorFlag: 0, + redMax: 0, + greenMax: 0, + blueMax: 0, + redShift: 0, + blueShift: 0, + greenShift: 0 + }; + + this._rects = 0; + + this._colorMap = []; + this.fb = null; + + this._socketBuffer?.flush(false); + + this._cursor = { + width: 0, + height: 0, + x: 0, + y: 0, + cursorPixels: null, + bitmask: null + }; + } + + // NIT(lily) instead of allocating it'd be way better if these just re-used a 16/32 byte buffer used just for these. + + /** + * Send a key event + * @param key - Key code (keysym) defined by X Window System, check https://wiki.linuxquestions.org/wiki/List_of_keysyms + * @param down - True if the key is pressed, false if it is not + */ + sendKeyEvent(key, down = false) { + const message = Buffer.alloc(8); + message.writeUInt8(4); // Message type + message.writeUInt8(down ? 1 : 0, 1); // Down flag + message.writeUInt8(0, 2); // Padding + message.writeUInt8(0, 3); // Padding + + message.writeUInt32BE(key, 4); // Key code + + this._connection.write(message); + } + + /** + * Send a raw pointer event + * @param xPosition - X Position + * @param yPosition - Y Position + * @param mask - Raw RFB button mask + */ + sendPointerEvent(xPosition, yPosition, buttonMask) { + const message = Buffer.alloc(6); + message.writeUInt8(consts.clientMsgTypes.pointerEvent); // Message type + message.writeUInt8(buttonMask, 1); // Button Mask + const reladd = this._relativePointer ? 0x7fff : 0; + message.writeUInt16BE(xPosition + reladd, 2); // X Position + message.writeUInt16BE(yPosition + reladd, 4); // Y Position + + this._cursor.posX = xPosition; + this._cursor.posY = yPosition; + + this._connection.write(message); + } + + /** + * Send client cut message to server + * @param text - latin1 encoded + */ + clientCutText(text) { + const textBuffer = Buffer.from(text, 'latin1'); + const message = Buffer.alloc(8 + textBuffer.length); + message.writeUInt8(6); // Message type + message.writeUInt8(0, 1); // Padding + message.writeUInt8(0, 2); // Padding + message.writeUInt8(0, 3); // Padding + message.writeUInt32BE(textBuffer.length, 4); // Padding + textBuffer.copy(message, 8); + + this._connection.write(message); + } + + sendAudio(enable) { + const message = Buffer.alloc(4); + message.writeUInt8(consts.clientMsgTypes.qemuAudio); // Message type + message.writeUInt8(1, 1); // Submessage Type + message.writeUInt16BE(enable ? 0 : 1, 2); // Operation + this._connection.write(message); + } + + sendAudioConfig(channels, frequency) { + const message = Buffer.alloc(10); + message.writeUInt8(consts.clientMsgTypes.qemuAudio); // Message type + message.writeUInt8(1, 1); // Submessage Type + message.writeUInt16BE(2, 2); // Operation + message.writeUInt8(0 /*U8*/, 4); // Sample Format + message.writeUInt8(channels, 5); // Number of Channels + message.writeUInt32BE(frequency, 6); // Frequency + this._connection.write(message); + } + + /** + * Print log info + * @param text + * @param debug + * @private + */ + _log(text, debug = false) { + if (!debug || (debug && this.debug)) { + console.log(text); + } + } +} diff --git a/qemu/src/rfb/constants.ts b/qemu/src/rfb/constants.ts new file mode 100644 index 0000000..bd8c46a --- /dev/null +++ b/qemu/src/rfb/constants.ts @@ -0,0 +1,46 @@ +// based on https://github.com/sidorares/node-rfb2 + +export const consts = { + clientMsgTypes: { + setPixelFormat: 0, + setEncodings: 2, + fbUpdate: 3, + keyEvent: 4, + pointerEvent: 5, + cutText: 6, + qemuAudio: 255 + }, + serverMsgTypes: { + fbUpdate: 0, + setColorMap: 1, + bell: 2, + cutText: 3, + qemuAudio: 255 + }, + versionString: { + V3_003: 'RFB 003.003\n', + V3_007: 'RFB 003.007\n', + V3_008: 'RFB 003.008\n' + }, + encodings: { + raw: 0, + copyRect: 1, + rre: 2, + corre: 4, + hextile: 5, + zlib: 6, + tight: 7, + zlibhex: 8, + trle: 15, + zrle: 16, + h264: 50, + pseudoCursor: -239, + pseudoDesktopSize: -223, + pseudoQemuPointerMotionChange: -257, + pseudoQemuAudio: -259 + }, + security: { + None: 1, + VNC: 2 + } +}; diff --git a/qemu/src/rfb/decoders/copyrect.ts b/qemu/src/rfb/decoders/copyrect.ts new file mode 100644 index 0000000..d87ae58 --- /dev/null +++ b/qemu/src/rfb/decoders/copyrect.ts @@ -0,0 +1,29 @@ +export class CopyRectDecoder { + getPixelBytePos(x, y, width, height) { + return (y * width + x) * 4; + } + + async decode(rect, fb, bitsPerPixel, colorMap, screenW, screenH, socket, depth): Promise { + return new Promise(async (resolve, reject) => { + await socket.waitBytes(4); + rect.data = socket.readNBytesOffset(4); + + const x = rect.data.readUInt16BE(); + const y = rect.data.readUInt16BE(2); + + for (let h = 0; h < rect.height; h++) { + for (let w = 0; w < rect.width; w++) { + const fbOrigBytePosOffset = this.getPixelBytePos(x + w, y + h, screenW, screenH); + const fbBytePosOffset = this.getPixelBytePos(rect.x + w, rect.y + h, screenW, screenH); + + fb.writeUInt8(fb.readUInt8(fbOrigBytePosOffset), fbBytePosOffset); + fb.writeUInt8(fb.readUInt8(fbOrigBytePosOffset + 1), fbBytePosOffset + 1); + fb.writeUInt8(fb.readUInt8(fbOrigBytePosOffset + 2), fbBytePosOffset + 2); + fb.writeUInt8(fb.readUInt8(fbOrigBytePosOffset + 3), fbBytePosOffset + 3); + } + } + + resolve(); + }); + } +} diff --git a/qemu/src/rfb/decoders/hextile.ts b/qemu/src/rfb/decoders/hextile.ts new file mode 100644 index 0000000..45f7b95 --- /dev/null +++ b/qemu/src/rfb/decoders/hextile.ts @@ -0,0 +1,241 @@ +export class HextileDecoder { + getPixelBytePos(x, y, width, height) { + return (y * width + x) * 4; + } + + async decode(rect, fb, bitsPerPixel, colorMap, screenW, screenH, socket, depth): Promise { + return new Promise(async (resolve, reject) => { + const initialOffset = socket.offset; + let dataSize = 0; + + let tiles; + let totalTiles; + let tilesX; + let tilesY; + + let lastSubEncoding; + + const backgroundColor = { r: 0, g: 0, b: 0, a: 255 }; + const foregroundColor = { r: 0, g: 0, b: 0, a: 255 }; + + tilesX = Math.ceil(rect.width / 16); + tilesY = Math.ceil(rect.height / 16); + tiles = tilesX * tilesY; + totalTiles = tiles; + + while (tiles) { + await socket.waitBytes(1); + const subEncoding = socket.readUInt8(); + dataSize++; + const currTile = totalTiles - tiles; + + // Calculate tile position and size + const tileX = currTile % tilesX; + const tileY = Math.floor(currTile / tilesX); + const tx = rect.x + tileX * 16; + const ty = rect.y + tileY * 16; + const tw = Math.min(16, rect.x + rect.width - tx); + const th = Math.min(16, rect.y + rect.height - ty); + + if (subEncoding === 0) { + if (lastSubEncoding & 0x01) { + // We need to ignore zeroed tile after a raw tile + } else { + // If zeroed tile and last tile was not raw, use the last backgroundColor + this.applyColor(tw, th, tx, ty, screenW, screenH, backgroundColor, fb); + } + } else if (subEncoding & 0x01) { + // If Raw, ignore all other bits + await socket.waitBytes(th * tw * (bitsPerPixel / 8)); + dataSize += th * tw * (bitsPerPixel / 8); + for (let h = 0; h < th; h++) { + for (let w = 0; w < tw; w++) { + const fbBytePosOffset = this.getPixelBytePos(tx + w, ty + h, screenW, screenH); + if (bitsPerPixel === 8) { + const index = socket.readUInt8(); + const color = colorMap[index]; + // RGB + // fb.writeUInt8(color?.r || 255, fbBytePosOffset); + // fb.writeUInt8(color?.g || 255, fbBytePosOffset + 1); + // fb.writeUInt8(color?.b || 255, fbBytePosOffset + 2); + + // BGR + fb.writeUInt8(color?.r || 255, fbBytePosOffset + 2); + fb.writeUInt8(color?.g || 255, fbBytePosOffset + 1); + fb.writeUInt8(color?.b || 255, fbBytePosOffset); + } else if (bitsPerPixel === 24) { + fb.writeUInt8(socket.readUInt8(), fbBytePosOffset); + fb.writeUInt8(socket.readUInt8(), fbBytePosOffset + 1); + fb.writeUInt8(socket.readUInt8(), fbBytePosOffset + 2); + } else if (bitsPerPixel === 32) { + // RGB + // fb.writeUInt8(rect.data.readUInt8(bytePosOffset), fbBytePosOffset); + // fb.writeUInt8(rect.data.readUInt8(bytePosOffset + 1), fbBytePosOffset + 1); + // fb.writeUInt8(rect.data.readUInt8(bytePosOffset + 2), fbBytePosOffset + 2); + + // BGR + fb.writeUInt8(socket.readUInt8(), fbBytePosOffset + 2); + fb.writeUInt8(socket.readUInt8(), fbBytePosOffset + 1); + fb.writeUInt8(socket.readUInt8(), fbBytePosOffset); + socket.readUInt8(); + } + // Alpha, always 255 + fb.writeUInt8(255, fbBytePosOffset + 3); + } + } + lastSubEncoding = subEncoding; + } else { + // Background bit + if (subEncoding & 0x02) { + switch (bitsPerPixel) { + case 8: + await socket.waitBytes(1); + const index = socket.readUInt8(); + dataSize++; + backgroundColor.r = colorMap[index].r || 255; + backgroundColor.g = colorMap[index].g || 255; + backgroundColor.b = colorMap[index].b || 255; + break; + + case 24: + await socket.waitBytes(3); + dataSize += 3; + backgroundColor.r = socket.readUInt8(); + backgroundColor.g = socket.readUInt8(); + backgroundColor.b = socket.readUInt8(); + break; + + case 32: + await socket.waitBytes(4); + dataSize += 4; + backgroundColor.r = socket.readUInt8(); + backgroundColor.g = socket.readUInt8(); + backgroundColor.b = socket.readUInt8(); + backgroundColor.a = socket.readUInt8(); + break; + } + } + + // Foreground bit + if (subEncoding & 0x04) { + switch (bitsPerPixel) { + case 8: + await socket.waitBytes(1); + const index = socket.readUInt8(); + dataSize++; + foregroundColor.r = colorMap[index].r || 255; + foregroundColor.g = colorMap[index].g || 255; + foregroundColor.b = colorMap[index].b || 255; + break; + + case 24: + await socket.waitBytes(3); + dataSize += 3; + foregroundColor.r = socket.readUInt8(); + foregroundColor.g = socket.readUInt8(); + foregroundColor.b = socket.readUInt8(); + break; + + case 32: + await socket.waitBytes(4); + dataSize += 4; + foregroundColor.r = socket.readUInt8(); + foregroundColor.g = socket.readUInt8(); + foregroundColor.b = socket.readUInt8(); + foregroundColor.a = socket.readUInt8(); + break; + } + } + + // Initialize tile with the background color + this.applyColor(tw, th, tx, ty, screenW, screenH, backgroundColor, fb); + + // AnySubrects bit + if (subEncoding & 0x08) { + await socket.waitBytes(1); + let subRects = socket.readUInt8(); + + if (subRects) { + while (subRects) { + subRects--; + const color = { r: 0, g: 0, b: 0, a: 255 }; + + // SubrectsColoured + if (subEncoding & 0x10) { + switch (bitsPerPixel) { + case 8: + await socket.waitBytes(1); + const index = socket.readUInt8(); + dataSize++; + color.r = colorMap[index].r || 255; + color.g = colorMap[index].g || 255; + color.b = colorMap[index].b || 255; + break; + + case 24: + await socket.waitBytes(3); + dataSize += 3; + color.r = socket.readUInt8(); + color.g = socket.readUInt8(); + color.b = socket.readUInt8(); + break; + + case 32: + await socket.waitBytes(4); + dataSize += 4; + color.r = socket.readUInt8(); + color.g = socket.readUInt8(); + color.b = socket.readUInt8(); + color.a = socket.readUInt8(); + break; + } + } else { + color.r = foregroundColor.r; + color.g = foregroundColor.g; + color.b = foregroundColor.b; + color.a = foregroundColor.a; + } + + await socket.waitBytes(2); + const xy = socket.readUInt8(); + const wh = socket.readUInt8(); + dataSize += 2; + + const sx = xy >> 4; + const sy = xy & 0x0f; + const sw = (wh >> 4) + 1; + const sh = (wh & 0x0f) + 1; + + this.applyColor(sw, sh, tx + sx, ty + sy, screenW, screenH, color, fb); + } + } else { + this.applyColor(tw, th, tx, ty, screenW, screenH, backgroundColor, fb); + } + } else { + this.applyColor(tw, th, tx, ty, screenW, screenH, backgroundColor, fb); + } + + lastSubEncoding = subEncoding; + } + + tiles--; + } + + rect.data = socket.readNBytes(dataSize, initialOffset); + resolve(); + }); + } + + // Apply color to a rect on buffer + applyColor(tw, th, tx, ty, screenW, screenH, color, fb) { + for (let h = 0; h < th; h++) { + for (let w = 0; w < tw; w++) { + const fbBytePosOffset = this.getPixelBytePos(tx + w, ty + h, screenW, screenH); + fb.writeUInt8(color.r || 255, fbBytePosOffset + 2); + fb.writeUInt8(color.g || 255, fbBytePosOffset + 1); + fb.writeUInt8(color.b || 255, fbBytePosOffset); + fb.writeUInt8(255, fbBytePosOffset + 3); + } + } + } +} diff --git a/qemu/src/rfb/decoders/raw.ts b/qemu/src/rfb/decoders/raw.ts new file mode 100644 index 0000000..d20fc12 --- /dev/null +++ b/qemu/src/rfb/decoders/raw.ts @@ -0,0 +1,54 @@ +export class RawDecoder { + getPixelBytePos(x, y, width, height) { + return (y * width + x) * 4; + } + + async decode(rect, fb, bitsPerPixel, colorMap, screenW, screenH, socket, depth): Promise { + return new Promise(async (resolve, reject) => { + await socket.waitBytes(rect.width * rect.height * (bitsPerPixel / 8)); + rect.data = socket.readNBytesOffset(rect.width * rect.height * (bitsPerPixel / 8)); + + for (let h = 0; h < rect.height; h++) { + for (let w = 0; w < rect.width; w++) { + const fbBytePosOffset = this.getPixelBytePos(rect.x + w, rect.y + h, screenW, screenH); + if (bitsPerPixel === 8) { + const bytePosOffset = h * rect.width + w; + const index = rect.data.readUInt8(bytePosOffset); + const color = colorMap[index]; + // RGB + // fb.writeUInt8(color?.r || 255, fbBytePosOffset); + // fb.writeUInt8(color?.g || 255, fbBytePosOffset + 1); + // fb.writeUInt8(color?.b || 255, fbBytePosOffset + 2); + + // BGR + fb.writeUInt8(color?.r || 255, fbBytePosOffset + 2); + fb.writeUInt8(color?.g || 255, fbBytePosOffset + 1); + fb.writeUInt8(color?.b || 255, fbBytePosOffset); + + fb.writeUInt8(255, fbBytePosOffset + 3); + } else if (bitsPerPixel === 24) { + const bytePosOffset = (h * rect.width + w) * 3; + fb.writeUInt8(rect.data.readUInt8(bytePosOffset), fbBytePosOffset); + fb.writeUInt8(rect.data.readUInt8(bytePosOffset + 1), fbBytePosOffset + 1); + fb.writeUInt8(rect.data.readUInt8(bytePosOffset + 2), fbBytePosOffset + 2); + fb.writeUInt8(255, fbBytePosOffset + 3); + } else if (bitsPerPixel === 32) { + const bytePosOffset = (h * rect.width + w) * 4; + // RGB + // fb.writeUInt8(rect.data.readUInt8(bytePosOffset), fbBytePosOffset); + // fb.writeUInt8(rect.data.readUInt8(bytePosOffset + 1), fbBytePosOffset + 1); + // fb.writeUInt8(rect.data.readUInt8(bytePosOffset + 2), fbBytePosOffset + 2); + + // BGR + fb.writeUInt8(rect.data.readUInt8(bytePosOffset), fbBytePosOffset + 2); + fb.writeUInt8(rect.data.readUInt8(bytePosOffset + 1), fbBytePosOffset + 1); + fb.writeUInt8(rect.data.readUInt8(bytePosOffset + 2), fbBytePosOffset); + // fb.writeUInt8(rect.data.readUInt8(bytePosOffset + 3), fbBytePosOffset + 3); + fb.writeUInt8(255, fbBytePosOffset + 3); + } + } + } + resolve(); + }); + } +} diff --git a/qemu/src/rfb/decoders/zrle.ts b/qemu/src/rfb/decoders/zrle.ts new file mode 100644 index 0000000..64f821c --- /dev/null +++ b/qemu/src/rfb/decoders/zrle.ts @@ -0,0 +1,357 @@ +import * as zlib from 'node:zlib'; +import { SocketBuffer } from '../socketbuffer.js'; + +export class ZrleDecoder { + private zlib: zlib.Inflate; + private unBuffer: SocketBuffer; + + constructor() { + this.zlib = zlib.createInflate({ chunkSize: 16 * 1024 * 1024, flush: zlib.constants.Z_FULL_FLUSH }); + this.unBuffer = new SocketBuffer(); + + this.zlib.on('data', async (chunk) => { + this.unBuffer.pushData(chunk); + }); + } + + getPixelBytePos(x, y, width, height) { + return (y * width + x) * 4; + } + + async decode(rect, fb, bitsPerPixel, colorMap, screenW, screenH, socket, depth): Promise { + return new Promise(async (resolve, reject) => { + await socket.waitBytes(4); + + const initialOffset = socket.offset; + const dataSize = socket.readUInt32BE(); + + await socket.waitBytes(dataSize); + + const compressedData = socket.readNBytesOffset(dataSize); + + rect.data = socket.readNBytes(dataSize + 4, initialOffset); + + this.unBuffer.flush(); + this.zlib.write(compressedData, async () => { + this.zlib.flush(); + + let tiles; + let totalTiles; + let tilesX; + let tilesY; + + tilesX = Math.ceil(rect.width / 64); + tilesY = Math.ceil(rect.height / 64); + tiles = tilesX * tilesY; + totalTiles = tiles; + + while (tiles) { + await this.unBuffer.waitBytes(1, 'tile begin.'); + const subEncoding = this.unBuffer.readUInt8(); + const currTile = totalTiles - tiles; + + const tileX = currTile % tilesX; + const tileY = Math.floor(currTile / tilesX); + const tx = rect.x + tileX * 64; + const ty = rect.y + tileY * 64; + const tw = Math.min(64, rect.x + rect.width - tx); + const th = Math.min(64, rect.y + rect.height - ty); + + const now = process.hrtime.bigint(); + let totalRun = 0; + let runs = 0; + + if (subEncoding === 127 || subEncoding === 129) { + console.log('Invalid subencoding. ' + subEncoding); + } else if (subEncoding === 0) { + // Raw + for (let h = 0; h < th; h++) { + for (let w = 0; w < tw; w++) { + const fbBytePosOffset = this.getPixelBytePos(tx + w, ty + h, screenW, screenH); + if (bitsPerPixel === 8) { + await this.unBuffer.waitBytes(1, 'raw 8bits'); + const index = this.unBuffer.readUInt8(); + const color = colorMap[index]; + // RGB + // fb.writeUInt8(color?.r || 255, fbBytePosOffset); + // fb.writeUInt8(color?.g || 255, fbBytePosOffset + 1); + // fb.writeUInt8(color?.b || 255, fbBytePosOffset + 2); + + // BGR + fb.writeUInt8(color?.r || 255, fbBytePosOffset + 2); + fb.writeUInt8(color?.g || 255, fbBytePosOffset + 1); + fb.writeUInt8(color?.b || 255, fbBytePosOffset); + } else if (bitsPerPixel === 24 || (bitsPerPixel === 32 && depth === 24)) { + await this.unBuffer.waitBytes(3, 'raw 24bits'); + fb.writeUInt8(this.unBuffer.readUInt8(), fbBytePosOffset + 2); + fb.writeUInt8(this.unBuffer.readUInt8(), fbBytePosOffset + 1); + fb.writeUInt8(this.unBuffer.readUInt8(), fbBytePosOffset); + } else if (bitsPerPixel === 32) { + // RGB + // fb.writeUInt8(rect.data.readUInt8(bytePosOffset), fbBytePosOffset); + // fb.writeUInt8(rect.data.readUInt8(bytePosOffset + 1), fbBytePosOffset + 1); + // fb.writeUInt8(rect.data.readUInt8(bytePosOffset + 2), fbBytePosOffset + 2); + + // BGR + await this.unBuffer.waitBytes(4, 'raw 32bits'); + fb.writeUInt8(this.unBuffer.readUInt8(), fbBytePosOffset + 2); + fb.writeUInt8(this.unBuffer.readUInt8(), fbBytePosOffset + 1); + fb.writeUInt8(this.unBuffer.readUInt8(), fbBytePosOffset); + this.unBuffer.readUInt8(); + } + // Alpha + fb.writeUInt8(255, fbBytePosOffset + 3); + } + } + } else if (subEncoding === 1) { + // Single Color + let color = { r: 0, g: 0, b: 0, a: 255 }; + if (bitsPerPixel === 8) { + await this.unBuffer.waitBytes(1, 'single color 8bits'); + const index = this.unBuffer.readUInt8(); + color = colorMap[index]; + } else if (bitsPerPixel === 24 || (bitsPerPixel === 32 && depth === 24)) { + await this.unBuffer.waitBytes(3, 'single color 24bits'); + color.r = this.unBuffer.readUInt8(); + color.g = this.unBuffer.readUInt8(); + color.b = this.unBuffer.readUInt8(); + } else if (bitsPerPixel === 32) { + await this.unBuffer.waitBytes(4, 'single color 32bits'); + color.r = this.unBuffer.readUInt8(); + color.g = this.unBuffer.readUInt8(); + color.b = this.unBuffer.readUInt8(); + color.a = this.unBuffer.readUInt8(); + } + this.applyColor(tw, th, tx, ty, screenW, screenH, color, fb); + } else if (subEncoding >= 2 && subEncoding <= 16) { + // Palette + const palette = []; + for (let x = 0; x < subEncoding; x++) { + let color; + if (bitsPerPixel === 24 || (bitsPerPixel === 32 && depth === 24)) { + await this.unBuffer.waitBytes(3, 'palette 24 bits'); + color = { + r: this.unBuffer.readUInt8(), + g: this.unBuffer.readUInt8(), + b: this.unBuffer.readUInt8(), + a: 255 + }; + } else if (bitsPerPixel === 32) { + await this.unBuffer.waitBytes(3, 'palette 32 bits'); + color = { + r: this.unBuffer.readUInt8(), + g: this.unBuffer.readUInt8(), + b: this.unBuffer.readUInt8(), + a: this.unBuffer.readUInt8() + }; + } + palette.push(color); + } + + const bitsPerIndex = subEncoding === 2 ? 1 : subEncoding < 5 ? 2 : 4; + // const i = (tw * th) / (8 / bitsPerIndex); + // const pixels = []; + + let byte; + let bitPos = 0; + + for (let h = 0; h < th; h++) { + for (let w = 0; w < tw; w++) { + if (bitPos === 0 || w === 0) { + await this.unBuffer.waitBytes(1, 'palette index data'); + byte = this.unBuffer.readUInt8(); + bitPos = 0; + } + let color; + switch (bitsPerIndex) { + case 1: + if (bitPos === 0) { + color = palette[(byte & 128) >> 7] || { r: 255, g: 255, b: 255, a: 255 }; + } else if (bitPos === 1) { + color = palette[(byte & 64) >> 6] || { r: 255, g: 255, b: 255, a: 255 }; + } else if (bitPos === 2) { + color = palette[(byte & 32) >> 5] || { r: 255, g: 255, b: 255, a: 255 }; + } else if (bitPos === 3) { + color = palette[(byte & 16) >> 4] || { r: 255, g: 255, b: 255, a: 255 }; + } else if (bitPos === 4) { + color = palette[(byte & 8) >> 3] || { r: 255, g: 255, b: 255, a: 255 }; + } else if (bitPos === 5) { + color = palette[(byte & 4) >> 2] || { r: 255, g: 255, b: 255, a: 255 }; + } else if (bitPos === 6) { + color = palette[(byte & 2) >> 1] || { r: 255, g: 255, b: 255, a: 255 }; + } else if (bitPos === 7) { + color = palette[byte & 1] || { r: 255, g: 255, b: 255, a: 255 }; + } + bitPos++; + if (bitPos === 8) { + bitPos = 0; + } + break; + + case 2: + if (bitPos === 0) { + color = palette[(byte & 196) >> 6] || { r: 255, g: 255, b: 255, a: 255 }; + } else if (bitPos === 1) { + color = palette[(byte & 48) >> 4] || { r: 255, g: 255, b: 255, a: 255 }; + } else if (bitPos === 2) { + color = palette[(byte & 12) >> 2] || { r: 255, g: 255, b: 255, a: 255 }; + } else if (bitPos === 3) { + color = palette[byte & 3] || { r: 255, g: 255, b: 255, a: 255 }; + } + bitPos++; + if (bitPos === 4) { + bitPos = 0; + } + break; + + case 4: + if (bitPos === 0) { + color = palette[(byte & 240) >> 4] || { r: 255, g: 255, b: 255, a: 255 }; + } else if (bitPos === 1) { + color = palette[byte & 15] || { r: 255, g: 255, b: 255, a: 255 }; + } + bitPos++; + if (bitPos === 2) { + bitPos = 0; + } + break; + } + const fbBytePosOffset = this.getPixelBytePos(tx + w, ty + h, screenW, screenH); + fb.writeUInt8(color.b ?? 0, fbBytePosOffset); + fb.writeUInt8(color.g ?? 0, fbBytePosOffset + 1); + fb.writeUInt8(color.r ?? 0, fbBytePosOffset + 2); + fb.writeUInt8(color.a ?? 255, fbBytePosOffset + 3); + } + } + } else if (subEncoding === 128) { + // Plain RLE + let runLength = 0; + let color = { r: 0, g: 0, b: 0, a: 0 }; + + for (let h = 0; h < th; h++) { + for (let w = 0; w < tw; w++) { + if (!runLength) { + if (bitsPerPixel === 24 || (bitsPerPixel === 32 && depth === 24)) { + await this.unBuffer.waitBytes(3, 'rle 24bits'); + color = { + r: this.unBuffer.readUInt8(), + g: this.unBuffer.readUInt8(), + b: this.unBuffer.readUInt8(), + a: 255 + }; + } else if (bitsPerPixel === 32) { + await this.unBuffer.waitBytes(4, 'rle 32bits'); + color = { + r: this.unBuffer.readUInt8(), + g: this.unBuffer.readUInt8(), + b: this.unBuffer.readUInt8(), + a: this.unBuffer.readUInt8() + }; + } + await this.unBuffer.waitBytes(1, 'rle runsize'); + let runSize = this.unBuffer.readUInt8(); + while (runSize === 255) { + runLength += runSize; + await this.unBuffer.waitBytes(1, 'rle runsize'); + runSize = this.unBuffer.readUInt8(); + } + runLength += runSize + 1; + totalRun += runLength; + runs++; + } + const fbBytePosOffset = this.getPixelBytePos(tx + w, ty + h, screenW, screenH); + fb.writeUInt8(color.b ?? 0, fbBytePosOffset); + fb.writeUInt8(color.g ?? 0, fbBytePosOffset + 1); + fb.writeUInt8(color.r ?? 0, fbBytePosOffset + 2); + fb.writeUInt8(color.a ?? 255, fbBytePosOffset + 3); + runLength--; + } + } + } else if (subEncoding >= 130) { + // Palette RLE + const paletteSize = subEncoding - 128; + const palette = []; + + for (let x = 0; x < paletteSize; x++) { + let color; + if (bitsPerPixel === 24 || (bitsPerPixel === 32 && depth === 24)) { + await this.unBuffer.waitBytes(3, 'paletterle 24bits'); + color = { + r: this.unBuffer.readUInt8(), + g: this.unBuffer.readUInt8(), + b: this.unBuffer.readUInt8(), + a: 255 + }; + } else if (bitsPerPixel === 32) { + await this.unBuffer.waitBytes(4, 'paletterle 32bits'); + color = { + r: this.unBuffer.readUInt8(), + g: this.unBuffer.readUInt8(), + b: this.unBuffer.readUInt8(), + a: this.unBuffer.readUInt8() + }; + } + palette.push(color); + } + + let runLength = 0; + let color = { r: 0, g: 0, b: 0, a: 255 }; + + for (let h = 0; h < th; h++) { + for (let w = 0; w < tw; w++) { + if (!runLength) { + await this.unBuffer.waitBytes(1, 'paletterle indexdata'); + const colorIndex = this.unBuffer.readUInt8(); + + if (!(colorIndex & 128)) { + // Run de tamanho 1 + color = palette[colorIndex] ?? { r: 0, g: 0, b: 0, a: 255 }; + runLength = 1; + } else { + color = palette[colorIndex - 128] ?? { r: 0, g: 0, b: 0, a: 255 }; + await this.unBuffer.waitBytes(1, 'paletterle runlength'); + let runSize = this.unBuffer.readUInt8(); + while (runSize === 255) { + runLength += runSize; + await this.unBuffer.waitBytes(1, 'paletterle runlength'); + runSize = this.unBuffer.readUInt8(); + } + runLength += runSize + 1; + } + totalRun += runLength; + runs++; + } + const fbBytePosOffset = this.getPixelBytePos(tx + w, ty + h, screenW, screenH); + fb.writeUInt8(color.b ?? 0, fbBytePosOffset); + fb.writeUInt8(color.g ?? 0, fbBytePosOffset + 1); + fb.writeUInt8(color.r ?? 0, fbBytePosOffset + 2); + fb.writeUInt8(color.a ?? 255, fbBytePosOffset + 3); + runLength--; + } + } + } + // 127 and 129 are not valid + // 17 to 126 are not used + + tiles--; + } + + this.unBuffer.flush(); + resolve(); + }); + }); + } + + // Apply color to a rect on buffer + applyColor(tw, th, tx, ty, screenW, screenH, color, fb) { + for (let h = 0; h < th; h++) { + for (let w = 0; w < tw; w++) { + const fbBytePosOffset = this.getPixelBytePos(tx + w, ty + h, screenW, screenH); + fb.writeUInt8(color.b || 255, fbBytePosOffset); + fb.writeUInt8(color.g || 255, fbBytePosOffset + 1); + fb.writeUInt8(color.r || 255, fbBytePosOffset + 2); + fb.writeUInt8(255, fbBytePosOffset + 3); + } + } + } +} diff --git a/qemu/src/rfb/socketbuffer.ts b/qemu/src/rfb/socketbuffer.ts new file mode 100644 index 0000000..519b195 --- /dev/null +++ b/qemu/src/rfb/socketbuffer.ts @@ -0,0 +1,132 @@ +/// this is a pretty poor name. +export class SocketBuffer { + public buffer?: Buffer; + private offset: number; + + constructor() { + this.flush(); + } + + flush(keep = true) { + if (keep && this.buffer?.length) { + this.buffer = this.buffer.subarray(this.offset); + this.offset = 0; + } else { + this.buffer = Buffer.from([]); + this.offset = 0; + } + } + + toString() { + return this.buffer.toString(); + } + + includes(check) { + return this.buffer.includes(check); + } + + pushData(data) { + this.buffer = Buffer.concat([this.buffer, data]); + } + + readInt32BE() { + const data = this.buffer.readInt32BE(this.offset); + this.offset += 4; + return data; + } + + readInt32LE() { + const data = this.buffer.readInt32LE(this.offset); + this.offset += 4; + return data; + } + + readUInt32BE() { + const data = this.buffer.readUInt32BE(this.offset); + this.offset += 4; + return data; + } + + readUInt32LE() { + const data = this.buffer.readUInt32LE(this.offset); + this.offset += 4; + return data; + } + + readUInt16BE() { + const data = this.buffer.readUInt16BE(this.offset); + this.offset += 2; + return data; + } + + readUInt16LE() { + const data = this.buffer.readUInt16LE(this.offset); + this.offset += 2; + return data; + } + + readUInt8() { + const data = this.buffer.readUInt8(this.offset); + this.offset += 1; + return data; + } + + readInt8() { + const data = this.buffer.readInt8(this.offset); + this.offset += 1; + return data; + } + + readNBytes(bytes, offset = this.offset) { + return this.buffer.slice(offset, offset + bytes); + } + + readNBytesOffset(bytes) { + const data = this.buffer.slice(this.offset, this.offset + bytes); + this.offset += bytes; + return data; + } + + setOffset(n) { + this.offset = n; + } + + bytesLeft() { + return this.buffer.length - this.offset; + } + + // name is nullable because there are Many(yay....) times it just isn't passed + waitBytes(bytes, name: any | null = null): Promise { + if (this.bytesLeft() >= bytes) { + return; + } + let counter = 0; + return new Promise(async (resolve, reject) => { + while (this.bytesLeft() < bytes) { + counter++; + // console.log('Esperando. BytesLeft: ' + this.bytesLeft() + ' Desejados: ' + bytes); + await this.sleep(4); + if (counter === 50) { + console.log('Stucked on ' + name + ' - Buffer Size: ' + this.buffer.length + ' BytesLeft: ' + this.bytesLeft() + ' BytesNeeded: ' + bytes); + } + } + resolve(); + }); + } + + fill(data) { + this.buffer.fill(data, this.offset, this.offset + data.length); + this.offset += data.length; + } + + fillMultiple(data, repeats) { + this.buffer.fill(data, this.offset, this.offset + data.length * repeats); + this.offset += data.length * repeats; + } + + sleep(n): Promise { + return new Promise((resolve, reject) => { + setTimeout(resolve, n); + }); + } +} diff --git a/qemu/tsconfig.json b/qemu/tsconfig.json new file mode 100644 index 0000000..e52e140 --- /dev/null +++ b/qemu/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig-base.json", + "compilerOptions": { + "composite": true, + "outDir": "./dist", + "rootDir": "." + }, + "references": [ + ] +} \ No newline at end of file diff --git a/shared/package.json b/shared/package.json new file mode 100644 index 0000000..d3ed648 --- /dev/null +++ b/shared/package.json @@ -0,0 +1,18 @@ +{ + "name": "@socketcomputer/shared", + "version": "1.0.0", + "private": "true", + "description": "crusttest shared bits", + "type": "module", + "main": "dist/src/index.js", + "files": [ + "dist" + ], + + "devDependencies": {}, + "scripts": { + "build": "tsc" + }, + "author": "", + "license": "ISC" +} diff --git a/shared/src/Protocol.ts b/shared/src/Protocol.ts new file mode 100644 index 0000000..f929b11 --- /dev/null +++ b/shared/src/Protocol.ts @@ -0,0 +1,422 @@ +import { Struct } from './Struct.js'; + +export enum MessageType { + // control + Key, + Mouse, + Turn, + + // Display/audio + DisplayRect, + DisplaySize, // display changed size + + // chat + Chat, + ChatHistory, + + // user + AddUser, + RemUser, + + // currently aren't used + RenUser, + Rename, +} + +export enum MouseButtons { + Left = 1 << 0, + Right = 1 << 1, + Middle = 1 << 2, + WheelUp = 1 << 3, + WheelDn = 1 << 4 +} + +export enum RenameResult { + Ok, + InvalidUsername, /// This username is too long or otherwise invalid + UsernameTaken /// This username is taken on this slot. +} + + + +export const kMaxUserNameLength = 24; +export const kMaxChatMessageLength = 150; + +/// This is a 16-bit value, sort of a structure if you will. +/// 0x55 is the actual magic value. +/// 0x[vv] is the protocol version. +/// +/// Any bumps to fields which are incompatible WILL require bumping the version field +/// to avoid older clients accidentally getting data they can't handle. +const kProtocolMagic = 0x5501; + +export type ProtocolHeader = { + magic: number; + type: number; + payloadSize: number; +}; + +export type ChatMessageObject = { + username: string; + message: string; +}; + +// Messages + +export type DeserializedMessageRoot = { type: MessageType }; + +export type KeyMessage = DeserializedMessageRoot & { + keysym: number; + pressed: boolean; +}; + +export type MouseMessage = DeserializedMessageRoot & { + x: number; + y: number; + buttons: MouseButtons; // Actually a bitmask +}; + +export type TurnMessage = DeserializedMessageRoot & {}; + +export type TurnServerMessage = DeserializedMessageRoot & { + time: number; + turnQueue: string[]; +}; + +// This is using browser types for simplicity's sake +export type DisplayRectMessage = DeserializedMessageRoot & { + x: number; + y: number; + data: ArrayBuffer; +}; + +export type DisplaySizeMessage = DeserializedMessageRoot & { + width: number; + height: number; +}; + +export type ChatMessage = DeserializedMessageRoot & { + message: string; +}; + +export type ChatServerMessage = DeserializedMessageRoot & ChatMessageObject; + +export type ChatHistoryMessage = DeserializedMessageRoot & { + history: ChatMessageObject[]; +}; + +export type AddUserMessage = DeserializedMessageRoot & { + user: string; +}; + +export type RemUserMessage = DeserializedMessageRoot & { + user: string; +}; + +export type RenUserMessage = DeserializedMessageRoot & { + prevUsername: string; + newUsername: string; +}; + +export type RenameMessage = DeserializedMessageRoot & { + newUsername: string; +}; + +export type RenameServerMessage = DeserializedMessageRoot & { + result: RenameResult; +}; + + +export type AnyMessage = + | KeyMessage + | MouseMessage + | TurnMessage + | TurnServerMessage + | DisplayRectMessage + | DisplaySizeMessage + | ChatMessage + | ChatServerMessage + | ChatHistoryMessage + | AddUserMessage + | RemUserMessage + | RenUserMessage + | RenameMessage + | RenameServerMessage; + +export type DeserializedMessage = AnyMessage; + +export const kProtocolHeaderSize = 8; + +export class MessageEncoder { + private struct: Struct; + private buffer: ArrayBuffer; + + Init(byteSize) { + this.buffer = new ArrayBuffer(byteSize + kProtocolHeaderSize); + this.struct = new Struct(this.buffer); + this.InitHeader(); + } + + SetKeyMessage(keysym, pressed) { + this.SetTypeCode(MessageType.Key); + this.struct.WriteU16(keysym); + this.struct.WriteU8(pressed == true ? 1 : 0); + } + + SetMouseMessage(x, y, buttons: MouseButtons) { + this.SetTypeCode(MessageType.Mouse); + this.struct.WriteU16(x); + this.struct.WriteU16(y); + this.struct.WriteU8(buttons); + } + + SetTurnMessage() { + this.SetTypeCode(MessageType.Turn); + } + + SetTurnSrvMessage(ms, usersQueue) { + this.SetTypeCode(MessageType.Turn); + this.struct.WriteU32(ms); + this.struct.WriteArray(usersQueue, this.struct.WriteString); + } + + SetDisplayRectMessage(x, y, buffer: ArrayBuffer) { + this.SetTypeCode(MessageType.DisplayRect); + this.struct.WriteU16(x); + this.struct.WriteU16(y); + this.struct.WriteBuffer(buffer); + } + + SetDisplaySizeMessage(w, h) { + this.SetTypeCode(MessageType.DisplaySize); + this.struct.WriteU16(w); + this.struct.WriteU16(h); + } + + SetChatMessage(msg) { + this.SetTypeCode(MessageType.Chat); + this.struct.WriteStringLen(msg, kMaxChatMessageLength); + } + + SetChatSrvMessage(user: string, msg: string) { + this.SetTypeCode(MessageType.Chat); + this.struct.WriteStringLen(user, kMaxUserNameLength); + this.struct.WriteStringLen(msg, kMaxChatMessageLength); + } + + SetChatHistoryMessage(history: ChatMessageObject[]) { + this.SetTypeCode(MessageType.ChatHistory); + this.struct.WriteArray(history, this.AddChatSrvMessage); + } + + SetAddUserMessage(user: string) { + this.SetTypeCode(MessageType.AddUser); + this.struct.WriteString(user); + } + + SetRemUserMessage(user: string) { + this.SetTypeCode(MessageType.AddUser); + this.struct.WriteString(user); + } + + SetRenUserMessage(prevUsername: string, newUsername: string) { + this.SetTypeCode(MessageType.RenUser); + this.struct.WriteStringLen(prevUsername, kMaxUserNameLength); + this.struct.WriteStringLen(newUsername, kMaxUserNameLength); + } + + SetRenameMessage(username: string) { + this.SetTypeCode(MessageType.Rename); + this.struct.WriteStringLen(username, kMaxUserNameLength); + } + + SetRenameServerMessage(result: RenameResult) { + this.SetTypeCode(MessageType.Rename); + this.struct.WriteU8(result); + } + + // Setup some stuff and then return the final message + Finish() { + let endOffset = this.struct.Tell(); + this.struct.Seek(4); // seek to size offset + this.struct.WriteU32(endOffset - kProtocolHeaderSize); + this.struct.Seek(endOffset); + return this.buffer.slice(0, endOffset); + } + + private InitHeader() { + this.struct.Seek(0); + this.struct.WriteU16(kProtocolMagic); + this.struct.WriteU16(0); // No message type yet + this.struct.WriteU32(0); // No payload size yet + } + + private SetTypeCode(type: MessageType) { + let oldOff = this.struct.Tell(); + this.struct.Seek(2); // seek to type offset + this.struct.WriteU16(type); + this.struct.Seek(oldOff); + } + + // TODO rename to AddChatMessageObject. lazy + private AddChatSrvMessage(message: ChatMessageObject) { + this.struct.WriteStringLen(message.username, kMaxUserNameLength); + this.struct.WriteStringLen(message.message, kMaxChatMessageLength); + } +} + +// goofy, but.. reentrancy! +class MessageDecoderExtContext { + public struct: Struct; + + ReadChatSrvMessage() { + let msg: ChatMessageObject; + msg.username = this.struct.ReadStringLen(kMaxUserNameLength); + msg.message = this.struct.ReadStringLen(kMaxChatMessageLength); + } +} + +export class MessageDecoder { + + static async ReadMessage(buffer: ArrayBuffer, asClient: boolean): Promise { + return new Promise((res, rej) => { + MessageDecoder.ReadMessageSync(buffer, asClient, (err: Error, message: DeserializedMessage) => { + if (err) rej(err); + res(message); + }); + }); + } + + private static ReadMessageSync(buffer: ArrayBuffer, asClient: boolean, callback: (err: Error, message:DeserializedMessage) => void) { + let struct = new Struct(buffer); + let context = new MessageDecoderExtContext(); + context.struct = struct; + + // Read and verify the header + let header: ProtocolHeader = { + magic: struct.ReadU16(), + type: struct.ReadU16() as MessageType, + payloadSize: struct.ReadU32() + }; + + if (header.magic !== kProtocolMagic) return callback(new Error('Invalid protocol message'), null); + + if(header.payloadSize > buffer.byteLength) + return callback(new Error('invalid header'), null); + + let message: DeserializedMessage = { + type: header.type + }; + + switch (header.type) { + case MessageType.Key: + if (asClient) { + return callback(new Error('unexpected server->client message'), null); + } + (message as KeyMessage).keysym = struct.ReadU16(); + (message as KeyMessage).pressed = struct.ReadU8() == 1 ? true : false; + return callback(null, message); + break; + + case MessageType.Mouse: + if (asClient) { + return callback(new Error('unexpected server->client message'), null); + } + (message as MouseMessage).x = struct.ReadU16(); + (message as MouseMessage).y = struct.ReadU16(); + (message as MouseMessage).buttons = struct.ReadU8() as MouseButtons; + return callback(null, message); + + case MessageType.Turn: + if (asClient) { + // the server->client version of this message contains fields + (message as TurnServerMessage).time = struct.ReadU32(); + (message as TurnServerMessage).turnQueue = struct.ReadArray(struct.ReadString); + } + return callback(null, message); + break; + + case MessageType.DisplayRect: + if (asClient) { + (message as DisplayRectMessage).x = struct.ReadU16(); + (message as DisplayRectMessage).y = struct.ReadU16(); + (message as DisplayRectMessage).data = struct.ReadBuffer(); + return callback(null, message); + } else { + return callback(new Error('unexpected client->server message'), null); + } + break; + + case MessageType.DisplaySize: + if (asClient) { + (message as DisplaySizeMessage).width = struct.ReadU16(); + (message as DisplaySizeMessage).height = struct.ReadU16(); + } else { + return callback(new Error('unexpected client->server message'), null); + } + return callback(null, message); + break; + + case MessageType.Chat: + if (asClient) { + (message as unknown as ChatMessageObject).username = struct.ReadStringLen(kMaxUserNameLength); + (message as unknown as ChatMessageObject).message = struct.ReadStringLen(kMaxChatMessageLength); + } else { + // the client->server version of this message only has the content + (message as ChatMessage).message = struct.ReadStringLen(kMaxChatMessageLength); + } + return callback(null, message); + break; + + case MessageType.ChatHistory: + if (asClient) { + (message as ChatHistoryMessage).history = struct.ReadArray(context.ReadChatSrvMessage); + return callback(null, message); + } else { + return callback(new Error('unexpected client->server message'), null); + } + break; + + case MessageType.AddUser: + if (asClient) { + (message as AddUserMessage).user = struct.ReadString(); + return callback(null, message); + } else { + return callback(new Error('unexpected client->server message'), null); + } + + case MessageType.RemUser: + if (asClient) { + (message as RemUserMessage).user = struct.ReadString(); + return callback(null, message); + } else { + return callback(new Error('unexpected client->server message'), null); + } + break; + + case MessageType.RenUser: + if (asClient) { + (message as RenUserMessage).prevUsername = struct.ReadStringLen(kMaxUserNameLength); + (message as RenUserMessage).newUsername = struct.ReadStringLen(kMaxUserNameLength); + return callback(null, message); + } else { + return callback(new Error('unexpected client->server message'), null); + } + break; + + case MessageType.Rename: + if (asClient) { + (message as RenameServerMessage).result = struct.ReadU8() as RenameResult; + } else { + (message as RenameMessage).newUsername = struct.ReadStringLen(kMaxUserNameLength); + } + return callback(null, message); + break; + + default: + return callback(new Error(`unknown type code ${header.type}`), null); + } + } + + +} diff --git a/shared/src/Struct.ts b/shared/src/Struct.ts new file mode 100644 index 0000000..eb03187 --- /dev/null +++ b/shared/src/Struct.ts @@ -0,0 +1,162 @@ +/// Helper class for reading structured binary data. +export class Struct { + private byteOffset = 0; + private buffer: ArrayBuffer; + private dv: DataView; + + constructor(buffer: ArrayBuffer) { + this.dv = new DataView(buffer); + this.buffer = buffer; + } + + Seek(off: number) { + // sanity checking should be added later + this.byteOffset = off; + } + + Tell(): number { + return this.byteOffset; + } + + // Reader functions + + ReadArray(functor: () => any) { + let len = this.ReadU32(); + let arr: any = []; + for (let i = 0; i < len; ++i) arr.push(functor.call(this)); + return arr; + } + + ReadString() { + let len = this.ReadU32(); + let s = ''; + + for (let i = 0; i < len; ++i) s += String.fromCharCode(this.ReadU16()); + + return s; + } + + ReadStringLen(maxStringLength) { + let stringLength = this.ReadU32(); + let s = ''; + + if(maxStringLength > stringLength) + throw new Error(`string length ${stringLength} is past the max ${maxStringLength}`); + + for (let i = 0; i < stringLength; ++i) + s += String.fromCharCode(this.ReadU16()); + + return s; + } + + ReadU8() { + let i = this.dv.getUint8(this.byteOffset); + this.byteOffset++; + return i; + } + + ReadS8() { + let i = this.dv.getInt8(this.byteOffset); + this.byteOffset++; + return i; + } + + ReadU16() { + let i = this.dv.getUint16(this.byteOffset, false); + this.byteOffset += 2; + return i; + } + + ReadS16() { + let i = this.dv.getInt16(this.byteOffset, false); + this.byteOffset += 2; + return i; + } + + ReadU32() { + let i = this.dv.getUint32(this.byteOffset, false); + this.byteOffset += 4; + return i; + } + + ReadS32() { + let i = this.dv.getInt32(this.byteOffset, false); + this.byteOffset += 4; + return i; + } + + ReadBuffer() { + let count = this.ReadU32(); + let buf = this.buffer.slice(this.byteOffset, this.byteOffset + count); + this.byteOffset += count; + return buf; + } + + // Writer functions + // TODO: let these grow!. we can do that by just allocating a new buffer + // then copying + + // Add an array with a fixed type + WriteArray(arr, functor) { + this.WriteU32(arr.length); + for (let elem of arr) functor.call(this, elem); + } + + // Add a pascal UTF-16 string. + WriteString(str) { + this.WriteU32(str.length); + for (let i in str) this.WriteU16(str.charCodeAt(i)); + } + + // writes a string with a max length. + // will trim strings that are too long + WriteStringLen(str, len) { + let length = len; + + // pick the string length, but ONLY + // if it is smaller or equal to our + // max bound lenfth + if(len <= str.length) + length = str.length; + + this.WriteU32(length); + + for (let i = 0; i < length; ++i) + this.WriteU16(str.charCodeAt(i)); + } + + WriteU8(i) { + this.dv.setUint8(this.byteOffset, i); + this.byteOffset++; + } + + WriteS8(i) { + this.dv.setInt8(this.byteOffset, i); + this.byteOffset++; + } + + WriteU16(i) { + this.dv.setUint16(this.byteOffset, i, false); + this.byteOffset += 2; + } + + WriteS16(i) { + this.dv.setInt16(this.byteOffset, i, false); + this.byteOffset += 2; + } + + WriteU32(i) { + this.dv.setUint32(this.byteOffset, i, false); + this.byteOffset += 4; + } + + WriteS32(i) { + this.dv.setInt32(this.byteOffset, i, false); + this.byteOffset += 4; + } + + WriteBuffer(buffer: ArrayBuffer) { + this.WriteU32(buffer.byteLength); + for (let i = 0; i < buffer.byteLength; ++i) this.WriteU8(buffer[i]); + } +} diff --git a/shared/src/UsernameValidator.ts b/shared/src/UsernameValidator.ts new file mode 100644 index 0000000..137cd57 --- /dev/null +++ b/shared/src/UsernameValidator.ts @@ -0,0 +1,9 @@ +import { kMaxUserNameLength, RenameResult } from './Protocol.js'; + +// This function simply validates that the username is ok to set. if it's not +// then we don't even bother checking if the user already exists, in the backend +// code we'll just give up. +export function ValidateUsername(username: string): RenameResult { + if (!/^[a-zA-Z0-9\ \-\_\.]+$/.test(username) || username.length > kMaxUserNameLength || username.trim().length < 3) return RenameResult.InvalidUsername; + return RenameResult.Ok; +} diff --git a/shared/src/index.ts b/shared/src/index.ts new file mode 100644 index 0000000..a6820d2 --- /dev/null +++ b/shared/src/index.ts @@ -0,0 +1,2 @@ +export * from './Protocol.js'; +export * from './UsernameValidator.js'; diff --git a/shared/test/Protocol.test.ts b/shared/test/Protocol.test.ts new file mode 100644 index 0000000..60e4c8c --- /dev/null +++ b/shared/test/Protocol.test.ts @@ -0,0 +1,77 @@ +import { MessageDecoder, MessageEncoder } from '../src/Protocol.js'; + +function buf2hex(buffer) { + // buffer is an ArrayBuffer + return [...new Uint8Array(buffer)].map((x) => x.toString(16).padStart(2, '0')).join(' '); +} + +async function decodeMessage(buf, isClient) { + // Let's try decoding this message + console.log('Got message:', await MessageDecoder.ReadMessage(buf, isClient)); +} + +function encodeMessage(f: (encoder: MessageEncoder) => void) { + var encoder = new MessageEncoder(); + encoder.Init(4096); + + f(encoder); + + var buf = encoder.Finish(); + console.log('the buffer of this message:', buf2hex(buf)); + return buf; +} + +(async () => { + console.log('[CLIENT TESTS]'); + await decodeMessage( + encodeMessage((encoder) => { + encoder.SetChatMessage('Hi, CrustTest World!'); + }), + false + ); + + await decodeMessage( + encodeMessage((encoder) => { + encoder.SetTurnMessage(); + }), + false + ); + + console.log('[END CLIENT TESTS]'); + + console.log('[SERVER TESTS]'); + + // server->client tests + await decodeMessage( + encodeMessage((encoder) => { + encoder.SetChatSrvMessage('tester19200', 'Hi, CrustTest World!'); + }), + true + ); + + await decodeMessage( + encodeMessage((encoder) => { + encoder.SetTurnSrvMessage(1000, ['tester19200', 'tester59340', 'tester10000', 'tester1337']); + }), + true + ); + + console.log('[END SERVER TESTS]'); + + console.log('[BEGIN THINGS THAT SHOULD INTENTIONALLY NOT WORK]'); + + try { + let r = await decodeMessage( + encodeMessage((encoder) => { + encoder.SetDisplaySizeMessage(1000, 1000); + }), + false // the client wouldn't usually send a display size message.. + ); + + console.log(`wait uhh.. how'd that work?`, r); + } catch (err) { + console.log(`ok cool, that returns this error: ${err}`); + } + + console.log('[END THINGS THAT SHOULD INTENTIONALLY NOT WORK]'); +})(); diff --git a/shared/tsconfig.json b/shared/tsconfig.json new file mode 100644 index 0000000..bb8e99b --- /dev/null +++ b/shared/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../tsconfig-base.json", + "compilerOptions": { + "composite": true, + "outDir": "./dist", + "rootDir": "." + } +} \ No newline at end of file diff --git a/tsconfig-base.json b/tsconfig-base.json new file mode 100644 index 0000000..626c87d --- /dev/null +++ b/tsconfig-base.json @@ -0,0 +1,13 @@ +// This is the base tsconfig +{ + "compilerOptions": { + "target": "ES2021", + "module": "ES2020", + "moduleResolution": "Node", + "declaration": true, + "sourceMap": true, + + // more compiler options? + "strict": false, // maybe enable later..? + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..5295cb9 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "files": [], + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "noEmitOnError": true, + "skipLibCheck": true, + "baseUrl": "./", + + "paths": { + "@socketcomputer/shared*": [ "shared/src/*" ], + "@socketcomputer/backend*": [ "backend/src/*" ], + "@socketcomputer/qemu*": [ "qemu/src/*" ] + }, + }, + + "exclude": [ + "node_modules", + "dist" + ], + + "references": [ + { "path": "./backend" }, + { "path": "./qemu" }, + { "path": "./shared" }, + ] +} diff --git a/webapp/package.json b/webapp/package.json new file mode 100644 index 0000000..99e6fb7 --- /dev/null +++ b/webapp/package.json @@ -0,0 +1,20 @@ +{ + "name": "@socketcomputer/webapp", + "version": "1.0.0", + "private": "true", + "description": "", + "scripts": { + "serve": "parcel src/index.html", + "build": "parcel build --dist-dir dist --public-url '.' src/index.html" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@parcel/transformer-sass": "^2.12.0", + "parcel": "^2.12.0", + "typescript": "^5.4.3" + }, + "dependencies": { + "nanoevents": "^9.0.0" + } +} diff --git a/webapp/src/css/main.css b/webapp/src/css/main.css new file mode 100644 index 0000000..b570366 --- /dev/null +++ b/webapp/src/css/main.css @@ -0,0 +1,107 @@ +html, body { + margin: 0; + padding: 0; + } + + body { + width: 100%; + height: 100%; + background-color: #fff; + font-family: Helvetica, sans-serif; + } + + #window-chrome { + display: none; + } + + img { + user-drag: none; + -moz-user-drag: none; + -webkit-user-drag: none; + } + + #xp-window { + /*cursor: none; /* let windows do that ;) */ + background: url(../../static/macbook.png) no-repeat center; + background-size: 100% 100%; + width: 690px; + height: 460px; + margin: auto; + text-align: center; + } + + .xp-image, .xp-image img, .xp-image canvas { + width: 500px; + height: 375px; + display: inline-block; + } + + .xp-image { + margin-top: 38px; + } + + .focused img, .focused canvas { + box-shadow: 0px 0px 9px 0px rgba(45, 213, 255, 0.75); + -moz-box-shadow: 0px 0px 9px 0px rgba(45, 213, 255, 0.75); + -webkit-box-shadow: 0px 0px 9px 0px rgba(45, 213, 255, 0.75); + } + + .waiting img, .waiting canvas { + box-shadow: 0px 0px 9px 0px rgba(242, 255, 63, 0.75); + -moz-box-shadow: 0px 0px 9px 0px rgba(242, 255, 63, 0.75); + -webkit-box-shadow: 0px 0px 9px 0px rgba(242, 255, 63, 0.75); + } + + .turn-timer { + font-size: 13px; + color: #999; + padding: 10px 0; + text-align: center; + } + + .user-count-wrapper { + text-align: center; + position: absolute; + top: 10px; + } + + .user-count { + background-color: #FFFF66; + padding: 4px 12px; + font-size: 12px; + color: #906F95; + } + + .count { + color: #61226B; + padding-right: 12px; + font-weight: 500; + } + + @media only screen and (min-width : 320px) and (max-width : 480px), (max-width : 568px) { + #xp-window { + background: none; + height: auto; + width: auto; + } + + .xp-image { + padding: 5px; + background: #000; + } + + .xp-image, .xp-image img, .xp-image canvas { + width: 310px; + margin-top: 0; + height: 234px; + display: block; + } + + .user-count-wrapper { + position: relative; + } + + .count { + padding: 0; + } + } diff --git a/webapp/src/index.html b/webapp/src/index.html new file mode 100644 index 0000000..94da2fa --- /dev/null +++ b/webapp/src/index.html @@ -0,0 +1,34 @@ + + + + socket.io makes computers + + + + + + +

Click the screen to request a turn and control the computer!

+
+ +
+ +
+ +
+
+
+ + Users logged in: + +
+ +

+ To avoid misusage, we re-snapshot the computer every 15 minutes! [Source code] +

+ + +

Not Powered by Socket.IO (Don't use this!!)

+ + + diff --git a/webapp/src/index.ts b/webapp/src/index.ts new file mode 100644 index 0000000..6a54bce --- /dev/null +++ b/webapp/src/index.ts @@ -0,0 +1,124 @@ + +import * as Shared from '@socketcomputer/shared'; + +type user = { + username: string + queuePos: number +}; + +// client for +class client { + private websocket: WebSocket = null; + private url = ""; + private userList = new Array(); + private canvas:HTMLCanvasElement = null; + private canvasCtx : CanvasRenderingContext2D = null; + + + constructor(url: string, canvas: HTMLCanvasElement) { + this.url = url + this.canvas = canvas; + this.canvasCtx = canvas.getContext('2d'); + } + + public connect() { + this.websocket = new WebSocket(this.url); + this.websocket.binaryType = 'arraybuffer'; // its 2024 people. + this.websocket.addEventListener('open', this.onWsOpen.bind(this)); + this.websocket.addEventListener('message', this.onWsMessage.bind(this)); + this.websocket.addEventListener('close', this.onWsClose.bind(this)); + } + + private onWsOpen() { + console.log("client WS OPEN!!"); + } + + private async onWsMessage(e: MessageEvent) { + // no guacmoale or shity fucking sockret io here. + if(typeof(e.data) == "string") + return; + + try { + let message = await Shared.MessageDecoder.ReadMessage(e.data as ArrayBuffer, true); + + switch(message.type) { + case Shared.MessageType.DisplaySize: + this.resizeDisplay((message as Shared.DisplaySizeMessage)); + break; + case Shared.MessageType.DisplayRect: + this.drawRects((message as Shared.DisplayRectMessage)); + break; + + case Shared.MessageType.AddUser: + this.addUser((message as Shared.AddUserMessage)); + break; + case Shared.MessageType.RemUser: + this.remUser((message as Shared.RemUserMessage)); + break; + + default: + console.log(`Unhandled message type ${message.type}`); + break; + } + } catch(e) { + console.log("Is not works..", e) + } + } + + private onWsClose() { + // backoff? + console.log("FUCK YOU FUCKING FUCKER GOOGLE."); + this.websocket.removeEventListener("open", this.onWsOpen); + this.websocket.removeEventListener("message", this.onWsMessage); + this.websocket.removeEventListener("close", this.onWsClose); + this.websocket = null; + } + + private resizeDisplay(message: Shared.DisplaySizeMessage) { + console.log("resizes to", message.width, message.height) + this.canvas.width = message.width; + this.canvas.height = message.height; + } + + private updateUserCount() { + let elem : HTMLSpanElement = document.getElementById("count") as HTMLSpanElement; + elem.innerText = this.userList.length.toString(); + } + + private addUser(message: Shared.AddUserMessage) { + this.userList.push({ + username: message.user, + queuePos: -1 + }); + + this.updateUserCount(); + } + + private remUser(message: Shared.RemUserMessage) { + let index = this.userList.findIndex((u) => { + return u.username == message.user; + }); + + if(index !== -1) + this.userList.splice(index, 1); + + this.updateUserCount(); + } + + private drawRects(message: Shared.DisplayRectMessage) { + let blob = new Blob([message.data]); + createImageBitmap(blob) + .then((image) => { + this.canvasCtx.drawImage(image, message.x, message.y); + }).catch((err) => { + console.error(`Fuck error decode rect...`, err); + }); + } +} + +let globalclient = null; + +document.addEventListener("DOMContentLoaded", async () => { + globalclient = new client("ws://127.0.0.1:4050", document.getElementById("xp-canvas") as HTMLCanvasElement); + globalclient.connect(); +}) diff --git a/webapp/static/macbook.png b/webapp/static/macbook.png new file mode 100644 index 0000000000000000000000000000000000000000..40511b47a763abd4ff0e0cffe05edad494177861 GIT binary patch literal 124659 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

Gu=uW+|FQO6)WMQMf_aVB- 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�YEMHl2yz^;c@eA{P_?0UOCm<4gCv<()lutv8I^w%|BZVmr*7u&>l7S55Z z2yZ59&u$EsIB8^Ku~wmL`LN; z?*QNIn(Zpw-YDNR<)@60cA|BDcO*dX)HK4pWBZIVG{KolfN$X|b=&JKzJH8vd+7J1 z_5r=fjkmu$cUq#S0R@!?3CahgNF+Gdq8jFGoU1h24~{)S7XtM9?(D)@z(9H;fJYU2 zPppAL3&WxhsZlH4{IxLRan9~CV%K@~TpOS&n+eKj4#}5@yLa{%r*JbGDU53{JN5EO zgeWK4j}m^8xK{I@9Q9OmQ39R~2WZBE2YckiO?|Vga>pkhCDeJ7UQTrraosh>TGL8AUg0aLp$4S= zE#9|BXVsn&&L6K-&jXhS9*0Tn$8z{{Je!^kjX^EonwzZSjZd>L%zJ*qlieF7-!eIx zoRqP0Hl?=$Q12`>(FDYTLMle_L0?HaaLvEg9d15T=I(V^&+V=- zYaqr2dwXPgVUkw)K+I{_j62K=>-8UCIHhI$#CCfVxy7|Vj6~1gJt0{E!a}bteoj-+B~;X z2RV*9_TfpQ#IZIzFJm+qf7dbow`7A1_2R3g4@g>8boFt5(ooW4gP;cud~x0xiE;Wp z)@M#?1%eQSv(mV&Xhx#0j7i!ZC0Q}l*!ay|35%>wpE8e3ek@AL6sXPMNSZ0gvKuWj zugRH4pivj|P+dhz(?0V~E<;y|>6mxaKN|QwR6t6b_P>!w%)HXRq%yMy@zUHCs+0JF zz;r?>NpA|aUQk(?FFrpvn+lh>bMxUoBkJX3nFx+kGw{rMi@;I$T)=)1ak zm#DzsIXO*+j+@j7jGBk5Y@%(z1cu6{ZFq~<$2*QvnsGST#zorynravHs9TNsfx!Hu zij*Rm8I)^1zfZcyYBYwO`}}A_&5%8{=A%mYW;M@Yil^1hOU>eJ^5J@=HNuL|ku$kg zu((}z2#T|*H)v5NYG9i1z1QW{gn(&(Y}il14usGr96 zUP9|MAujpTdNi7&oXd9()Aj%iun-Htf{F zgO8^iT~Ken<}pX%b>d6p4)7mmW`6i+`Jl&}nB{FL&!RIS^ncED0BBl}r`!kjT>I4D z$^8Kkr|JDxw**R{EG1I79vP5BwNy`CwQuumXZrRyl?L)qc*39wCaUc4wwCQAJBK}U zo0~r`V<599E0PKjdWC*Aaq#osr$~JlSW+n=Nc4-HjdavYzv5wiE*!eLwz{rD^t8Gh zWx^hM>AiD|ky|Wtg~tkGHTw&@0awKWDFAuh+39MrP|^@(Yl}F>-q6s8BRr|_3C|j= z2%BFge63fu=}xXnelrq2S@_-}uZM_!&^XdR$Q?$%RIHhP>?AfZ=md3da0_`2)5y*Q zHs6YKsCa@n!gc*q#+d51+{)E;tLS3>LD$J~1MJzNxCYj|uTP!&nUMQoynZi}^o8k1 zjtTb4PR{cVC!X)DG}!2Er#cQH*vc=PwxnlzaV*YOOh;n82<|;Z%%T(~eEr_-vZCBH z{SoV_hpX1fNdrd`YatGur^3^ZF&=6^DFEJfC4@4 zeMD+Y2NEjPiJd6l3c~aVohIJpG-{+_vHJQZ-ehlX)p4~ zHeRDLjG9|NW{K6c{!-{8qMkKgOvn#oC&=k>Ykrfo>AZk5!K9G2*qo{X-g<#T+Nr9O zf>t;OiMIp<%bqDvvE)j%0<}hh$lb6%-!9M~hl}7Z18YWBruyB_c2%ES7?44oC5fi^wXM5cx~X2OBFsK=dI7yxDl(muljtKmQ}l zV{KFQDkny=!$j;U@VeLiFuydv$l{NP3%MEei8t90t^m3{H<#E94T=tzp!=N_D1Y4k zKt}v)WSj$F2b%BYav_W7%UWvgK>OAKv9d-U~Fo8YzxmqRm#nMPBsu7f5 z!>y2{os>09&Eh-WWzwZoYOu zhSm>Ignsd4207~i%WxuMxRrLlY`TdiPSp)ac!Fo(1L>BP-&e1nm>>CI6bA-Qk-XO4 z+qDn4oi2JRpYlV(>|Z|W!ReksV!}kA|`j7WBTp&k%B$NTJUPHk0#1OUtz@*g^TlX zpMSf(JNQe_M?Hoe$N-nW9Sfh#Lx}ESFnS6pwvd20lVJ$Ed;^)TUjysdbJ%aX%K5AM zR804;IcC6i$f4a)iwB`UJ$iT4`K*)iot>9qVGxbGs)aRC4d#P_9LE&K&zruH$@%+C zijwG;|BPDL;Yi{{x%lC)0fN{cMLa-4hs-4qBQ`N}gA#rAXE*ZNC?s3cY-F?lRq?=P znJekdWH2B7&6EG+f9ms(j40jTGFRB{)eBgt;6t9HBjLtWJ{S(=XM|WRiQ{!-fiJ4~ zmvo39>>kcqFZ+BrrPt6!q)9i~Fn7!W_sSlsY!g zMoLZs2EbkoPVg9a&<^Fy8ltdIm19uBd#Zo89_^AAcoxV%%*EdMYutCgMUB3vq*3^t zT`fUzHEOLFTGLqY&d)1b3)?JoPtACx% z??cAk*h#;}_T_TU*`$mQeso}mfG!6rMgWSfn11wc@9$56>4Q%OF- z&Z$^zb4KmOnnDsGBh8fkXeGIh6a3H~~`2F6HbyERpCy z)9)VKYEmMRpyoEd(l$_*DNVk5tHjP(+w65^Z2#k#gvy|zz{iZ@8m0I)3EZw<;)DUQ zzh<|<-32yeV`GC(O~NW-DI`p8#PKzvY(Ha)8koH#h8Q&VoS4JYPT9HNL*W=Xc3U== z;b8=SLcjt2vj`do_F~=L930sE6Z=xhtMRlm_@S^@PP$d!`xI{Hu?`wzVHpCV!-O57 z(J5NC73Zj&fyuISAJk|+m-@;Uj5}~EY}7sb&gj(cL2klwJ$^j3IeouI-!h1>?pbg| zoPck_bCMtoW4ZL~m+lT(?9#gYyYG{C5_NkmBEr?w$r)f(aS=ZAh zi{~}ie08|t&|>rVS{cjmkHdU;&V3X$XZi(BWHmfNKhc(~=X`ZKr|;2MxaOrbrVqNH z?x{EIo7!CF$~pbLcseDK%wsJp0elH)h5WWlFN1yR*P4cAlFq{MSK;b$YNG_841MIa zh6JU+$FYlQsPVL$%nKvT?j$?bEmR3@5;&TAw~-8uZgf1bntB@J;El4vWxGx0`}zdS zmX)v?Zmo4Are{NqWv@SE7e6jzneV7bEouoFF8*P87nSzEp;(9=89Vtx+$79c8OH+~ zt>_;9RfsMJDZysFgBIcRatNwBZ3p{yy^8Z(mGi26daZZ@ySQ>Yl){1F`5Ox4e@=xwk3!dLTO0cK;M z?|*D1ydE5Lj_vO#>{pmeCi+3blC*%*X!oZfHrI2q?0^=vKEB~dd|sO)kr-mW42AE7 zL3aNhWCt{y@gIx8NWXVKP*AHxd1uGtkm`qVjgNocT6KR??-prsaYbnT=wjI_)|N{1 zle^6r0krNS=Q^=8S32W`B4FL#(o-+gtlb^N2s>ih+|njEqIX9aX=f0h z@VftMP2RWGyg1J@bUT=QV7CN=iN*o-;J9h$1{{vS-4P%*8KuF&16A zra!1JRr-q!nIwCKY&;GFkzZHOS8~ali-%D6)-%W%CXU_y{nhvwqEPnnq{?MSEnft- zth-+hlUD}hWXA2h-drc{-!1e4()7ke6Foyd2=kmApqQ&)L3=VnC^hI68q^XlmuALN zM09d^J&rCU)XL4N*#webS7kNOgb}yQOIy=il)0Wt4?1DkKY^Q=*%ImTMk$Ef8Zu~) z;tt^@6__r)=7bmE~UaiO><(5j~=3ZgV)P%@2b3YWMbx?qYer zP^j857qJbg@O@fy+Z^~>0{T0o90vsmkrt>)>RLm+ahkvCv`U^COIrTi`_z;OnKX5L zmqB9=41rTY{YM{5;LG$`hFm#=}L6Y84sVkqfkac{)9$dk@X$AAR#U z4rA&#lOpb88X2oV8WO`i#VhbRp%aGIFY&+i;1bdp1xuiB)NbYy8*SMqsnu>d{my0v z9vCC?J``23;bI;r07@-O?!b=0r_8sr*EMyOyt@mnB=el{*@QF66HYbmdWkUxN7lcV zFFN(}jy#;L4BoY7L>_*(!}Z1rbmG$*3%AJ0x(3T*+I6^R3fi?Qa$-J2Cze#iJj#h8 z>$P*I-%-seuwN^>43{c=AsX_}eQ*5+@L-?)y4iCRyG|DJ7TO|_`1~S2lYx=LX!R#a zq8+arJWxj2T`|&CHFs%YjI5_N)_t6-*BbKf`OMmWNmO_G2ayJ0dM2q(AR~~WQ|AT_ zN7sH>oNWKZ=$KLSo!5z06^pxUV^4zE4Rkfbl8d*{lw-u9Fy4yJK;{0Jdl6rx+W@FQ zts3pd*eF$p$ur7XGaaTM`On;_`w010eC7o($?A6J_ILVVqxFqjv}+jkd?|4{_~DP> zVpQ{9xA^XvPcx0Y7mCVXZ;XY3o%1+pK-Ms;pwI$KZM>AYE+Hw|U@&>+xpb z?RB{KY?&&GeWLB_Tf>!REZ5BEUnu;_uqW+XGtX6zT~0!)w06!(b}`?dK$?it$QibB zJ}0->o$AI1;sx$q1Xsw}>#4{GvM?Dj2Est!)3H5bWaM(mM(;DQ3lfr5O(&Qr+TL`@ zxFc}Yd*TAVQ~hB}SAfPGSh>RKyJ3G(@!j8ZwihdlRW&XV)Rv}D`P5(10}LZe z)zV)}TRwG}`j!qS+$l!Yqn&Q=Hl>hT-szp%DmQd9ryR0K&!E;54G7c zYL9Bed$M@nynAyMMhCB^{vTCe6%g09Y>P{P03o__y9W>M?lkVM0TML0ySux) zyEN|JxI3?N-aTh$f33e>t7qA$QB||(wrxzK?rPN@6$-*NIW&)2xm$`-rmS8JHBRO~ z{WVZQgLeOC)+Bu$9HTn${pbZ)3QLp9ep%?^YSrIc0(s~eJr{&xQ;=NRBN3#%FbT5D z3nt6UW~sD4fEN|CK_vf4sR&w~FNe(ujI&dC1`a70r*AW>1F3%#k5Ql3s?yYc&K|nf zltRH3$>-=-sCKfZkeChk)E%84f%@N8Dex`8zF^!-v8a0PcZI&r21QxdEE1!B;#~7W z>9cyZ{1BBqpN#6F&o`OxQe-=};7W1qa;9A=~HTpc{xKX+syqqEud zdrguRP|0aIN5~a0799FS7VtiQdb)yxrLLx3vK=ZdL(12OKUvtNBYx;Y3udputvsLh z%*J#gz#X<*IAIe2y`|QZAy^jP)pom>-!xq;mMX=;GI20mEytXD2^UpcX<6GILrFig(mDqo`f`4eA41%yXIu& zExaj_2d5=GX_NZKT+X^jaVvI%F=q~{fWHh4lN3UC$RS1bIhkwW!dVmVRP$PV3hZ(4rPdIctQ(;5 zU*xG^+Iyu?G+k zT^b#Ft&gbz8knSD&!4*e92`RMU-SXvM=Jy&UbEgbMEgCtQWqWOq0k1x51yJq1k-@a z%uaWH*aqOa#Ny;|u@+76(akXc9-Gm7N4=VUPLtq>g0S^7+e&IGJC`dZ3AVj0eM+*? z_@C|@_4BcDXJ)|85)(nXG4U~bc@Ps}M^sOi7I{XP>oD*6iNyGoQ@Px)Q@4OZZoALB zwqzN^abooUE8GJ^A!MmiNzPVU*O<<^`I`)xv^vGYHoIe5;<1-hIdC0-vlpCh8YL(< zF;v@O5$OORsn>U9|3zIGzqyx=pWOhZZtcXkj5e2tI?KiBmRoX`i8i1Uv2w$(voa!= znz1_q;)o#HcCSjbwRKQssG1Wv9Cz(UpPaGzli?N?g2Bkk)*(;PoRz(yo z@X56q$%F@yh~rXWzcP`!!s2t54cCZp(xAnLyo&19Uwe>GB7B7k7x>RZ%=hM9$Ffmy zjUxHEIr{ly9II8lLySn>lt}yCCD)?;wP71p!>JW&K*SB62krWe6#7Bhm=yZ* z=7eApBvrsQxws+!K_77`p<%Q23vIWOChl>`Z~hK9ea#HSSf)?N%Nx;#u%_48{2tRE zzcerW_vM~@2+uoI{8JS!*5zxhZdXa$;a2(z%Z@hl65;XK~&RDdI!mPElfTyBpO zT#L*SX&_y~xH)Hw@*xM_lS_B6bbC;ylg7dHHrj#Sm0e@nH>S)4LEd)jBIpG{Dt~l= z543x&3kUY!)&1oo7NX;|M|Oq;W@Ah)h3`ZVGh}xZwL94Vdk*29`@s-^ACJpp6R3Wt z7wpiG724_}3TyKh%)q8tW@RGk__2AdP%1gNa^YcvZ*(+^)b?#(atF7@3Y~Ai!5pc+ zi&UC2!1Vic5Z3wI4}?v=TtO<|fAgV`jFbyQKM9#tfe<`5(oCws{$PHDJi03Dk ziEaji=Qn{4iM8p%6*woC|3Xml%3d^KOv)qFqV=?w9M3GZaY756zwQC(Rs@%{FUcM3 z17j%*huFko?0Aip8xeZG;-vWNzYoU`&J|C4-Y*B+Z z1DrQOO~1S@p{rFG?lUdnN$-8`;^E>RDa|l6E=gss)-fzmQz$Ru^h#_=JTK7Rv0vqN z%FW#GQOm=&()L7#BDCa#tMf3rr=-*g+x%n_hGr+O;0QHg<-qwkoEbU9ykPJ_uT!p4 z6cAgcW0m&5BLRN~hk&nPEtpW8d)&H40zpSnvO_WYb~;10vdRN%W5;aVdEb@H<4hC< zrQ#j|#3R)Drr`WSd2{ob=>K%|QzzTP*;BFnc8*2@Ii;oeX77e?qs2O~Gx zZ6=WUtIj0Rdsbm|S=80p{6@T=5)ci%3K-PP&%`WO{6@loDuLgVp4}+JXYfc;It(=gs&E-iki|c6wGIN-{uaQ_hJ<34enUUc9d?U5ZYDRe#?vek5)GJH@@KCLNEs4G9k!R3CeI_m{E| zpzG;Rtgr0o3xcI52fp%f$*C>gxG#n0)t>?cF72>3Vi?D_dnfTF^aL)3!$0(JqJI3! ziFROEJMje#=lOZ0t3$r=_}h`#xYnv)|25w%>KpeEb5?cBlEHoS1?`EVw_R9hP-9&j~Vi)j+Er8rzryrhRwv zM)#QmJI!N=XlFFyF%j+FvG3*BB4*q_NLI!Yrscn1`nDGiQ7=|oJLk*V_*C{EOa9Nf z@TtNqEK+Rj+#D%0BJl(5&g*Wuv0LnB<9s9YEXv)qZ@H3g=oycm=iul2W7R=>q4f_+ z%Vv1ZwXW|uXC{RuALyQVZwB_Fu004(=EpOFAXKwwSV%S?lZ$a`-RCY^MU#iB-PPon zulmCKaE>27$UHN3sW8vkNhxb2;Qv=k{uofCZ-%r-dWj~!pQ#Xu^Jl{6sI#~`OXDxe zPt*@7((yC6rPZ=x`Il<&DBj9=&R|x5W$nR%v=YTHUFyuV#1!!zZfn?j^GJu_vLeU` zD)pEz9?||oF6HbMe)7|_k-)F|ok_7TxF8}R3w4_FZ{h-!0ZO?E=yUh>T1208xr_aL^HU)Uc7-=<*Gsy$OAz*I zC+?u`)WIzQA6?B%Y56PWImysxHb=06GybCumxsw;fFyrIlv81^7(cSky$k8YG@I3_^VG*3b%2p=m+!^vLP*&82{NDMHBsn+-#qj>A8O_#SBITq4I$8;FxNr zT<>r=luV#^U~3*p3*RB_RtsZ)FZ6IoTp&r%7%q*jrN)}qgOe+*l<_1{GF`B686cGa zzThh0KQXrgdU_Xu1owI?GEvdySdT$>QlO$)He#G?v;^7Fr-__1Ih7zYdf#X?zP(Yb z@amrr;1KEr1i!LSizBdt7kd_VW#8q11iA^eUO0RAPMT`s{5bzlrX^q^-5-GBqD z{N(5PJUXPNmx(Fgtbo;tIhfU{H)$^$*1sI$13m=|Ew+I zWY-R;MmnNx6ImH_qmfv@Vtg?jzg?|PX_o3aX%Ai$MGiwJ$IYdNZ)m!wPULRM!SQ%f zh2U8Epti$i|1gFS5F^N~QzCeTD6mX$%rxncIv{89%FoeZrfDv z()5q9;`xO@44A!SX6kS&88ttbBJ>&nK`Q%AeEqWJ+MjoWkn(1B`mWGX{b-uYGJNNm z$II$2ljgx@vQhCNU25I5C>X!mX1F)dn<>F{O6>d4P7*X>;jo@u(5_3WxInpM`T>8m zh5J92@)c?y{an%f$t7jq?r6S%98GHL6Pn1nb9kxf=?A)I%l2f&e6N&Dx3n<^lT)Ma zI8@x>o92Y|9i54NcdwEPP~;yq7$9qi8WH4ycd)*xDM<`N-oSskQZ#k2jTDQRy^K|F zGsujDhhKylFiwDnMIpPTS;gpOzJ+g&5!$-nci;a<5|aoxkn``Ktz))pW4T$O82tt4 zEUpkMjgx2;!#MeI*#?LQv_Q1L30GEN!|Zqgk9?xI%jQbNNo`ewGhao6Cb7Q&dSFbojMV*(_8^Xr&=_s|1;83^zTibr4=5XqGOt-d_QrdGJO zgNG7$D&RE7w~&f{bXlSJw>&%$Y-4X%Gz{eO&BHrI5yI9jFYo9bG6G@gE819#v0IjJ zdDoC7FFp}J$5iVWPy`MJ_q65j+8)HaRoO{(84Rub)!ZfP-6G@nlhLWbdWL@xr~o+xR4JqD zceIuip19lF+j>897###iZ$7M0!^4UFlrY%pgG3{%G;uwL6uV2;7~>P1)_rJED6M6+ zk<;1JsXFqhTtXPGg%!wMrraDoMk=ZP#0${~=y81`xO}`vL(>}X8D_7GouSOB>)-Om z;L(_pzubcv3{j9TwCpRgBv__BrA-V1dN7GiDT(%BP0c3dOR~T|9gH4Pw>CCe^_dkN z<7mV78Rv2@d*?&@Kaxl=bSCS9S%mX=4;9mdyy4+Yw@F1U>RkrC^tH_@g&y>QJLwrT z`(NY_&OSPY(KiGI35rf;5n7R!Y3D zOP1C6F({%D-fPPaKN5NsfDC>pqOL6RK`SiP4{;gXnJzEffJf-`t_%imVR1H`rbqp> z;T?zfjE0ghn0dnEd&>)S3NXxTZ76$d6Sl<92#ttHXRiNo#tXu}ub6)sgfG@7MXXo&YnO0Npq6q(*&skmL{b0 zM?oBY!hT{kky8p|zXpG3jPQS*B%t92`~{FU$Y2oIIAuoy{M}dW&>hD-!EI$u3{Eqt zIbVR?tl!E^qTf$&eUnLVqICI^(nb5oGRE49STn|x2A)-XJS$Wo`xlDHT z$D%-e<@yOoT9r=cYsp3toh&Ula>iZF(#4p3J{m}g{+F&BZ3uVMd&|1?OYG$*_Xs$ zHeDLTIuL;Q)sWm44iY}R5{-v}X-%s?J<2%-IEx_fEX!@IL)SS3l+a`mkuHRD%3z6c&@3<@fI*_2s_O#<$C7jv`Z)+wY%u*sC`wA@5Rj2@7(d2xpN0A zCDXy(J;z+0O}DaZxjx6RterfZPL&Tv2#S6--AvzZP&RJhMUxshJ8>U?G%SPrBHxSa$fN7<$^512?y+dAdAp|>QGsY}(vNgZU)a;N1MKDX^@$X>G2})MbZYy7|*&}Zl+Ru!cJ7;5Zj{HF1s+Q)FrtPp( z?q&H~vOt`?lHl00HV6wD24@1?v7-aU7Y%<}C^)p`8Y$qAHOnR!FsqMsZ50zRPTvOl zM;wy~JeZK_-$vw@WWzfhNHQ@NHHh~|9``QeZ_adzLE!%TR^@p6U5At_%Q3vO7#&vU zhr>T1-4WTafvJP{bHGx^+{%HAz`LZ*C`OsBydbzLB=O zu<%E8OsPYUKcaM3y()&vnEz#SdZ7?4$-=O^2Ds9 zA?n+8Xd^`96@Lf+Z0VzBUb1UuiqU>QZjwah5Y0EL;P5v9{tb?4@xE8j8U<4pFsI_3 z17=jFzwE!N@3UNXy$?% zDAkO(v1FcQcl5~s%F^WD?ps%m2bBJiPz+)GjK7FJM!cT zak4+H(-JvKhApQ*pQP=*&skSN{0)Y;>_D4^@$EZ9Z2DKdg`$6;8-sAC57h3{`rGTJ zL)OUGuP)@j5mr{wC*{{PQ_AJGj5kb^A6Q6GP)atw+yGrDT<3bZaIY=~O8_q@>UKMq zp5-xj^V`be3B(Zs;i^OobE*62dc8zEBxdLoID{PyT4t#9B-@o0qJ-hfikDuu6aQ zsfi54$Sll!WW7PxmG!zUx+)->95BlW+4w#2s|zjtDU^N5Z|BVH$cuBh*>c3;qcgH~ zOJ!|RCJ$8?#caX2>=CtF`(XIZ)MBa#r+$qm+47ISrGE-u*v0=K;pz7Jcmwj_XlX)k z{0K>GDjEUdP;ROYOy_gDZT04Na&BpIxVoU6-*e#-^2ob%Hs%3tmx8!?G5zRY<>u}d zhbIKA-%CS{C0Qg5#c-#A*S_xPCPbujwQO~NU55$$yi8tbJna0}Is7r|`-YUW+MdNC zbXOjzi%^MQ9&H-J(QV2l50z@mG3kI+Nx89jgY96E)!jm?$aioF>vSjX`;mf~I*S8? z4PoHihwQ?*^=s;{C$d`%(uO_a_t@OY&n^6%W`XRV7MQf<*prE}LEeC|sedp-1|wAJ z4%vq1de`1QH$wW-6~eh%f_vR?`KJn{RG%y6gz_eD=Vb&OLkWPok$t%4=Yw+E^~l`= zx?PUE6q`_Doryd7rPlWoH)~ycETo5qwNmAR<4hVp`dg5e4toKoAG65(%(JGP9UZY2 z9~wf8r25fzZ98X^6@Me8iMv~s{R^ovi2HUlJ-n7wkO?$uHGjhMqldCgMbjz|7#@l{ zk@|jE=*ZmL3=~8WEVz@w#)=pp?zeYT!MsW_ZD9jeh_B8*V4xkWTk=7+(lnz`nlHnJ zYrbl@aQe@nf1W3~+IF=+$z`H7He^K&+Mt=OHQ5F1O;7gyEqkIM2=0_WZdR|x+x+#* z;ojph`cFd9@8`{NeU|iRq)KkZrST2MF?}NjwZ@$5hQi{a6=Q}4Gov1RBHS;m`;dk) z6J(QJd+M%4^Em52EZbl7zWdPmyuuJoH>EJgd~%JKz?qxVj3Ztdxz=KeklZ!ha-%uB zVEc<@!Sj1U)062rH7O9=xIm~38B_i&Wkh^5;shwS1rbTmS6I2lhLw`a?;_$ik{2&o;Ej7HZ5@+b0N+(h`heYdMy5ts?hPOrEI zznTV>P%(STutalepSAwu>+t*rNH$hwB4)vsz#!fUQk<`eZ7W}$*QFom14U%?gMyaU8 z9ws@U8(qUP)BT|+nMR89#+^3HCExBjVM+bL0s>h%QR-#euU3JUD(FVn}ydG43bt;+#$h z8EH~}zyNfX2)^{=B?hvJ87Ua|dpI;tZUOF~5ZX9lG%npyy5$=n5gla`2 z(0(G;JhmCbB}n-eA@0lSr*{Mk>eK-+?cCiOk^Oe~f%tRoBE%FBdBj&OtodO1_@^x08F1riUHE z*vly?Y^FAf38L4`6J$G4cB;wAVxddu@tnW zzpLjA=?4~SnB)qbo9WBX zYTmNdVJSH~PP8Dk`10jlKzZKHZqR&Vtjmn+rDbWLlMx%)tQtxQbr!-`^-NF)@M3&w zzwV~|l#&m}uk)>IWz6*_#MQ3ZIL86c$8wZ(?d)COiJ|Z6@zu!g3SSU;NfU1K^zwH8 zRiAOyf#DDvv0HM0?7h;kv|6`@QHz;Jn+7QDFLD7&gKJERT<_&XLgGdW69Kbz-{x6< z8VpeRD`Jhcz9*j}1un**L+r`QiF7WXf+S)j7sp!PT#{gl)2V;tOaEq;SSTal&Unu- zrPDNe!n2gH9&W)`~(cdG3pt4rw)I}-MMaF+bM-tTn8rc zVT(6%kaG1nzYM!EIyT%%GOd-nzgNsc^J<*+K|>fw6bxo=B73Yp_5ElfEmWe|*ZV>h z)!&@%+*bmZ>U*l+wktfjmV~EXy99qA+6v=g6y*z|${9)BD%(+!1!X@u- zKVd51djrp%0?v5nSj+&J{BsDE{o%Y=HnpW5BPKbT1cd@ExN<3N_ydOInOf-rnoi%A zcmh4CjDcKiPf-PvN20QXfH2*T)*!}gZBHmDugX`%4P|-MNy!|ofYGJ+n4b{JvxP>L zlD6NrRw2#NZr6l3@Ud8xWksBs^?Q2T9&atb{wqYnlWY`BS5qFrK0!k~X{isF=Lqp+ zWwP2b1finoGnLd?f{CoM+<`5&{?vu|43qxi1PJ@$A9Kp~M=(_l?oRHUU^dCq#FFKp zcA5!VYjw|JTyTPde7A+FQ|Pmxab-BW98Ru+0_^y31e{qMRkrLBm___2iky!kpjlpK zW`Z+6b(jT2^$+^_4H{Tmvii2+#>ZOQ`3xmSR3EK?Awp*&KO&j6oM5Hf+_Nk^1-la{ zN_V+=iT{`kM*I!Oah({~dhKuShr5Au!EwlnyZhS-MUs4RS!B4D``@89@S6^9g9GTA zaZ{VWqV#D~Q+^3`4)1J6F8mudCZTWKC6|idvrzNI)xRMRvp_#97BbA&r+6E8CIa*# zdOpxSBG5~z&o8*e#@CfK_Vu8vzqU#cG!O!`&pIkEUj{E04X9*a^T@E4@;-%Dl@F*D zP?3XCObqFYJX(&s%zBEn?auuziA#F5)QMgt<4G13AmXz;fnqLm-jcV?Un$~7AKC#J zz!^-we*A0_$2DIpuOQHP&9H2MC?!xBn`NLq`<|;MA>_-$RXMI%NRpXTOzV}oeQ8G? zS*>)BwGNvdmJl{kc<1=8Yi;?2%9qITy>DZeIIWyNpcC-Jk5;n2N~eDrm!X0fqxW>2 zIs)$C7s|XTVQn=+M(?s27#H z5CnXP$^v#aHIDH+keyb|JCXJ%|Fj;gzFojR$cF8@6yoL?N@#WBjXc-APk0;5&OwS4 z|HDrkHbkdFUsL+GBj4`{pqhw13GifaAvs<4#=rdhm{VF|PA2d%VL6ptePs43cm}3; zwwl+ztuJah3t!`3=B)$e2hO6Iy<3Ks3Khw1e?V|Yfet3M5K(MShi;49b z?`AnS{N^#qMLgJRhD|?cjUTgt>`HX)K{wUU&T4nh!EBlW=Pa=2{BHRzv z>IRq9Uz~_!mMgY(Adc;-we>$b6H-?nhf^!5!!U0>49aV?{S^K@L&WqM*?kI&DRi+iO3NSY`}E+FIe!E3e{SC&BfQxAn))^|w_s_oHu)2tWzO># z`BS^|x{VSyMJ|>9)c%`c*f2&r*9cs<2J81N`k-O8^>!j%mTsvrB2f)mt(6*1>BulP z%6l<&-HH){m@%JPWA6CdXy8>Fc}MX0w)u;1<{trFhtF|_bDBAP6P|F5-tQAK#4^r~ zb&xzemnZHj82NTCiqi2CMj>QhoL~K59t<492zEcP7yeiEpcM`3Y`r70ZpfwMY1KIV zQYc^JqygE_SVr?@p3Uf$fu(_>&BXWwpLT(xPh3Xa6w`|D8l9qQxd_v?G+kId9;Cr3 zov98u*Q$uDtHI%d@uBefkN)=XD3a`8i6xH8Qnua|RJJZl|uYYOC=6HCygSOf2A@ z!Xs4(bnW?{uTk4WakOqT)$Y*_h$|T9>Z6!Oap4tQk=)?O|G*P1%G{nQ&-(#aQ>;yT%6;NQ3^ofy))Wzoms>cQ@X z4PeCmsa`3*GkOfGLP5QWYp&WXgrlx^Obf{*oh$U+w~~UX_jUk+x#+T+AW!p?t*bw2 zBg+P^<~h@2N%f6@js^nE0FnmbM(B?v!q#{-M|pitjCyQz-qy*sgCDrY;_uAQ{#ABh zAv*`xn>b{j!e}(-VFKYU@n0UxzOXm;;s9x*qV5ORpp z0bND-E;7lG>`j~Rbnr5}^i)nDBNcsseYkp%<-ffg1f(C64InKMt1VG%AA%DrN;{x~O!faN)hjOB&9_ zDiJbXUB4d-^wSMXz@AA)kT^^xItH7MwjLs_n0|V%>?HhmCX_(d`53fa`#d%VcEx0w z-yP~I=^G3EAb#&PdFN9m$5)mbR0IUqF_D74I~Lj=rGqwe!8fKI^b!F;Rk!IM{`7gQ zFX&wr5k7J4;ugAugf~Ecg*&(XLGNCG!N>Z=ZUd2lCY|G>)Y9-J>w9KFVm9g=ve`+t z9YwZADUjK5>EG6aE#MmrbMXjXEVB;QU)b}P+oD;Guj6g;UZ03_jBko_YQ3|M!_x&o zH*_zpq4ZSpj#^kmDqXZBenH;W>W!+gKtLu+RuXg6;ukc#X)eC?B`!FoHU zwcr@%fA0d=ayTexc@ORvdjFy68$%B!4-;o1$%EvfXXFDL!k#hZ+>r7^%gR6ORCuQp z>Bh}Ki#PL$l;AV{jMVjj#T-7#M|N5RdhcKS2{ko2!IepKAJEMrZfC%`PjlS@Yta?9 zaHb1e2ILaLy(GsVI|Y!vLA8PFy$3U-*>(zcKPRF|wd;gJbRy0>3b)`)3(p1&dcv(TW?tg9KDsO2GyD6E=tk}u?8e|S z@bMU-Elj+d7v2d;g2$}7LTkI-7nDr-|DMSP8LBiGfz8I+y4>2vri015jywYbvdoy@ zf?!mMe|bJ5ka(bCe@ii%nnexBqd4JRWM32K;E-^;@TpBD$(Zx?JnyHja*UZVQhj|d zUYoPg>V48&rv%f8f3O`$?}`)7U$0Fb&Benjr6rjEibb`d<9bAu_^a8~*@T6Hxqq)7 zkhM{V?E6%rEzECnyTHOVJ05|DmD^ydhs#(2KF-j?ZY1!=cn-lYE)pGAZ?A17>vs)4 zlo7;Qc;>y*nnAyj`IyJjd87q{R#3OSsImP#)+|mFTwN2c9#es?Ycx2jo}OM4pw?PU zkwc%yr}I~Bi~Yp6%>!akX5NcA>*R= zuUq<9C$!5yrh{JZK6CdOZzq=t^F9Er1Wjttk)ZtLZJ)^BShCZ2C?;D25$Vf22;&??&bT#cJ=%j=r>aSVjH zQJ39=+Wc|qV;PM+DnX`+RB4T&11upqX6fmcn1k01%}U$M#6o#_`&FNd!K!yz0o4_Z zvH!Tw9phX7T*i~?D=gsPISS#6M|;%J)cJf99+S;7pXdw!{m2_=mH%bg2Xqt%$I(TE zdiU_uaTQtq`pW+t{77iZ5JQ$}1O>CSHPl2R372xu`)vw#AyMZB+MIW_w_*sdL5K`o zwX?jV518M1g7>*Q(|Xxkf8hS~Q^uT<)sG&-xlrpc8`ZVxzR2C;$Jnfdgm;5Xvl~6{ z!-kWPZd@1pk!JEkpmHNbdd%Jjx9J2i`Ge4x_YphjpG)OL@k`G0+iT6&{qd}5W;#+dnV32c0UG!3G8t(ZEvNMWORGI?HV#e8L@s7*R`>apk7v&eQAHPQ;s8EjxkO#U#mG+4IK|Rp@CiXZIt*m z4hIesyE-WjT6(yVMn@~fW@VfZU*Pk;nwJ7W2i{VwxVYi1E{2CD_{R@wB{86NVx|}WX?jKLxG|h3_ z7lElp%%7=bS~i!(el!vZC$i>1nURXfj)H~eNWCqp5DTsrXEAxmE+=2`QhD2t!x z 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 literal 0 HcmV?d00001 diff --git a/webapp/tsconfig.json b/webapp/tsconfig.json new file mode 100644 index 0000000..328821b --- /dev/null +++ b/webapp/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../tsconfig-base.json", + "compilerOptions": { + "composite": true, + "esModuleInterop": true, + "experimentalDecorators": true, + + "lib": ["DOM", "ESNext"], + "module": "ESNext", + "moduleResolution": "Node", + "target": "ES6", + + "outDir": "./dist", + "baseUrl": "." + }, + "references": [ + { "path": "../shared" } + ] +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..ddbb0d3 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,2765 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0": + version "7.24.2" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz" + integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== + dependencies: + "@babel/highlight" "^7.24.2" + picocolors "^1.0.0" + +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/highlight@^7.24.2": + version "7.24.2" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz" + integrity sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@fastify/ajv-compiler@^3.5.0": + version "3.5.0" + resolved "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.5.0.tgz" + integrity sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA== + dependencies: + ajv "^8.11.0" + ajv-formats "^2.1.1" + fast-uri "^2.0.0" + +"@fastify/error@^3.3.0", "@fastify/error@^3.4.0": + version "3.4.1" + resolved "https://registry.npmjs.org/@fastify/error/-/error-3.4.1.tgz" + integrity sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ== + +"@fastify/fast-json-stringify-compiler@^4.3.0": + version "4.3.0" + resolved "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.3.0.tgz" + integrity sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA== + dependencies: + fast-json-stringify "^5.7.0" + +"@fastify/merge-json-schemas@^0.1.0": + version "0.1.1" + resolved "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.1.1.tgz" + integrity sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA== + dependencies: + fast-deep-equal "^3.1.3" + +"@fastify/websocket@^10.0.1": + version "10.0.1" + resolved "https://registry.npmjs.org/@fastify/websocket/-/websocket-10.0.1.tgz" + integrity sha512-8/pQIxTPRD8U94aILTeJ+2O3el/r19+Ej5z1O1mXlqplsUH7KzCjAI0sgd5DM/NoPjAi5qLFNIjgM5+9/rGSNw== + dependencies: + duplexify "^4.1.2" + fastify-plugin "^4.0.0" + ws "^8.0.0" + +"@julusian/jpeg-turbo@^2.1.0": + version "2.1.0" + resolved "https://registry.npmjs.org/@julusian/jpeg-turbo/-/jpeg-turbo-2.1.0.tgz" + integrity sha512-tmMnOTuiXr3W+lPOlyrG/efBxVvp/tbJe6iVsY2qKl5TLHytCkWabsnq4PHS6ddCYJeiGWQBvRwQpKlFd3I2gg== + dependencies: + cmake-js "^7.0.0" + node-addon-api "^5.0.0" + pkg-prebuilds "~0.1.0" + +"@lezer/common@^1.0.0": + version "1.2.1" + resolved "https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz" + integrity sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ== + +"@lezer/lr@^1.0.0": + version "1.4.0" + resolved "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz" + integrity sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg== + dependencies: + "@lezer/common" "^1.0.0" + +"@lmdb/lmdb-linux-x64@2.8.5": + version "2.8.5" + resolved "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-2.8.5.tgz" + integrity sha512-Xkc8IUx9aEhP0zvgeKy7IQ3ReX2N8N1L0WPcQwnZweWmOuKfwpS3GRIYqLtK5za/w3E60zhFfNdS+3pBZPytqQ== + +"@mapbox/node-pre-gyp@^1.0.0": + version "1.0.11" + resolved "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz" + integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ== + dependencies: + detect-libc "^2.0.0" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.7" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + +"@mischnic/json-sourcemap@^0.1.0": + version "0.1.1" + resolved "https://registry.npmjs.org/@mischnic/json-sourcemap/-/json-sourcemap-0.1.1.tgz" + integrity sha512-iA7+tyVqfrATAIsIRWQG+a7ZLLD0VaOCKV2Wd/v4mqIU3J9c4jx9p7S0nw1XH3gJCKNBOOwACOPYYSUu9pgT+w== + dependencies: + "@lezer/common" "^1.0.0" + "@lezer/lr" "^1.0.0" + json5 "^2.2.1" + +"@msgpackr-extract/msgpackr-extract-linux-x64@3.0.2": + version "3.0.2" + resolved "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.2.tgz" + integrity sha512-gsWNDCklNy7Ajk0vBBf9jEx04RUxuDQfBse918Ww+Qb9HCPoGzS+XJTLe96iN3BVK7grnLiYghP/M4L8VsaHeA== + +"@parcel/bundler-default@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/bundler-default/-/bundler-default-2.12.0.tgz" + integrity sha512-3ybN74oYNMKyjD6V20c9Gerdbh7teeNvVMwIoHIQMzuIFT6IGX53PyOLlOKRLbjxMc0TMimQQxIt2eQqxR5LsA== + dependencies: + "@parcel/diagnostic" "2.12.0" + "@parcel/graph" "3.2.0" + "@parcel/plugin" "2.12.0" + "@parcel/rust" "2.12.0" + "@parcel/utils" "2.12.0" + nullthrows "^1.1.1" + +"@parcel/cache@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/cache/-/cache-2.12.0.tgz" + integrity sha512-FX5ZpTEkxvq/yvWklRHDESVRz+c7sLTXgFuzz6uEnBcXV38j6dMSikflNpHA6q/L4GKkCqRywm9R6XQwhwIMyw== + dependencies: + "@parcel/fs" "2.12.0" + "@parcel/logger" "2.12.0" + "@parcel/utils" "2.12.0" + lmdb "2.8.5" + +"@parcel/codeframe@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/codeframe/-/codeframe-2.12.0.tgz" + integrity sha512-v2VmneILFiHZJTxPiR7GEF1wey1/IXPdZMcUlNXBiPZyWDfcuNgGGVQkx/xW561rULLIvDPharOMdxz5oHOKQg== + dependencies: + chalk "^4.1.0" + +"@parcel/compressor-raw@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/compressor-raw/-/compressor-raw-2.12.0.tgz" + integrity sha512-h41Q3X7ZAQ9wbQ2csP8QGrwepasLZdXiuEdpUryDce6rF9ZiHoJ97MRpdLxOhOPyASTw/xDgE1xyaPQr0Q3f5A== + dependencies: + "@parcel/plugin" "2.12.0" + +"@parcel/config-default@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/config-default/-/config-default-2.12.0.tgz" + integrity sha512-dPNe2n9eEsKRc1soWIY0yToMUPirPIa2QhxcCB3Z5RjpDGIXm0pds+BaiqY6uGLEEzsjhRO0ujd4v2Rmm0vuFg== + dependencies: + "@parcel/bundler-default" "2.12.0" + "@parcel/compressor-raw" "2.12.0" + "@parcel/namer-default" "2.12.0" + "@parcel/optimizer-css" "2.12.0" + "@parcel/optimizer-htmlnano" "2.12.0" + "@parcel/optimizer-image" "2.12.0" + "@parcel/optimizer-svgo" "2.12.0" + "@parcel/optimizer-swc" "2.12.0" + "@parcel/packager-css" "2.12.0" + "@parcel/packager-html" "2.12.0" + "@parcel/packager-js" "2.12.0" + "@parcel/packager-raw" "2.12.0" + "@parcel/packager-svg" "2.12.0" + "@parcel/packager-wasm" "2.12.0" + "@parcel/reporter-dev-server" "2.12.0" + "@parcel/resolver-default" "2.12.0" + "@parcel/runtime-browser-hmr" "2.12.0" + "@parcel/runtime-js" "2.12.0" + "@parcel/runtime-react-refresh" "2.12.0" + "@parcel/runtime-service-worker" "2.12.0" + "@parcel/transformer-babel" "2.12.0" + "@parcel/transformer-css" "2.12.0" + "@parcel/transformer-html" "2.12.0" + "@parcel/transformer-image" "2.12.0" + "@parcel/transformer-js" "2.12.0" + "@parcel/transformer-json" "2.12.0" + "@parcel/transformer-postcss" "2.12.0" + "@parcel/transformer-posthtml" "2.12.0" + "@parcel/transformer-raw" "2.12.0" + "@parcel/transformer-react-refresh-wrap" "2.12.0" + "@parcel/transformer-svg" "2.12.0" + +"@parcel/core@^2.12.0", "@parcel/core@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/core/-/core-2.12.0.tgz" + integrity sha512-s+6pwEj+GfKf7vqGUzN9iSEPueUssCCQrCBUlcAfKrJe0a22hTUCjewpB0I7lNrCIULt8dkndD+sMdOrXsRl6Q== + dependencies: + "@mischnic/json-sourcemap" "^0.1.0" + "@parcel/cache" "2.12.0" + "@parcel/diagnostic" "2.12.0" + "@parcel/events" "2.12.0" + "@parcel/fs" "2.12.0" + "@parcel/graph" "3.2.0" + "@parcel/logger" "2.12.0" + "@parcel/package-manager" "2.12.0" + "@parcel/plugin" "2.12.0" + "@parcel/profiler" "2.12.0" + "@parcel/rust" "2.12.0" + "@parcel/source-map" "^2.1.1" + "@parcel/types" "2.12.0" + "@parcel/utils" "2.12.0" + "@parcel/workers" "2.12.0" + abortcontroller-polyfill "^1.1.9" + base-x "^3.0.8" + browserslist "^4.6.6" + clone "^2.1.1" + dotenv "^7.0.0" + dotenv-expand "^5.1.0" + json5 "^2.2.0" + msgpackr "^1.9.9" + nullthrows "^1.1.1" + semver "^7.5.2" + +"@parcel/diagnostic@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/diagnostic/-/diagnostic-2.12.0.tgz" + integrity sha512-8f1NOsSFK+F4AwFCKynyIu9Kr/uWHC+SywAv4oS6Bv3Acig0gtwUjugk0C9UaB8ztBZiW5TQZhw+uPZn9T/lJA== + dependencies: + "@mischnic/json-sourcemap" "^0.1.0" + nullthrows "^1.1.1" + +"@parcel/events@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/events/-/events-2.12.0.tgz" + integrity sha512-nmAAEIKLjW1kB2cUbCYSmZOGbnGj8wCzhqnK727zCCWaA25ogzAtt657GPOeFyqW77KyosU728Tl63Fc8hphIA== + +"@parcel/fs@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/fs/-/fs-2.12.0.tgz" + integrity sha512-NnFkuvou1YBtPOhTdZr44WN7I60cGyly2wpHzqRl62yhObyi1KvW0SjwOMa0QGNcBOIzp4G0CapoZ93hD0RG5Q== + dependencies: + "@parcel/rust" "2.12.0" + "@parcel/types" "2.12.0" + "@parcel/utils" "2.12.0" + "@parcel/watcher" "^2.0.7" + "@parcel/workers" "2.12.0" + +"@parcel/graph@3.2.0": + version "3.2.0" + resolved "https://registry.npmjs.org/@parcel/graph/-/graph-3.2.0.tgz" + integrity sha512-xlrmCPqy58D4Fg5umV7bpwDx5Vyt7MlnQPxW68vae5+BA4GSWetfZt+Cs5dtotMG2oCHzZxhIPt7YZ7NRyQzLA== + dependencies: + nullthrows "^1.1.1" + +"@parcel/logger@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/logger/-/logger-2.12.0.tgz" + integrity sha512-cJ7Paqa7/9VJ7C+KwgJlwMqTQBOjjn71FbKk0G07hydUEBISU2aDfmc/52o60ErL9l+vXB26zTrIBanbxS8rVg== + dependencies: + "@parcel/diagnostic" "2.12.0" + "@parcel/events" "2.12.0" + +"@parcel/markdown-ansi@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/markdown-ansi/-/markdown-ansi-2.12.0.tgz" + integrity sha512-WZz3rzL8k0H3WR4qTHX6Ic8DlEs17keO9gtD4MNGyMNQbqQEvQ61lWJaIH0nAtgEetu0SOITiVqdZrb8zx/M7w== + dependencies: + chalk "^4.1.0" + +"@parcel/namer-default@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/namer-default/-/namer-default-2.12.0.tgz" + integrity sha512-9DNKPDHWgMnMtqqZIMiEj/R9PNWW16lpnlHjwK3ciRlMPgjPJ8+UNc255teZODhX0T17GOzPdGbU/O/xbxVPzA== + dependencies: + "@parcel/diagnostic" "2.12.0" + "@parcel/plugin" "2.12.0" + nullthrows "^1.1.1" + +"@parcel/node-resolver-core@3.3.0": + version "3.3.0" + resolved "https://registry.npmjs.org/@parcel/node-resolver-core/-/node-resolver-core-3.3.0.tgz" + integrity sha512-rhPW9DYPEIqQBSlYzz3S0AjXxjN6Ub2yS6tzzsW/4S3Gpsgk/uEq4ZfxPvoPf/6TgZndVxmKwpmxaKtGMmf3cA== + dependencies: + "@mischnic/json-sourcemap" "^0.1.0" + "@parcel/diagnostic" "2.12.0" + "@parcel/fs" "2.12.0" + "@parcel/rust" "2.12.0" + "@parcel/utils" "2.12.0" + nullthrows "^1.1.1" + semver "^7.5.2" + +"@parcel/optimizer-css@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/optimizer-css/-/optimizer-css-2.12.0.tgz" + integrity sha512-ifbcC97fRzpruTjaa8axIFeX4MjjSIlQfem3EJug3L2AVqQUXnM1XO8L0NaXGNLTW2qnh1ZjIJ7vXT/QhsphsA== + dependencies: + "@parcel/diagnostic" "2.12.0" + "@parcel/plugin" "2.12.0" + "@parcel/source-map" "^2.1.1" + "@parcel/utils" "2.12.0" + browserslist "^4.6.6" + lightningcss "^1.22.1" + nullthrows "^1.1.1" + +"@parcel/optimizer-htmlnano@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.12.0.tgz" + integrity sha512-MfPMeCrT8FYiOrpFHVR+NcZQlXAptK2r4nGJjfT+ndPBhEEZp4yyL7n1y7HfX9geg5altc4WTb4Gug7rCoW8VQ== + dependencies: + "@parcel/plugin" "2.12.0" + htmlnano "^2.0.0" + nullthrows "^1.1.1" + posthtml "^0.16.5" + svgo "^2.4.0" + +"@parcel/optimizer-image@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/optimizer-image/-/optimizer-image-2.12.0.tgz" + integrity sha512-bo1O7raeAIbRU5nmNVtx8divLW9Xqn0c57GVNGeAK4mygnQoqHqRZ0mR9uboh64pxv6ijXZHPhKvU9HEpjPjBQ== + dependencies: + "@parcel/diagnostic" "2.12.0" + "@parcel/plugin" "2.12.0" + "@parcel/rust" "2.12.0" + "@parcel/utils" "2.12.0" + "@parcel/workers" "2.12.0" + +"@parcel/optimizer-svgo@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/optimizer-svgo/-/optimizer-svgo-2.12.0.tgz" + integrity sha512-Kyli+ZZXnoonnbeRQdoWwee9Bk2jm/49xvnfb+2OO8NN0d41lblBoRhOyFiScRnJrw7eVl1Xrz7NTkXCIO7XFQ== + dependencies: + "@parcel/diagnostic" "2.12.0" + "@parcel/plugin" "2.12.0" + "@parcel/utils" "2.12.0" + svgo "^2.4.0" + +"@parcel/optimizer-swc@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/optimizer-swc/-/optimizer-swc-2.12.0.tgz" + integrity sha512-iBi6LZB3lm6WmbXfzi8J3DCVPmn4FN2lw7DGXxUXu7MouDPVWfTsM6U/5TkSHJRNRogZ2gqy5q9g34NPxHbJcw== + dependencies: + "@parcel/diagnostic" "2.12.0" + "@parcel/plugin" "2.12.0" + "@parcel/source-map" "^2.1.1" + "@parcel/utils" "2.12.0" + "@swc/core" "^1.3.36" + nullthrows "^1.1.1" + +"@parcel/package-manager@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/package-manager/-/package-manager-2.12.0.tgz" + integrity sha512-0nvAezcjPx9FT+hIL+LS1jb0aohwLZXct7jAh7i0MLMtehOi0z1Sau+QpgMlA9rfEZZ1LIeFdnZZwqSy7Ccspw== + dependencies: + "@parcel/diagnostic" "2.12.0" + "@parcel/fs" "2.12.0" + "@parcel/logger" "2.12.0" + "@parcel/node-resolver-core" "3.3.0" + "@parcel/types" "2.12.0" + "@parcel/utils" "2.12.0" + "@parcel/workers" "2.12.0" + "@swc/core" "^1.3.36" + semver "^7.5.2" + +"@parcel/packager-css@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/packager-css/-/packager-css-2.12.0.tgz" + integrity sha512-j3a/ODciaNKD19IYdWJT+TP+tnhhn5koBGBWWtrKSu0UxWpnezIGZetit3eE+Y9+NTePalMkvpIlit2eDhvfJA== + dependencies: + "@parcel/diagnostic" "2.12.0" + "@parcel/plugin" "2.12.0" + "@parcel/source-map" "^2.1.1" + "@parcel/utils" "2.12.0" + lightningcss "^1.22.1" + nullthrows "^1.1.1" + +"@parcel/packager-html@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/packager-html/-/packager-html-2.12.0.tgz" + integrity sha512-PpvGB9hFFe+19NXGz2ApvPrkA9GwEqaDAninT+3pJD57OVBaxB8U+HN4a5LICKxjUppPPqmrLb6YPbD65IX4RA== + dependencies: + "@parcel/plugin" "2.12.0" + "@parcel/types" "2.12.0" + "@parcel/utils" "2.12.0" + nullthrows "^1.1.1" + posthtml "^0.16.5" + +"@parcel/packager-js@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/packager-js/-/packager-js-2.12.0.tgz" + integrity sha512-viMF+FszITRRr8+2iJyk+4ruGiL27Y6AF7hQ3xbJfzqnmbOhGFtLTQwuwhOLqN/mWR2VKdgbLpZSarWaO3yAMg== + dependencies: + "@parcel/diagnostic" "2.12.0" + "@parcel/plugin" "2.12.0" + "@parcel/rust" "2.12.0" + "@parcel/source-map" "^2.1.1" + "@parcel/types" "2.12.0" + "@parcel/utils" "2.12.0" + globals "^13.2.0" + nullthrows "^1.1.1" + +"@parcel/packager-raw@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/packager-raw/-/packager-raw-2.12.0.tgz" + integrity sha512-tJZqFbHqP24aq1F+OojFbQIc09P/u8HAW5xfndCrFnXpW4wTgM3p03P0xfw3gnNq+TtxHJ8c3UFE5LnXNNKhYA== + dependencies: + "@parcel/plugin" "2.12.0" + +"@parcel/packager-svg@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/packager-svg/-/packager-svg-2.12.0.tgz" + integrity sha512-ldaGiacGb2lLqcXas97k8JiZRbAnNREmcvoY2W2dvW4loVuDT9B9fU777mbV6zODpcgcHWsLL3lYbJ5Lt3y9cg== + dependencies: + "@parcel/plugin" "2.12.0" + "@parcel/types" "2.12.0" + "@parcel/utils" "2.12.0" + posthtml "^0.16.4" + +"@parcel/packager-wasm@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/packager-wasm/-/packager-wasm-2.12.0.tgz" + integrity sha512-fYqZzIqO9fGYveeImzF8ll6KRo2LrOXfD+2Y5U3BiX/wp9wv17dz50QLDQm9hmTcKGWxK4yWqKQh+Evp/fae7A== + dependencies: + "@parcel/plugin" "2.12.0" + +"@parcel/plugin@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/plugin/-/plugin-2.12.0.tgz" + integrity sha512-nc/uRA8DiMoe4neBbzV6kDndh/58a4wQuGKw5oEoIwBCHUvE2W8ZFSu7ollSXUGRzfacTt4NdY8TwS73ScWZ+g== + dependencies: + "@parcel/types" "2.12.0" + +"@parcel/profiler@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/profiler/-/profiler-2.12.0.tgz" + integrity sha512-q53fvl5LDcFYzMUtSusUBZSjQrKjMlLEBgKeQHFwkimwR1mgoseaDBDuNz0XvmzDzF1UelJ02TUKCGacU8W2qA== + dependencies: + "@parcel/diagnostic" "2.12.0" + "@parcel/events" "2.12.0" + chrome-trace-event "^1.0.2" + +"@parcel/reporter-cli@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/reporter-cli/-/reporter-cli-2.12.0.tgz" + integrity sha512-TqKsH4GVOLPSCanZ6tcTPj+rdVHERnt5y4bwTM82cajM21bCX1Ruwp8xOKU+03091oV2pv5ieB18pJyRF7IpIw== + dependencies: + "@parcel/plugin" "2.12.0" + "@parcel/types" "2.12.0" + "@parcel/utils" "2.12.0" + chalk "^4.1.0" + term-size "^2.2.1" + +"@parcel/reporter-dev-server@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/reporter-dev-server/-/reporter-dev-server-2.12.0.tgz" + integrity sha512-tIcDqRvAPAttRlTV28dHcbWT5K2r/MBFks7nM4nrEDHWtnrCwimkDmZTc1kD8QOCCjGVwRHcQybpHvxfwol6GA== + dependencies: + "@parcel/plugin" "2.12.0" + "@parcel/utils" "2.12.0" + +"@parcel/reporter-tracer@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/reporter-tracer/-/reporter-tracer-2.12.0.tgz" + integrity sha512-g8rlu9GxB8Ut/F8WGx4zidIPQ4pcYFjU9bZO+fyRIPrSUFH2bKijCnbZcr4ntqzDGx74hwD6cCG4DBoleq2UlQ== + dependencies: + "@parcel/plugin" "2.12.0" + "@parcel/utils" "2.12.0" + chrome-trace-event "^1.0.3" + nullthrows "^1.1.1" + +"@parcel/resolver-default@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/resolver-default/-/resolver-default-2.12.0.tgz" + integrity sha512-uuhbajTax37TwCxu7V98JtRLiT6hzE4VYSu5B7Qkauy14/WFt2dz6GOUXPgVsED569/hkxebPx3KCMtZW6cHHA== + dependencies: + "@parcel/node-resolver-core" "3.3.0" + "@parcel/plugin" "2.12.0" + +"@parcel/runtime-browser-hmr@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.12.0.tgz" + integrity sha512-4ZLp2FWyD32r0GlTulO3+jxgsA3oO1P1b5oO2IWuWilfhcJH5LTiazpL5YdusUjtNn9PGN6QLAWfxmzRIfM+Ow== + dependencies: + "@parcel/plugin" "2.12.0" + "@parcel/utils" "2.12.0" + +"@parcel/runtime-js@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/runtime-js/-/runtime-js-2.12.0.tgz" + integrity sha512-sBerP32Z1crX5PfLNGDSXSdqzlllM++GVnVQVeM7DgMKS8JIFG3VLi28YkX+dYYGtPypm01JoIHCkvwiZEcQJg== + dependencies: + "@parcel/diagnostic" "2.12.0" + "@parcel/plugin" "2.12.0" + "@parcel/utils" "2.12.0" + nullthrows "^1.1.1" + +"@parcel/runtime-react-refresh@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.12.0.tgz" + integrity sha512-SCHkcczJIDFTFdLTzrHTkQ0aTrX3xH6jrA4UsCBL6ji61+w+ohy4jEEe9qCgJVXhnJfGLE43HNXek+0MStX+Mw== + dependencies: + "@parcel/plugin" "2.12.0" + "@parcel/utils" "2.12.0" + react-error-overlay "6.0.9" + react-refresh "^0.9.0" + +"@parcel/runtime-service-worker@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/runtime-service-worker/-/runtime-service-worker-2.12.0.tgz" + integrity sha512-BXuMBsfiwpIEnssn+jqfC3jkgbS8oxeo3C7xhSQsuSv+AF2FwY3O3AO1c1RBskEW3XrBLNINOJujroNw80VTKA== + dependencies: + "@parcel/plugin" "2.12.0" + "@parcel/utils" "2.12.0" + nullthrows "^1.1.1" + +"@parcel/rust@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/rust/-/rust-2.12.0.tgz" + integrity sha512-005cldMdFZFDPOjbDVEXcINQ3wT4vrxvSavRWI3Az0e3E18exO/x/mW9f648KtXugOXMAqCEqhFHcXECL9nmMw== + +"@parcel/source-map@^2.1.1": + version "2.1.1" + resolved "https://registry.npmjs.org/@parcel/source-map/-/source-map-2.1.1.tgz" + integrity sha512-Ejx1P/mj+kMjQb8/y5XxDUn4reGdr+WyKYloBljpppUy8gs42T+BNoEOuRYqDVdgPc6NxduzIDoJS9pOFfV5Ew== + dependencies: + detect-libc "^1.0.3" + +"@parcel/transformer-babel@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/transformer-babel/-/transformer-babel-2.12.0.tgz" + integrity sha512-zQaBfOnf/l8rPxYGnsk/ufh/0EuqvmnxafjBIpKZ//j6rGylw5JCqXSb1QvvAqRYruKeccxGv7+HrxpqKU6V4A== + dependencies: + "@parcel/diagnostic" "2.12.0" + "@parcel/plugin" "2.12.0" + "@parcel/source-map" "^2.1.1" + "@parcel/utils" "2.12.0" + browserslist "^4.6.6" + json5 "^2.2.0" + nullthrows "^1.1.1" + semver "^7.5.2" + +"@parcel/transformer-css@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/transformer-css/-/transformer-css-2.12.0.tgz" + integrity sha512-vXhOqoAlQGATYyQ433Z1DXKmiKmzOAUmKysbYH3FD+LKEKLMEl/pA14goqp00TW+A/EjtSKKyeMyHlMIIUqj4Q== + dependencies: + "@parcel/diagnostic" "2.12.0" + "@parcel/plugin" "2.12.0" + "@parcel/source-map" "^2.1.1" + "@parcel/utils" "2.12.0" + browserslist "^4.6.6" + lightningcss "^1.22.1" + nullthrows "^1.1.1" + +"@parcel/transformer-html@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/transformer-html/-/transformer-html-2.12.0.tgz" + integrity sha512-5jW4dFFBlYBvIQk4nrH62rfA/G/KzVzEDa6S+Nne0xXhglLjkm64Ci9b/d4tKZfuGWUbpm2ASAq8skti/nfpXw== + dependencies: + "@parcel/diagnostic" "2.12.0" + "@parcel/plugin" "2.12.0" + "@parcel/rust" "2.12.0" + nullthrows "^1.1.1" + posthtml "^0.16.5" + posthtml-parser "^0.10.1" + posthtml-render "^3.0.0" + semver "^7.5.2" + srcset "4" + +"@parcel/transformer-image@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/transformer-image/-/transformer-image-2.12.0.tgz" + integrity sha512-8hXrGm2IRII49R7lZ0RpmNk27EhcsH+uNKsvxuMpXPuEnWgC/ha/IrjaI29xCng1uGur74bJF43NUSQhR4aTdw== + dependencies: + "@parcel/plugin" "2.12.0" + "@parcel/utils" "2.12.0" + "@parcel/workers" "2.12.0" + nullthrows "^1.1.1" + +"@parcel/transformer-js@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/transformer-js/-/transformer-js-2.12.0.tgz" + integrity sha512-OSZpOu+FGDbC/xivu24v092D9w6EGytB3vidwbdiJ2FaPgfV7rxS0WIUjH4I0OcvHAcitArRXL0a3+HrNTdQQw== + dependencies: + "@parcel/diagnostic" "2.12.0" + "@parcel/plugin" "2.12.0" + "@parcel/rust" "2.12.0" + "@parcel/source-map" "^2.1.1" + "@parcel/utils" "2.12.0" + "@parcel/workers" "2.12.0" + "@swc/helpers" "^0.5.0" + browserslist "^4.6.6" + nullthrows "^1.1.1" + regenerator-runtime "^0.13.7" + semver "^7.5.2" + +"@parcel/transformer-json@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/transformer-json/-/transformer-json-2.12.0.tgz" + integrity sha512-Utv64GLRCQILK5r0KFs4o7I41ixMPllwOLOhkdjJKvf1hZmN6WqfOmB1YLbWS/y5Zb/iB52DU2pWZm96vLFQZQ== + dependencies: + "@parcel/plugin" "2.12.0" + json5 "^2.2.0" + +"@parcel/transformer-postcss@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/transformer-postcss/-/transformer-postcss-2.12.0.tgz" + integrity sha512-FZqn+oUtiLfPOn67EZxPpBkfdFiTnF4iwiXPqvst3XI8H+iC+yNgzmtJkunOOuylpYY6NOU5jT8d7saqWSDv2Q== + dependencies: + "@parcel/diagnostic" "2.12.0" + "@parcel/plugin" "2.12.0" + "@parcel/rust" "2.12.0" + "@parcel/utils" "2.12.0" + clone "^2.1.1" + nullthrows "^1.1.1" + postcss-value-parser "^4.2.0" + semver "^7.5.2" + +"@parcel/transformer-posthtml@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/transformer-posthtml/-/transformer-posthtml-2.12.0.tgz" + integrity sha512-z6Z7rav/pcaWdeD+2sDUcd0mmNZRUvtHaUGa50Y2mr+poxrKilpsnFMSiWBT+oOqPt7j71jzDvrdnAF4XkCljg== + dependencies: + "@parcel/plugin" "2.12.0" + "@parcel/utils" "2.12.0" + nullthrows "^1.1.1" + posthtml "^0.16.5" + posthtml-parser "^0.10.1" + posthtml-render "^3.0.0" + semver "^7.5.2" + +"@parcel/transformer-raw@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/transformer-raw/-/transformer-raw-2.12.0.tgz" + integrity sha512-Ht1fQvXxix0NncdnmnXZsa6hra20RXYh1VqhBYZLsDfkvGGFnXIgO03Jqn4Z8MkKoa0tiNbDhpKIeTjyclbBxQ== + dependencies: + "@parcel/plugin" "2.12.0" + +"@parcel/transformer-react-refresh-wrap@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.12.0.tgz" + integrity sha512-GE8gmP2AZtkpBIV5vSCVhewgOFRhqwdM5Q9jNPOY5PKcM3/Ff0qCqDiTzzGLhk0/VMBrdjssrfZkVx6S/lHdJw== + dependencies: + "@parcel/plugin" "2.12.0" + "@parcel/utils" "2.12.0" + react-refresh "^0.9.0" + +"@parcel/transformer-sass@^2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/transformer-sass/-/transformer-sass-2.12.0.tgz" + integrity sha512-xLLoSLPST+2AHJwFRLl4foArDjjy6P1RChP3TxMU2MVS1sbKGJnfFhFpHAacH8ASjuGtu5rbpfpHRZePlvoZxw== + dependencies: + "@parcel/plugin" "2.12.0" + "@parcel/source-map" "^2.1.1" + sass "^1.38.0" + +"@parcel/transformer-svg@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/transformer-svg/-/transformer-svg-2.12.0.tgz" + integrity sha512-cZJqGRJ4JNdYcb+vj94J7PdOuTnwyy45dM9xqbIMH+HSiiIkfrMsdEwYft0GTyFTdsnf+hdHn3tau7Qa5hhX+A== + dependencies: + "@parcel/diagnostic" "2.12.0" + "@parcel/plugin" "2.12.0" + "@parcel/rust" "2.12.0" + nullthrows "^1.1.1" + posthtml "^0.16.5" + posthtml-parser "^0.10.1" + posthtml-render "^3.0.0" + semver "^7.5.2" + +"@parcel/types@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/types/-/types-2.12.0.tgz" + integrity sha512-8zAFiYNCwNTQcglIObyNwKfRYQK5ELlL13GuBOrSMxueUiI5ylgsGbTS1N7J3dAGZixHO8KhHGv5a71FILn9rQ== + dependencies: + "@parcel/cache" "2.12.0" + "@parcel/diagnostic" "2.12.0" + "@parcel/fs" "2.12.0" + "@parcel/package-manager" "2.12.0" + "@parcel/source-map" "^2.1.1" + "@parcel/workers" "2.12.0" + utility-types "^3.10.0" + +"@parcel/utils@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/utils/-/utils-2.12.0.tgz" + integrity sha512-z1JhLuZ8QmDaYoEIuUCVZlhcFrS7LMfHrb2OCRui5SQFntRWBH2fNM6H/fXXUkT9SkxcuFP2DUA6/m4+Gkz72g== + dependencies: + "@parcel/codeframe" "2.12.0" + "@parcel/diagnostic" "2.12.0" + "@parcel/logger" "2.12.0" + "@parcel/markdown-ansi" "2.12.0" + "@parcel/rust" "2.12.0" + "@parcel/source-map" "^2.1.1" + chalk "^4.1.0" + nullthrows "^1.1.1" + +"@parcel/watcher-linux-x64-glibc@2.4.1": + version "2.4.1" + resolved "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.4.1.tgz" + integrity sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg== + +"@parcel/watcher-linux-x64-musl@2.4.1": + version "2.4.1" + resolved "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.4.1.tgz" + integrity sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ== + +"@parcel/watcher@^2.0.7": + version "2.4.1" + resolved "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.4.1.tgz" + integrity sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA== + dependencies: + detect-libc "^1.0.3" + is-glob "^4.0.3" + micromatch "^4.0.5" + node-addon-api "^7.0.0" + optionalDependencies: + "@parcel/watcher-android-arm64" "2.4.1" + "@parcel/watcher-darwin-arm64" "2.4.1" + "@parcel/watcher-darwin-x64" "2.4.1" + "@parcel/watcher-freebsd-x64" "2.4.1" + "@parcel/watcher-linux-arm-glibc" "2.4.1" + "@parcel/watcher-linux-arm64-glibc" "2.4.1" + "@parcel/watcher-linux-arm64-musl" "2.4.1" + "@parcel/watcher-linux-x64-glibc" "2.4.1" + "@parcel/watcher-linux-x64-musl" "2.4.1" + "@parcel/watcher-win32-arm64" "2.4.1" + "@parcel/watcher-win32-ia32" "2.4.1" + "@parcel/watcher-win32-x64" "2.4.1" + +"@parcel/workers@2.12.0": + version "2.12.0" + resolved "https://registry.npmjs.org/@parcel/workers/-/workers-2.12.0.tgz" + integrity sha512-zv5We5Jmb+ZWXlU6A+AufyjY4oZckkxsZ8J4dvyWL0W8IQvGO1JB4FGeryyttzQv3RM3OxcN/BpTGPiDG6keBw== + dependencies: + "@parcel/diagnostic" "2.12.0" + "@parcel/logger" "2.12.0" + "@parcel/profiler" "2.12.0" + "@parcel/types" "2.12.0" + "@parcel/utils" "2.12.0" + nullthrows "^1.1.1" + +"@socketcomputer/backend@file:/home/lily/source/socket-revamp/backend": + version "1.0.0" + resolved "file:backend" + dependencies: + "@fastify/websocket" "^10.0.1" + "@julusian/jpeg-turbo" "^2.1.0" + "@socketcomputer/qemu" "*" + "@socketcomputer/shared" "*" + canvas "^2.11.2" + fastify "^4.26.2" + mnemonist "^0.39.8" + +"@socketcomputer/qemu@*", "@socketcomputer/qemu@file:/home/lily/source/socket-revamp/qemu": + version "1.0.0" + resolved "file:qemu" + dependencies: + canvas "^2.11.2" + execa "^8.0.1" + split "^1.0.1" + +"@socketcomputer/shared@*", "@socketcomputer/shared@file:/home/lily/source/socket-revamp/shared": + version "1.0.0" + resolved "file:shared" + +"@socketcomputer/webapp@file:/home/lily/source/socket-revamp/webapp": + version "1.0.0" + resolved "file:webapp" + dependencies: + nanoevents "^9.0.0" + +"@swc/core-linux-x64-gnu@1.4.11": + version "1.4.11" + resolved "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.11.tgz" + integrity sha512-WA1iGXZ2HpqM1OR9VCQZJ8sQ1KP2or9O4bO8vWZo6HZJIeoQSo7aa9waaCLRpkZvkng1ct/TF/l6ymqSNFXIzQ== + +"@swc/core-linux-x64-musl@1.4.11": + version "1.4.11" + resolved "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.11.tgz" + integrity sha512-UkVJToKf0owwQYRnGvjHAeYVDfeimCEcx0VQSbJoN7Iy0ckRZi7YPlmWJU31xtKvikE2bQWCOVe0qbSDqqcWXA== + +"@swc/core@^1.3.36": + version "1.4.11" + resolved "https://registry.npmjs.org/@swc/core/-/core-1.4.11.tgz" + integrity sha512-WKEakMZxkVwRdgMN4AMJ9K5nysY8g8npgQPczmjBeNK5In7QEAZAJwnyccrWwJZU0XjVeHn2uj+XbOKdDW17rg== + dependencies: + "@swc/counter" "^0.1.2" + "@swc/types" "^0.1.5" + optionalDependencies: + "@swc/core-darwin-arm64" "1.4.11" + "@swc/core-darwin-x64" "1.4.11" + "@swc/core-linux-arm-gnueabihf" "1.4.11" + "@swc/core-linux-arm64-gnu" "1.4.11" + "@swc/core-linux-arm64-musl" "1.4.11" + "@swc/core-linux-x64-gnu" "1.4.11" + "@swc/core-linux-x64-musl" "1.4.11" + "@swc/core-win32-arm64-msvc" "1.4.11" + "@swc/core-win32-ia32-msvc" "1.4.11" + "@swc/core-win32-x64-msvc" "1.4.11" + +"@swc/counter@^0.1.2", "@swc/counter@^0.1.3": + version "0.1.3" + resolved "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz" + integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== + +"@swc/helpers@^0.5.0": + version "0.5.8" + resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.8.tgz" + integrity sha512-lruDGw3pnfM3wmZHeW7JuhkGQaJjPyiKjxeGhdmfoOT53Ic9qb5JLDNaK2HUdl1zLDeX28H221UvKjfdvSLVMg== + dependencies: + tslib "^2.4.0" + +"@swc/types@^0.1.5": + version "0.1.6" + resolved "https://registry.npmjs.org/@swc/types/-/types-0.1.6.tgz" + integrity sha512-/JLo/l2JsT/LRd80C3HfbmVpxOAJ11FO2RCEslFrgzLltoP9j8XIbsyDcfCt2WWyX+CM96rBoNM+IToAkFOugg== + dependencies: + "@swc/counter" "^0.1.3" + +"@trysound/sax@0.2.0": + version "0.2.0" + resolved "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz" + integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== + +"@types/node@*", "@types/node@^20.12.2": + version "20.12.2" + resolved "https://registry.npmjs.org/@types/node/-/node-20.12.2.tgz" + integrity sha512-zQ0NYO87hyN6Xrclcqp7f8ZbXNbRfoGWNcMvHTPQp9UUrwI0mI7XBz+cu7/W6/VClYo2g63B0cjull/srU7LgQ== + dependencies: + undici-types "~5.26.4" + +"@types/ws@^8.5.10": + version "8.5.10" + resolved "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz" + integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== + dependencies: + "@types/node" "*" + +abbrev@1: + version "1.1.1" + resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + +abortcontroller-polyfill@^1.1.9: + version "1.7.5" + resolved "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz" + integrity sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ== + +abstract-logging@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz" + integrity sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA== + +agent-base@6: + version "6.0.2" + resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + +ajv@^8.0.0, ajv@^8.10.0, ajv@^8.11.0: + version "8.12.0" + resolved "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz" + integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + 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== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +archy@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz" + integrity sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw== + +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + +are-we-there-yet@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz" + integrity sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +atomic-sleep@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz" + integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== + +avvio@^8.3.0: + version "8.3.0" + resolved "https://registry.npmjs.org/avvio/-/avvio-8.3.0.tgz" + integrity sha512-VBVH0jubFr9LdFASy/vNtm5giTrnbVquWBhT0fyizuNK2rQ7e7ONU2plZQWUNqtE1EmxFEb+kbSkFRkstiaS9Q== + dependencies: + "@fastify/error" "^3.3.0" + archy "^1.0.0" + debug "^4.0.0" + fastq "^1.17.1" + +axios@^1.6.5: + version "1.6.8" + resolved "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz" + integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base-x@^3.0.8: + version "3.0.9" + resolved "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz" + integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== + dependencies: + safe-buffer "^5.0.1" + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.2, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist@^4.6.6, "browserslist@>= 4.21.0": + version "4.23.0" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz" + integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== + dependencies: + caniuse-lite "^1.0.30001587" + electron-to-chromium "^1.4.668" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" + +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +caniuse-lite@^1.0.30001587: + version "1.0.30001603" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001603.tgz" + integrity sha512-iL2iSS0eDILMb9n5yKQoTBim9jMZ0Yrk8g0N9K7UzYyWnfIKzXBZD5ngpM37ZcL/cv0Mli8XtVMRYMQAfFpi5Q== + +canvas@^2.11.2: + version "2.11.2" + resolved "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz" + integrity sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw== + dependencies: + "@mapbox/node-pre-gyp" "^1.0.0" + nan "^2.17.0" + simple-get "^3.0.3" + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +"chokidar@>=3.0.0 <4.0.0": + version "3.6.0" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + 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" + optionalDependencies: + fsevents "~2.3.2" + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + +chrome-trace-event@^1.0.2, chrome-trace-event@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz" + integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +clone@^2.1.1: + version "2.1.2" + resolved "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz" + integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== + +cmake-js@^7.0.0: + version "7.3.0" + resolved "https://registry.npmjs.org/cmake-js/-/cmake-js-7.3.0.tgz" + integrity sha512-dXs2zq9WxrV87bpJ+WbnGKv8WUBXDw8blNiwNHoRe/it+ptscxhQHKB1SJXa1w+kocLMeP28Tk4/eTCezg4o+w== + dependencies: + axios "^1.6.5" + debug "^4" + fs-extra "^11.2.0" + lodash.isplainobject "^4.0.6" + memory-stream "^1.0.0" + node-api-headers "^1.1.0" + npmlog "^6.0.2" + rc "^1.2.7" + semver "^7.5.4" + tar "^6.2.0" + url-join "^4.0.1" + which "^2.0.2" + yargs "^17.7.2" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-support@^1.1.2, color-support@^1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@^7.0.0, commander@^7.2.0: + version "7.2.0" + resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +console-control-strings@^1.0.0, console-control-strings@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + +cookie@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz" + integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== + +cosmiconfig@^8.0.0: + version "8.3.6" + resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz" + integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA== + dependencies: + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + path-type "^4.0.0" + +cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +css-select@^4.1.3: + version "4.3.0" + resolved "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz" + integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== + dependencies: + boolbase "^1.0.0" + css-what "^6.0.1" + domhandler "^4.3.1" + domutils "^2.8.0" + nth-check "^2.0.1" + +css-tree@^1.1.2, css-tree@^1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + +css-what@^6.0.1: + version "6.1.0" + resolved "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + +csso@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz" + integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== + dependencies: + css-tree "^1.1.2" + +debug@^4, debug@^4.0.0, debug@4: + version "4.3.4" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +decompress-response@^4.2.0: + version "4.2.1" + resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz" + integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== + dependencies: + mimic-response "^2.0.0" + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz" + integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== + +detect-libc@^2.0.0: + version "2.0.3" + resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz" + integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== + +detect-libc@^2.0.1: + version "2.0.3" + resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz" + integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== + +dom-serializer@^1.0.1: + version "1.4.1" + resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz" + integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + +domelementtype@^2.0.1, domelementtype@^2.2.0: + version "2.3.0" + resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^4.2.0, domhandler@^4.2.2, domhandler@^4.3.1: + version "4.3.1" + resolved "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz" + integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== + dependencies: + domelementtype "^2.2.0" + +domutils@^2.8.0: + version "2.8.0" + resolved "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + +dotenv-expand@^5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz" + integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== + +dotenv@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-7.0.0.tgz" + integrity sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g== + +duplexify@^4.1.2: + version "4.1.3" + resolved "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz" + integrity sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA== + dependencies: + end-of-stream "^1.4.1" + inherits "^2.0.3" + readable-stream "^3.1.1" + stream-shift "^1.0.2" + +electron-to-chromium@^1.4.668: + version "1.4.723" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.723.tgz" + integrity sha512-rxFVtrMGMFROr4qqU6n95rUi9IlfIm+lIAt+hOToy/9r6CDv0XiEcQdC3VP71y1pE5CFTzKV0RvxOGYCPWWHPw== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + +entities@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz" + integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +escalade@^3.1.1: + version "3.1.2" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + +events@^3.3.0: + version "3.3.0" + resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +execa@^8.0.1: + version "8.0.1" + resolved "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz" + integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^8.0.1" + human-signals "^5.0.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^4.1.0" + strip-final-newline "^3.0.0" + +fast-content-type-parse@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz" + integrity sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ== + +fast-decode-uri-component@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz" + integrity sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stringify@^5.7.0, fast-json-stringify@^5.8.0: + version "5.13.0" + resolved "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-5.13.0.tgz" + integrity sha512-XjTDWKHP3GoMQUOfnjYUbqeHeEt+PvYgvBdG2fRSmYaORILbSr8xTJvZX+w1YSAP5pw2NwKrGRmQleYueZEoxw== + dependencies: + "@fastify/merge-json-schemas" "^0.1.0" + ajv "^8.10.0" + ajv-formats "^2.1.1" + fast-deep-equal "^3.1.3" + fast-uri "^2.1.0" + json-schema-ref-resolver "^1.0.1" + rfdc "^1.2.0" + +fast-querystring@^1.0.0: + version "1.1.2" + resolved "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz" + integrity sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg== + dependencies: + fast-decode-uri-component "^1.0.1" + +fast-redact@^3.1.1: + version "3.5.0" + resolved "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz" + integrity sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A== + +fast-uri@^2.0.0, fast-uri@^2.1.0: + version "2.3.0" + resolved "https://registry.npmjs.org/fast-uri/-/fast-uri-2.3.0.tgz" + integrity sha512-eel5UKGn369gGEWOqBShmFJWfq/xSJvsgDzgLYC845GneayWvXBf0lJCBn5qTABfewy1ZDPoaR5OZCP+kssfuw== + +fastify-plugin@^4.0.0: + version "4.5.1" + resolved "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-4.5.1.tgz" + integrity sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ== + +fastify@^4.26.2: + version "4.26.2" + resolved "https://registry.npmjs.org/fastify/-/fastify-4.26.2.tgz" + integrity sha512-90pjTuPGrfVKtdpLeLzND5nyC4woXZN5VadiNQCicj/iJU4viNHKhsAnb7jmv1vu2IzkLXyBiCzdWuzeXgQ5Ug== + dependencies: + "@fastify/ajv-compiler" "^3.5.0" + "@fastify/error" "^3.4.0" + "@fastify/fast-json-stringify-compiler" "^4.3.0" + abstract-logging "^2.0.1" + avvio "^8.3.0" + fast-content-type-parse "^1.1.0" + fast-json-stringify "^5.8.0" + find-my-way "^8.0.0" + light-my-request "^5.11.0" + pino "^8.17.0" + process-warning "^3.0.0" + proxy-addr "^2.0.7" + rfdc "^1.3.0" + secure-json-parse "^2.7.0" + semver "^7.5.4" + toad-cache "^3.3.0" + +fastq@^1.17.1: + version "1.17.1" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + dependencies: + reusify "^1.0.4" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-my-way@^8.0.0: + version "8.1.0" + resolved "https://registry.npmjs.org/find-my-way/-/find-my-way-8.1.0.tgz" + integrity sha512-41QwjCGcVTODUmLLqTMeoHeiozbMXYMAE1CKFiDyi9zVZ2Vjh0yz3MF0WQZoIb+cmzP/XlbFjlF2NtJmvZHznA== + dependencies: + fast-deep-equal "^3.1.3" + fast-querystring "^1.0.0" + safe-regex2 "^2.0.0" + +follow-redirects@^1.15.6: + version "1.15.6" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fs-extra@^11.2.0: + version "11.2.0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz" + integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +gauge@^3.0.0: + version "3.0.2" + resolved "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz" + integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + +gauge@^4.0.3: + version "4.0.4" + resolved "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz" + integrity sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.3" + console-control-strings "^1.1.0" + has-unicode "^2.0.1" + signal-exit "^3.0.7" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.5" + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-port@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/get-port/-/get-port-4.2.0.tgz" + integrity sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw== + +get-stream@^8.0.1: + version "8.0.1" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz" + integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@^7.1.3: + version "7.2.3" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^13.2.0: + version "13.24.0" + resolved "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +graceful-fs@^4.1.6, graceful-fs@^4.2.0: + version "4.2.11" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + +htmlnano@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.0.tgz" + integrity sha512-jVGRE0Ep9byMBKEu0Vxgl8dhXYOUk0iNQ2pjsG+BcRB0u0oDF5A9p/iBGMg/PGKYUyMD0OAGu8dVT5Lzj8S58g== + dependencies: + cosmiconfig "^8.0.0" + posthtml "^0.16.5" + timsort "^0.3.0" + +htmlparser2@^7.1.1: + version "7.2.0" + resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz" + integrity sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.2" + domutils "^2.8.0" + entities "^3.0.1" + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +human-signals@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz" + integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== + +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +immutable@^4.0.0: + version "4.3.5" + resolved "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz" + integrity sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw== + +import-fresh@^3.3.0: + version "3.3.0" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@^2.0.3, inherits@2: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@~1.3.0: + version "1.3.8" + resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + 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== + +is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-json@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz" + integrity sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz" + integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-ref-resolver@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-1.0.1.tgz" + integrity sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw== + dependencies: + fast-deep-equal "^3.1.3" + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json5@^2.2.0, json5@^2.2.1: + version "2.2.3" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +light-my-request@^5.11.0: + version "5.12.0" + resolved "https://registry.npmjs.org/light-my-request/-/light-my-request-5.12.0.tgz" + integrity sha512-P526OX6E7aeCIfw/9UyJNsAISfcFETghysaWHQAlQYayynShT08MOj4c6fBCvTWBrHXSvqBAKDp3amUPSCQI4w== + dependencies: + cookie "^0.6.0" + process-warning "^3.0.0" + set-cookie-parser "^2.4.1" + +lightningcss-linux-x64-gnu@1.24.1: + version "1.24.1" + resolved "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.24.1.tgz" + integrity sha512-TYdEsC63bHV0h47aNRGN3RiK7aIeco3/keN4NkoSQ5T8xk09KHuBdySltWAvKLgT8JvR+ayzq8ZHnL1wKWY0rw== + +lightningcss-linux-x64-musl@1.24.1: + version "1.24.1" + resolved "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.24.1.tgz" + integrity sha512-HLfzVik3RToot6pQ2Rgc3JhfZkGi01hFetHt40HrUMoeKitLoqUUT5owM6yTZPTytTUW9ukLBJ1pc3XNMSvlLw== + +lightningcss@^1.22.1: + version "1.24.1" + resolved "https://registry.npmjs.org/lightningcss/-/lightningcss-1.24.1.tgz" + integrity sha512-kUpHOLiH5GB0ERSv4pxqlL0RYKnOXtgGtVe7shDGfhS0AZ4D1ouKFYAcLcZhql8aMspDNzaUCumGHZ78tb2fTg== + dependencies: + detect-libc "^1.0.3" + optionalDependencies: + lightningcss-darwin-arm64 "1.24.1" + lightningcss-darwin-x64 "1.24.1" + lightningcss-freebsd-x64 "1.24.1" + lightningcss-linux-arm-gnueabihf "1.24.1" + lightningcss-linux-arm64-gnu "1.24.1" + lightningcss-linux-arm64-musl "1.24.1" + lightningcss-linux-x64-gnu "1.24.1" + lightningcss-linux-x64-musl "1.24.1" + lightningcss-win32-x64-msvc "1.24.1" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +lmdb@2.8.5: + version "2.8.5" + resolved "https://registry.npmjs.org/lmdb/-/lmdb-2.8.5.tgz" + integrity sha512-9bMdFfc80S+vSldBmG3HOuLVHnxRdNTlpzR6QDnzqCQtCzGUEAGTzBKYMeIM+I/sU4oZfgbcbS7X7F65/z/oxQ== + dependencies: + msgpackr "^1.9.5" + node-addon-api "^6.1.0" + node-gyp-build-optional-packages "5.1.1" + ordered-binary "^1.4.1" + weak-lru-cache "^1.2.2" + optionalDependencies: + "@lmdb/lmdb-darwin-arm64" "2.8.5" + "@lmdb/lmdb-darwin-x64" "2.8.5" + "@lmdb/lmdb-linux-arm" "2.8.5" + "@lmdb/lmdb-linux-arm64" "2.8.5" + "@lmdb/lmdb-linux-x64" "2.8.5" + "@lmdb/lmdb-win32-x64" "2.8.5" + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-dir@^3.1.0: + 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== + dependencies: + semver "^6.0.0" + +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + +memory-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/memory-stream/-/memory-stream-1.0.0.tgz" + integrity sha512-Wm13VcsPIMdG96dzILfij09PvuS3APtcKNh7M28FsCA/w6+1mjR7hhPmfFNoilX9xU7wTdhsH5lJAm6XNzdtww== + dependencies: + readable-stream "^3.4.0" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +micromatch@^4.0.5: + version "4.0.5" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mimic-fn@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz" + integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== + +mimic-response@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz" + integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== + +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0: + version "1.2.8" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +minipass@^3.0.0: + version "3.3.6" + resolved "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +mnemonist@^0.39.8: + version "0.39.8" + resolved "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.8.tgz" + integrity sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ== + dependencies: + obliterator "^2.0.1" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +msgpackr-extract@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.2.tgz" + integrity sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A== + dependencies: + node-gyp-build-optional-packages "5.0.7" + optionalDependencies: + "@msgpackr-extract/msgpackr-extract-darwin-arm64" "3.0.2" + "@msgpackr-extract/msgpackr-extract-darwin-x64" "3.0.2" + "@msgpackr-extract/msgpackr-extract-linux-arm" "3.0.2" + "@msgpackr-extract/msgpackr-extract-linux-arm64" "3.0.2" + "@msgpackr-extract/msgpackr-extract-linux-x64" "3.0.2" + "@msgpackr-extract/msgpackr-extract-win32-x64" "3.0.2" + +msgpackr@^1.9.5, msgpackr@^1.9.9: + version "1.10.1" + resolved "https://registry.npmjs.org/msgpackr/-/msgpackr-1.10.1.tgz" + integrity sha512-r5VRLv9qouXuLiIBrLpl2d5ZvPt8svdQTl5/vMvE4nzDMyEX4sgW5yWhuBBj5UmgwOTWj8CIdSXn5sAfsHAWIQ== + optionalDependencies: + msgpackr-extract "^3.0.2" + +nan@^2.17.0: + version "2.19.0" + resolved "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz" + integrity sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw== + +nanoevents@^9.0.0: + version "9.0.0" + resolved "https://registry.npmjs.org/nanoevents/-/nanoevents-9.0.0.tgz" + integrity sha512-X8pU7IOpgKXVLPxYUI55ymXc8XuBE+uypfEyEFBtHkD1EX9KavYTVc+vXZHFyHKzA1TaZoVDqklLdQBBrxIuAw== + +node-addon-api@^5.0.0: + version "5.1.0" + resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz" + integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== + +node-addon-api@^6.1.0: + version "6.1.0" + resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz" + integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA== + +node-addon-api@^7.0.0: + version "7.1.0" + resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz" + integrity sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g== + +node-api-headers@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/node-api-headers/-/node-api-headers-1.1.0.tgz" + integrity sha512-ucQW+SbYCUPfprvmzBsnjT034IGRB2XK8rRc78BgjNKhTdFKgAwAmgW704bKIBmcYW48it0Gkjpkd39Azrwquw== + +node-fetch@^2.6.7: + version "2.7.0" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + +node-gyp-build-optional-packages@5.0.7: + version "5.0.7" + resolved "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.7.tgz" + integrity sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w== + +node-gyp-build-optional-packages@5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz" + integrity sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw== + dependencies: + detect-libc "^2.0.1" + +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^5.1.0: + version "5.3.0" + resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz" + integrity sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ== + dependencies: + path-key "^4.0.0" + +npmlog@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + +npmlog@^6.0.2: + version "6.0.2" + resolved "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz" + integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== + dependencies: + are-we-there-yet "^3.0.0" + console-control-strings "^1.1.0" + gauge "^4.0.3" + set-blocking "^2.0.0" + +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + +nullthrows@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz" + integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw== + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +obliterator@^2.0.1: + version "2.0.4" + resolved "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz" + integrity sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ== + +on-exit-leak-free@^2.1.0: + version "2.1.2" + resolved "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz" + integrity sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA== + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz" + integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== + dependencies: + mimic-fn "^4.0.0" + +ordered-binary@^1.4.1: + version "1.5.1" + resolved "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.1.tgz" + integrity sha512-5VyHfHY3cd0iza71JepYG50My+YUbrFtGoUz2ooEydPyPM7Aai/JW098juLr+RG6+rDJuzNNTsEQu2DZa1A41A== + +parcel@^2.12.0: + version "2.12.0" + resolved "https://registry.npmjs.org/parcel/-/parcel-2.12.0.tgz" + integrity sha512-W+gxAq7aQ9dJIg/XLKGcRT0cvnStFAQHPaI0pvD0U2l6IVLueUAm3nwN7lkY62zZNmlvNx6jNtE4wlbS+CyqSg== + dependencies: + "@parcel/config-default" "2.12.0" + "@parcel/core" "2.12.0" + "@parcel/diagnostic" "2.12.0" + "@parcel/events" "2.12.0" + "@parcel/fs" "2.12.0" + "@parcel/logger" "2.12.0" + "@parcel/package-manager" "2.12.0" + "@parcel/reporter-cli" "2.12.0" + "@parcel/reporter-dev-server" "2.12.0" + "@parcel/reporter-tracer" "2.12.0" + "@parcel/utils" "2.12.0" + chalk "^4.1.0" + commander "^7.0.0" + get-port "^4.2.0" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-key@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz" + integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pino-abstract-transport@v1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz" + integrity sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA== + dependencies: + readable-stream "^4.0.0" + split2 "^4.0.0" + +pino-std-serializers@^6.0.0: + version "6.2.2" + resolved "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz" + integrity sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA== + +pino@^8.17.0: + version "8.19.0" + resolved "https://registry.npmjs.org/pino/-/pino-8.19.0.tgz" + integrity sha512-oswmokxkav9bADfJ2ifrvfHUwad6MLp73Uat0IkQWY3iAw5xTRoznXbXksZs8oaOUMpmhVWD+PZogNzllWpJaA== + dependencies: + atomic-sleep "^1.0.0" + fast-redact "^3.1.1" + on-exit-leak-free "^2.1.0" + pino-abstract-transport v1.1.0 + pino-std-serializers "^6.0.0" + process-warning "^3.0.0" + quick-format-unescaped "^4.0.3" + real-require "^0.2.0" + safe-stable-stringify "^2.3.1" + sonic-boom "^3.7.0" + thread-stream "^2.0.0" + +pkg-prebuilds@~0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/pkg-prebuilds/-/pkg-prebuilds-0.1.0.tgz" + integrity sha512-ALsGSiwO6EDvjrrFRiv7Q6HZPrqCgTpNxQMFs3P4Ic25cP94DmLy0iGvZDlJmQBbq2IS8xkZrifwkoOHIetY9Q== + dependencies: + yargs "^17.5.1" + +postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +posthtml-parser@^0.10.1: + version "0.10.2" + resolved "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.10.2.tgz" + integrity sha512-PId6zZ/2lyJi9LiKfe+i2xv57oEjJgWbsHGGANwos5AvdQp98i6AtamAl8gzSVFGfQ43Glb5D614cvZf012VKg== + dependencies: + htmlparser2 "^7.1.1" + +posthtml-parser@^0.11.0: + version "0.11.0" + resolved "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz" + integrity sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw== + dependencies: + htmlparser2 "^7.1.1" + +posthtml-render@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz" + integrity sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA== + dependencies: + is-json "^2.0.1" + +posthtml@^0.16.4, posthtml@^0.16.5: + version "0.16.6" + resolved "https://registry.npmjs.org/posthtml/-/posthtml-0.16.6.tgz" + integrity sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ== + dependencies: + posthtml-parser "^0.11.0" + posthtml-render "^3.0.0" + +prettier@^3.2.5: + version "3.2.5" + resolved "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz" + integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== + +process-warning@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz" + integrity sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + +proxy-addr@^2.0.7: + version "2.0.7" + resolved "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +quick-format-unescaped@^4.0.3: + version "4.0.4" + resolved "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz" + integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== + +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +react-error-overlay@6.0.9: + version "6.0.9" + resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz" + integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew== + +react-refresh@^0.9.0: + version "0.9.0" + resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.9.0.tgz" + integrity sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ== + +readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: + version "3.6.2" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readable-stream@^4.0.0: + version "4.5.2" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz" + integrity sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g== + dependencies: + abort-controller "^3.0.0" + buffer "^6.0.3" + events "^3.3.0" + process "^0.11.10" + string_decoder "^1.3.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +real-require@^0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz" + integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== + +regenerator-runtime@^0.13.7: + version "0.13.11" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +ret@~0.2.0: + version "0.2.2" + resolved "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz" + integrity sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rfdc@^1.2.0, rfdc@^1.3.0: + version "1.3.1" + resolved "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz" + integrity sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +safe-buffer@^5.0.1, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-regex2@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/safe-regex2/-/safe-regex2-2.0.0.tgz" + integrity sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ== + dependencies: + ret "~0.2.0" + +safe-stable-stringify@^2.3.1: + version "2.4.3" + resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz" + integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== + +sass@^1.38.0: + version "1.72.0" + resolved "https://registry.npmjs.org/sass/-/sass-1.72.0.tgz" + integrity sha512-Gpczt3WA56Ly0Mn8Sl21Vj94s1axi9hDIzDFn9Ph9x3C3p4nNyvsqJoQyVXKou6cBlfFWEgRW4rT8Tb4i3XnVA== + dependencies: + chokidar ">=3.0.0 <4.0.0" + immutable "^4.0.0" + source-map-js ">=0.6.2 <2.0.0" + +secure-json-parse@^2.7.0: + version "2.7.0" + resolved "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz" + integrity sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw== + +semver@^6.0.0: + version "6.3.1" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.3.5, semver@^7.5.2, semver@^7.5.4: + version "7.6.0" + resolved "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + dependencies: + lru-cache "^6.0.0" + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + +set-cookie-parser@^2.4.1: + version "2.6.0" + resolved "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz" + integrity sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^3.0.0, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^3.0.3: + version "3.1.1" + resolved "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz" + integrity sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA== + dependencies: + decompress-response "^4.2.0" + once "^1.3.1" + simple-concat "^1.0.0" + +sonic-boom@^3.7.0: + version "3.8.0" + resolved "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.0.tgz" + integrity sha512-ybz6OYOUjoQQCQ/i4LU8kaToD8ACtYP+Cj5qd2AO36bwbdewxWJ3ArmJ2cr6AvxlL2o0PqnCcPGUgkILbfkaCA== + dependencies: + atomic-sleep "^1.0.0" + +"source-map-js@>=0.6.2 <2.0.0": + version "1.2.0" + resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz" + integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== + +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +split@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/split/-/split-1.0.1.tgz" + integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== + dependencies: + through "2" + +split2@^4.0.0: + version "4.2.0" + resolved "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz" + integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== + +srcset@4, srcset@4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz" + integrity sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw== + +stable@^0.1.8: + version "0.1.8" + resolved "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz" + integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== + +stream-shift@^1.0.2: + version "1.0.3" + resolved "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz" + integrity sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ== + +string_decoder@^1.1.1, string_decoder@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-final-newline@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz" + integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +svgo@^2.4.0, svgo@^3.0.2: + version "2.8.0" + resolved "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz" + integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== + dependencies: + "@trysound/sax" "0.2.0" + commander "^7.2.0" + css-select "^4.1.3" + css-tree "^1.1.3" + csso "^4.2.0" + picocolors "^1.0.0" + stable "^0.1.8" + +tar@^6.1.11, tar@^6.2.0: + version "6.2.1" + resolved "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +term-size@^2.2.1: + version "2.2.1" + resolved "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz" + integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg== + +thread-stream@^2.0.0: + version "2.4.1" + resolved "https://registry.npmjs.org/thread-stream/-/thread-stream-2.4.1.tgz" + integrity sha512-d/Ex2iWd1whipbT681JmTINKw0ZwOUBZm7+Gjs64DHuX34mmw8vJL2bFAaNacaW72zYiTJxSHi5abUuOi5nsfg== + dependencies: + real-require "^0.2.0" + +through@2: + version "2.3.8" + resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + +timsort@^0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz" + integrity sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A== + +to-regex-range@^5.0.1: + 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== + dependencies: + is-number "^7.0.0" + +toad-cache@^3.3.0: + version "3.7.0" + resolved "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz" + integrity sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw== + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +tslib@^2.4.0: + version "2.6.2" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +typescript@^5.4.3, typescript@>=4.9.5: + version "5.4.3" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz" + integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + +update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +url-join@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz" + integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +utility-types@^3.10.0: + version "3.11.0" + resolved "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz" + integrity sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw== + +weak-lru-cache@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz" + integrity sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +which@^2.0.1, which@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.2, wide-align@^1.1.5: + version "1.1.5" + resolved "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +ws@^8.0.0: + version "8.16.0" + resolved "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz" + integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.5.1, yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1"