commit 071b5316795e9d0bea62abd1ed1e69b08d99af28 Author: modeco80 Date: Tue Apr 2 07:43:54 2024 -0400 initial commit 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 0000000..40511b4 Binary files /dev/null and b/webapp/static/macbook.png differ 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"