From 432e75d42ab89753e09a7001ddff63497e600c02 Mon Sep 17 00:00:00 2001 From: modeco80 Date: Tue, 16 Jul 2024 08:29:52 -0400 Subject: [PATCH] cvmts: Use npm versions of superqemu/nodejs-rfb. We publish them now, so let's use them in cvmts! Additionally, this removes the 'shared' module entirely, since it has little purpose anymore. The logger is replaced with pino (because superqemu uses pino for logging itself). --- .gitmodules | 3 - Justfile | 3 - cvmts/package.json | 5 +- cvmts/src/AuthManager.ts | 3 - cvmts/src/CollabVMServer.ts | 27 ++- cvmts/src/GeoIPDownloader.ts | 184 ++++++++------- cvmts/src/IPData.ts | 6 +- cvmts/src/JPEGEncoder.ts | 2 +- cvmts/src/TCP/TCPServer.ts | 9 +- cvmts/src/User.ts | 10 +- cvmts/src/VM.ts | 4 +- cvmts/src/VMDisplay.ts | 17 +- cvmts/src/VNCVM/VNCVM.ts | 27 ++- cvmts/src/WebSocket/WSClient.ts | 1 - cvmts/src/WebSocket/WSServer.ts | 15 +- cvmts/src/index.ts | 23 +- nodejs-rfb | 1 - package.json | 2 - qemu/package.json | 29 --- qemu/src/QemuDisplay.ts | 151 ------------ qemu/src/QemuUtil.ts | 41 ---- qemu/src/QemuVM.ts | 271 --------------------- qemu/src/QmpClient.ts | 170 -------------- qemu/src/index.ts | 5 - qemu/tsconfig.json | 1 - shared/package.json | 28 --- shared/src/Logger.ts | 50 ---- shared/src/StringLike.ts | 8 - shared/src/format.ts | 77 ------ shared/src/index.ts | 24 -- shared/tsconfig.json | 1 - yarn.lock | 402 +++++++++++++++++++++++--------- 32 files changed, 469 insertions(+), 1131 deletions(-) delete mode 160000 nodejs-rfb delete mode 100644 qemu/package.json delete mode 100644 qemu/src/QemuDisplay.ts delete mode 100644 qemu/src/QemuUtil.ts delete mode 100644 qemu/src/QemuVM.ts delete mode 100644 qemu/src/QmpClient.ts delete mode 100644 qemu/src/index.ts delete mode 120000 qemu/tsconfig.json delete mode 100644 shared/package.json delete mode 100644 shared/src/Logger.ts delete mode 100644 shared/src/StringLike.ts delete mode 100644 shared/src/format.ts delete mode 100644 shared/src/index.ts delete mode 120000 shared/tsconfig.json diff --git a/.gitmodules b/.gitmodules index 08a9b15..925776f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "nodejs-rfb"] - path = nodejs-rfb - url = https://github.com/computernewb/nodejs-rfb [submodule "collab-vm-1.2-binary-protocol"] path = collab-vm-1.2-binary-protocol url = https://github.com/computernewb/collab-vm-1.2-binary-protocol diff --git a/Justfile b/Justfile index fe019e4..684a827 100644 --- a/Justfile +++ b/Justfile @@ -1,8 +1,5 @@ all: yarn workspace @cvmts/cvm-rs run build - yarn workspace @computernewb/nodejs-rfb run build - yarn workspace @cvmts/shared run build - yarn workspace @cvmts/qemu run build yarn workspace @cvmts/collab-vm-1.2-binary-protocol run build yarn workspace @cvmts/cvmts run build diff --git a/cvmts/package.json b/cvmts/package.json index 3602618..c998bd0 100644 --- a/cvmts/package.json +++ b/cvmts/package.json @@ -11,12 +11,14 @@ "author": "Elijah R, modeco80", "license": "GPL-3.0", "dependencies": { + "@computernewb/nodejs-rfb": "^0.3.0", + "@computernewb/superqemu": "^0.1.0", "@cvmts/cvm-rs": "*", - "@cvmts/qemu": "*", "@maxmind/geoip2-node": "^5.0.0", "execa": "^8.0.1", "mnemonist": "^0.39.5", "msgpackr": "^1.10.2", + "pino": "^9.3.1", "sharp": "^0.33.3", "toml": "^3.0.0", "ws": "^8.14.1" @@ -24,6 +26,7 @@ "devDependencies": { "@types/node": "^20.12.5", "@types/ws": "^8.5.5", + "pino-pretty": "^11.2.1", "prettier": "^3.2.5", "typescript": "^5.4.4" } diff --git a/cvmts/src/AuthManager.ts b/cvmts/src/AuthManager.ts index 8cfe96e..4a6ff7f 100644 --- a/cvmts/src/AuthManager.ts +++ b/cvmts/src/AuthManager.ts @@ -1,12 +1,9 @@ -import { Logger } from '@cvmts/shared'; import { Rank, User } from './User.js'; export default class AuthManager { apiEndpoint: string; secretKey: string; - private logger = new Logger('CVMTS.AuthMan'); - constructor(apiEndpoint: string, secretKey: string) { this.apiEndpoint = apiEndpoint; this.secretKey = secretKey; diff --git a/cvmts/src/CollabVMServer.ts b/cvmts/src/CollabVMServer.ts index abb2b98..b61c7af 100644 --- a/cvmts/src/CollabVMServer.ts +++ b/cvmts/src/CollabVMServer.ts @@ -6,18 +6,20 @@ import * as cvm from '@cvmts/cvm-rs'; import CircularBuffer from 'mnemonist/circular-buffer.js'; import Queue from 'mnemonist/queue.js'; import { createHash } from 'crypto'; -import { VMState, QemuVM, QemuVmDefinition } from '@cvmts/qemu'; +import { VMState, QemuVM, QemuVmDefinition } from '@computernewb/superqemu'; import { IPDataManager } from './IPData.js'; import { readFileSync } from 'node:fs'; import path from 'node:path'; import AuthManager from './AuthManager.js'; -import { Size, Rect, Logger } from '@cvmts/shared'; import { JPEGEncoder } from './JPEGEncoder.js'; import VM from './VM.js'; import { ReaderModel } from '@maxmind/geoip2-node'; import * as msgpack from 'msgpackr'; import { CollabVMProtocolMessage, CollabVMProtocolMessageType } from '@cvmts/collab-vm-1.2-binary-protocol'; +import { Size, Rect } from './VMDisplay.js'; +import pino from 'pino'; + // Instead of strange hacks we can just use nodejs provided // import.meta properties, which have existed since LTS if not before const __dirname = import.meta.dirname; @@ -36,6 +38,7 @@ type VoteTally = { no: number; }; + export default class CollabVMServer { private Config: IConfig; @@ -87,7 +90,7 @@ export default class CollabVMServer { // Geoip private geoipReader: ReaderModel | null; - private logger = new Logger('CVMTS.Server'); + private logger = pino({ name: 'CVMTS.Server' }); constructor(config: IConfig, vm: VM, auth: AuthManager | null, geoipReader: ReaderModel | null) { this.Config = config; @@ -121,7 +124,7 @@ export default class CollabVMServer { if (config.vm.type == 'qemu') { (vm as QemuVM).on('statechange', (newState: VMState) => { if (newState == VMState.Started) { - self.logger.Info('VM started'); + self.logger.info('VM started'); // well aware this sucks but whatever self.VM.GetDisplay().on('resize', (size: Size) => self.OnDisplayResized(size)); self.VM.GetDisplay().on('rect', (rect: Rect) => self.OnDisplayRectangle(rect)); @@ -129,7 +132,7 @@ export default class CollabVMServer { if (newState == VMState.Stopped) { setTimeout(async () => { - self.logger.Info('restarting VM'); + self.logger.info('restarting VM'); await self.VM.Start(); }, kRestartTimeout); } @@ -154,7 +157,7 @@ export default class CollabVMServer { try { user.countryCode = this.geoipReader!.country(user.IP.address).country!.isoCode; } catch (error) { - this.logger.Warning(`Failed to get country code for ${user.IP.address}: ${(error as Error).message}`); + this.logger.warn(`Failed to get country code for ${user.IP.address}: ${(error as Error).message}`); } } user.socket.on('msg', (msg: string) => this.onMessage(user, msg)); @@ -179,7 +182,7 @@ export default class CollabVMServer { this.clients.splice(clientIndex, 1); - this.logger.Info(`Disconnect From ${user.IP.address}${user.username ? ` with username ${user.username}` : ''}`); + this.logger.info(`Disconnect From ${user.IP.address}${user.username ? ` with username ${user.username}` : ''}`); if (!user.username) return; if (this.TurnQueue.toArray().indexOf(user) !== -1) { var hadturn = this.TurnQueue.peek() === user; @@ -204,7 +207,7 @@ export default class CollabVMServer { try { let res = await this.auth!.Authenticate(msgArr[1], client); if (res.clientSuccess) { - this.logger.Info(`${client.IP.address} logged in as ${res.username}`); + this.logger.info(`${client.IP.address} logged in as ${res.username}`); client.sendMsg(cvm.guacEncode('login', '1')); let old = this.clients.find((c) => c.username === res.username); if (old) { @@ -236,7 +239,7 @@ export default class CollabVMServer { } } } catch (err) { - this.logger.Error(`Error authenticating client ${client.IP.address}: ${(err as Error).message}`); + this.logger.error(`Error authenticating client ${client.IP.address}: ${(err as Error).message}`); // for now? client.sendMsg(cvm.guacEncode('login', '0', 'There was an internal error while authenticating. Please let a staff member know as soon as possible')); } @@ -679,7 +682,7 @@ export default class CollabVMServer { } } catch (err) { // No - this.logger.Error(`User ${user?.IP.address} ${user?.username ? `with username ${user?.username}` : ''} sent broken Guacamole: ${err as Error}`); + this.logger.error(`User ${user?.IP.address} ${user?.username ? `with username ${user?.username}` : ''} sent broken Guacamole: ${err as Error}`); user?.kick(); } } @@ -721,10 +724,10 @@ export default class CollabVMServer { client.sendMsg(cvm.guacEncode('rename', '0', status, client.username!, client.rank.toString())); if (hadName) { - this.logger.Info(`Rename ${client.IP.address} from ${oldname} to ${client.username}`); + this.logger.info(`Rename ${client.IP.address} from ${oldname} to ${client.username}`); if (announce) this.clients.forEach((c) => c.sendMsg(cvm.guacEncode('rename', '1', oldname, client.username!, client.rank.toString()))); } else { - this.logger.Info(`Rename ${client.IP.address} to ${client.username}`); + this.logger.info(`Rename ${client.IP.address} to ${client.username}`); if (announce) this.clients.forEach((c) => { c.sendMsg(cvm.guacEncode('adduser', '1', client.username!, client.rank.toString())); diff --git a/cvmts/src/GeoIPDownloader.ts b/cvmts/src/GeoIPDownloader.ts index ef297cf..e53a067 100644 --- a/cvmts/src/GeoIPDownloader.ts +++ b/cvmts/src/GeoIPDownloader.ts @@ -1,4 +1,3 @@ -import { Logger } from '@cvmts/shared'; import { Reader, ReaderModel } from '@maxmind/geoip2-node'; import * as fs from 'fs/promises'; import * as path from 'node:path'; @@ -6,102 +5,101 @@ import { Readable } from 'node:stream'; import { ReadableStream } from 'node:stream/web'; import { finished } from 'node:stream/promises'; import { execa } from 'execa'; +import pino from 'pino'; export default class GeoIPDownloader { - private directory: string; - private accountID: string; - private licenseKey: string; - private logger: Logger - constructor(filename: string, accountID: string, licenseKey: string) { - this.directory = filename; - if (!this.directory.endsWith('/')) this.directory += '/'; - this.accountID = accountID; - this.licenseKey = licenseKey; - this.logger = new Logger('CVMTS.GeoIPDownloader'); - } + private directory: string; + private accountID: string; + private licenseKey: string; + private logger = pino({ name: 'CVMTS.GeoIPDownloader' }); + constructor(filename: string, accountID: string, licenseKey: string) { + this.directory = filename; + if (!this.directory.endsWith('/')) this.directory += '/'; + this.accountID = accountID; + this.licenseKey = licenseKey; + } - private genAuthHeader(): string { - return `Basic ${Buffer.from(`${this.accountID}:${this.licenseKey}`).toString('base64')}`; - } + private genAuthHeader(): string { + return `Basic ${Buffer.from(`${this.accountID}:${this.licenseKey}`).toString('base64')}`; + } - private async ensureDirectoryExists(): Promise { - let stat; - try { - stat = await fs.stat(this.directory); - } - catch (e) { - var error = e as NodeJS.ErrnoException; - if (error.code === 'ENOTDIR') { - this.logger.Warning('File exists at GeoIP directory path, unlinking...'); - await fs.unlink(this.directory.substring(0, this.directory.length - 1)); - } else if (error.code !== 'ENOENT') { - this.logger.Error('Failed to access GeoIP directory: {0}', error.message); - process.exit(1); - } - this.logger.Info('Creating GeoIP directory: {0}', this.directory); - await fs.mkdir(this.directory, { recursive: true }); - return; - } - } + private async ensureDirectoryExists(): Promise { + let stat; + try { + stat = await fs.stat(this.directory); + } catch (e) { + var error = e as NodeJS.ErrnoException; + if (error.code === 'ENOTDIR') { + this.logger.warn('File exists at GeoIP directory path, unlinking...'); + await fs.unlink(this.directory.substring(0, this.directory.length - 1)); + } else if (error.code !== 'ENOENT') { + this.logger.error('Failed to access GeoIP directory: {0}', error.message); + process.exit(1); + } + this.logger.info('Creating GeoIP directory: {0}', this.directory); + await fs.mkdir(this.directory, { recursive: true }); + return; + } + } - async getGeoIPReader(): Promise { - await this.ensureDirectoryExists(); - let dbpath = path.join(this.directory, (await this.getLatestVersion()).replace('.tar.gz', ''), 'GeoLite2-Country.mmdb'); - try { - await fs.access(dbpath, fs.constants.F_OK | fs.constants.R_OK); - this.logger.Info('Loading cached GeoIP database: {0}', dbpath); - } catch (ex) { - var error = ex as NodeJS.ErrnoException; - if (error.code === 'ENOENT') { - await this.downloadLatestDatabase(); - } else { - this.logger.Error('Failed to access GeoIP database: {0}', error.message); - process.exit(1); - } - } - return await Reader.open(dbpath); - } + async getGeoIPReader(): Promise { + await this.ensureDirectoryExists(); + let dbpath = path.join(this.directory, (await this.getLatestVersion()).replace('.tar.gz', ''), 'GeoLite2-Country.mmdb'); + try { + await fs.access(dbpath, fs.constants.F_OK | fs.constants.R_OK); + this.logger.info('Loading cached GeoIP database: {0}', dbpath); + } catch (ex) { + var error = ex as NodeJS.ErrnoException; + if (error.code === 'ENOENT') { + await this.downloadLatestDatabase(); + } else { + this.logger.error('Failed to access GeoIP database: {0}', error.message); + process.exit(1); + } + } + return await Reader.open(dbpath); + } - async getLatestVersion(): Promise { - let res = await fetch('https://download.maxmind.com/geoip/databases/GeoLite2-Country/download?suffix=tar.gz', { - redirect: 'follow', - method: "HEAD", - headers: { - "Authorization": this.genAuthHeader() - } - }); - let disposition = res.headers.get('Content-Disposition'); - if (!disposition) { - this.logger.Error('Failed to get latest version of GeoIP database: No Content-Disposition header'); - process.exit(1); - } - let filename = disposition.match(/filename=(.*)$/); - if (!filename) { - this.logger.Error('Failed to get latest version of GeoIP database: Could not parse version from Content-Disposition header'); - process.exit(1); - } - return filename[1]; - } + async getLatestVersion(): Promise { + let res = await fetch('https://download.maxmind.com/geoip/databases/GeoLite2-Country/download?suffix=tar.gz', { + redirect: 'follow', + method: 'HEAD', + headers: { + Authorization: this.genAuthHeader() + } + }); + let disposition = res.headers.get('Content-Disposition'); + if (!disposition) { + this.logger.error('Failed to get latest version of GeoIP database: No Content-Disposition header'); + process.exit(1); + } + let filename = disposition.match(/filename=(.*)$/); + if (!filename) { + this.logger.error('Failed to get latest version of GeoIP database: Could not parse version from Content-Disposition header'); + process.exit(1); + } + return filename[1]; + } - async downloadLatestDatabase(): Promise { - let filename = await this.getLatestVersion(); - this.logger.Info('Downloading latest GeoIP database: {0}', filename); - let dbpath = path.join(this.directory, filename); - let file = await fs.open(dbpath, fs.constants.O_CREAT | fs.constants.O_TRUNC | fs.constants.O_WRONLY); - let stream = file.createWriteStream(); - let res = await fetch('https://download.maxmind.com/geoip/databases/GeoLite2-Country/download?suffix=tar.gz', { - redirect: 'follow', - headers: { - "Authorization": this.genAuthHeader() - } - }); - await finished(Readable.fromWeb(res.body as ReadableStream).pipe(stream)); - await file.close(); - this.logger.Info('Finished downloading latest GeoIP database: {0}', filename); - this.logger.Info('Extracting GeoIP database: {0}', filename); - // yeah whatever - await execa('tar', ['xzf', filename], {cwd: this.directory}); - this.logger.Info('Unlinking GeoIP tarball'); - await fs.unlink(dbpath); - } -} \ No newline at end of file + async downloadLatestDatabase(): Promise { + let filename = await this.getLatestVersion(); + this.logger.info('Downloading latest GeoIP database: {0}', filename); + let dbpath = path.join(this.directory, filename); + let file = await fs.open(dbpath, fs.constants.O_CREAT | fs.constants.O_TRUNC | fs.constants.O_WRONLY); + let stream = file.createWriteStream(); + let res = await fetch('https://download.maxmind.com/geoip/databases/GeoLite2-Country/download?suffix=tar.gz', { + redirect: 'follow', + headers: { + Authorization: this.genAuthHeader() + } + }); + await finished(Readable.fromWeb(res.body as ReadableStream).pipe(stream)); + await file.close(); + this.logger.info('Finished downloading latest GeoIP database: {0}', filename); + this.logger.info('Extracting GeoIP database: {0}', filename); + // yeah whatever + await execa('tar', ['xzf', filename], { cwd: this.directory }); + this.logger.info('Unlinking GeoIP tarball'); + await fs.unlink(dbpath); + } +} diff --git a/cvmts/src/IPData.ts b/cvmts/src/IPData.ts index 9de8fdd..4228fa9 100644 --- a/cvmts/src/IPData.ts +++ b/cvmts/src/IPData.ts @@ -1,4 +1,4 @@ -import { Logger } from '@cvmts/shared'; +import pino from 'pino'; export class IPData { tempMuteExpireTimeout?: NodeJS.Timeout; @@ -22,7 +22,7 @@ export class IPData { export class IPDataManager { static ipDatas = new Map(); - static logger = new Logger('CVMTS.IPDataManager'); + static logger = pino({ name: 'CVMTS.IPDataManager' }); static GetIPData(address: string) { if (IPDataManager.ipDatas.has(address)) { @@ -64,7 +64,7 @@ export class IPDataManager { setInterval(() => { for (let tuple of IPDataManager.ipDatas) { if (tuple[1].refCount == 0) { - IPDataManager.logger.Info('Deleted IPData for IP {0}', tuple[0]); + IPDataManager.logger.info(`Deleted IPData for IP ${tuple[0]}`); IPDataManager.ipDatas.delete(tuple[0]); } } diff --git a/cvmts/src/JPEGEncoder.ts b/cvmts/src/JPEGEncoder.ts index a9a6f7a..763b3c5 100644 --- a/cvmts/src/JPEGEncoder.ts +++ b/cvmts/src/JPEGEncoder.ts @@ -1,4 +1,4 @@ -import { Size, Rect } from '@cvmts/shared'; +import { Size, Rect } from './VMDisplay.js'; import sharp from 'sharp'; import * as cvm from '@cvmts/cvm-rs'; diff --git a/cvmts/src/TCP/TCPServer.ts b/cvmts/src/TCP/TCPServer.ts index c32c694..040a81a 100644 --- a/cvmts/src/TCP/TCPServer.ts +++ b/cvmts/src/TCP/TCPServer.ts @@ -2,20 +2,19 @@ import EventEmitter from 'events'; import NetworkServer from '../NetworkServer.js'; import { Server, Socket } from 'net'; import IConfig from '../IConfig.js'; -import { Logger } from '@cvmts/shared'; import TCPClient from './TCPClient.js'; import { IPDataManager } from '../IPData.js'; import { User } from '../User.js'; +import pino from 'pino'; export default class TCPServer extends EventEmitter implements NetworkServer { listener: Server; Config: IConfig; - logger: Logger; + logger = pino({name: 'CVMTS.TCPServer'}); clients: TCPClient[]; constructor(config: IConfig) { super(); - this.logger = new Logger('CVMTS.TCPServer'); this.Config = config; this.listener = new Server(); this.clients = []; @@ -23,7 +22,7 @@ export default class TCPServer extends EventEmitter implements NetworkServer { } private onConnection(socket: Socket) { - this.logger.Info(`New TCP connection from ${socket.remoteAddress}`); + this.logger.info(`New TCP connection from ${socket.remoteAddress}`); var client = new TCPClient(socket); this.clients.push(client); this.emit('connect', new User(client, IPDataManager.GetIPData(client.getIP()), this.Config)); @@ -31,7 +30,7 @@ export default class TCPServer extends EventEmitter implements NetworkServer { start(): void { this.listener.listen(this.Config.tcp.port, this.Config.tcp.host, () => { - this.logger.Info(`TCP server listening on ${this.Config.tcp.host}:${this.Config.tcp.port}`); + this.logger.info(`TCP server listening on ${this.Config.tcp.host}:${this.Config.tcp.port}`); }); } stop(): void { diff --git a/cvmts/src/User.ts b/cvmts/src/User.ts index 040d74e..a9cb62b 100644 --- a/cvmts/src/User.ts +++ b/cvmts/src/User.ts @@ -4,9 +4,9 @@ import { IPData } from './IPData.js'; import IConfig from './IConfig.js'; import RateLimiter from './RateLimiter.js'; import { execa, execaCommand, ExecaSyncError } from 'execa'; -import { Logger } from '@cvmts/shared'; import NetworkClient from './NetworkClient.js'; import { CollabVMCapabilities } from '@cvmts/collab-vm-1.2-binary-protocol'; +import pino from 'pino'; export class User { socket: NetworkClient; @@ -31,7 +31,7 @@ export class User { TurnRateLimit: RateLimiter; VoteRateLimit: RateLimiter; - private logger = new Logger('CVMTS.User'); + private logger = pino({ name: 'CVMTS.User' }); constructor(socket: NetworkClient, ip: IPData, config: IConfig, username?: string, node?: string) { this.IP = ip; @@ -148,7 +148,7 @@ export class User { await execa(args.shift()!, args, { stdout: process.stdout, stderr: process.stderr }); this.kick(); } else { - this.logger.Error(`Failed to ban ${this.IP.address} (${this.username}): Empty command`); + this.logger.error(`Failed to ban ${this.IP.address} (${this.username}): Empty command`); } } else if (typeof this.Config.collabvm.bancmd == 'string') { let cmd: string = this.banCmdArgs(this.Config.collabvm.bancmd); @@ -156,11 +156,11 @@ export class User { await execaCommand(cmd, { stdout: process.stdout, stderr: process.stderr }); this.kick(); } else { - this.logger.Error(`Failed to ban ${this.IP.address} (${this.username}): Empty command`); + this.logger.error(`Failed to ban ${this.IP.address} (${this.username}): Empty command`); } } } catch (e) { - this.logger.Error(`Failed to ban ${this.IP.address} (${this.username}): ${(e as ExecaSyncError).shortMessage}`); + this.logger.error(`Failed to ban ${this.IP.address} (${this.username}): ${(e as ExecaSyncError).shortMessage}`); } } diff --git a/cvmts/src/VM.ts b/cvmts/src/VM.ts index d2a8657..0038e9b 100644 --- a/cvmts/src/VM.ts +++ b/cvmts/src/VM.ts @@ -1,5 +1,5 @@ -import { VMState } from '@cvmts/qemu'; -import VMDisplay from './VMDisplay.js'; +import { VMState } from '@computernewb/superqemu'; +import { VMDisplay } from './VMDisplay.js'; export default interface VM { Start(): Promise; diff --git a/cvmts/src/VMDisplay.ts b/cvmts/src/VMDisplay.ts index b06a6d7..559e9b8 100644 --- a/cvmts/src/VMDisplay.ts +++ b/cvmts/src/VMDisplay.ts @@ -1,7 +1,20 @@ -import { Size } from '@cvmts/shared'; import EventEmitter from 'node:events'; -export default interface VMDisplay extends EventEmitter { +// not great but whatever +// nodejs-rfb COULD probably export them though. +export type Size = { + width: number; + height: number; +}; + +export type Rect = { + x: number; + y: number; + width: number; + height: number; +}; + +export interface VMDisplay extends EventEmitter { Connect(): void; Disconnect(): void; Connected(): boolean; diff --git a/cvmts/src/VNCVM/VNCVM.ts b/cvmts/src/VNCVM/VNCVM.ts index 6cf2976..d2106a9 100644 --- a/cvmts/src/VNCVM/VNCVM.ts +++ b/cvmts/src/VNCVM/VNCVM.ts @@ -1,15 +1,23 @@ import EventEmitter from 'events'; import VNCVMDef from './VNCVMDef'; import VM from '../VM'; -import VMDisplay from '../VMDisplay'; -import { Clamp, Logger, Rect, Size, Sleep } from '@cvmts/shared'; +import { Size, Rect, VMDisplay } from '../VMDisplay'; import { VncClient } from '@computernewb/nodejs-rfb'; -import { BatchRects, VMState } from '@cvmts/qemu'; +import { BatchRects, VMState } from '@computernewb/superqemu'; import { execaCommand } from 'execa'; +import pino from 'pino'; + +function Clamp(input: number, min: number, max: number) { + return Math.min(Math.max(input, min), max); +} + +async function Sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} export default class VNCVM extends EventEmitter implements VM, VMDisplay { def: VNCVMDef; - logger: Logger; + logger; private displayVnc = new VncClient({ debug: false, fps: 60, @@ -20,7 +28,8 @@ export default class VNCVM extends EventEmitter implements VM, VMDisplay { constructor(def: VNCVMDef) { super(); this.def = def; - this.logger = new Logger(`CVMTS.VNCVM/${this.def.vncHost}:${this.def.vncPort}`); + // TODO: Now that we're using an actual structured logger can we please + this.logger = pino({ name: `CVMTS.VNCVM/${this.def.vncHost}:${this.def.vncPort}` }); this.displayVnc.on('connectTimeout', () => { this.Reconnect(); @@ -31,7 +40,7 @@ export default class VNCVM extends EventEmitter implements VM, VMDisplay { }); this.displayVnc.on('disconnect', () => { - this.logger.Info('Disconnected'); + this.logger.info('Disconnected'); this.Reconnect(); }); @@ -40,7 +49,7 @@ export default class VNCVM extends EventEmitter implements VM, VMDisplay { }); this.displayVnc.on('firstFrameUpdate', () => { - this.logger.Info('Connected'); + this.logger.info('Connected'); // apparently this library is this good. // at least it's better than the two others which exist. this.displayVnc.changeFps(60); @@ -101,13 +110,13 @@ export default class VNCVM extends EventEmitter implements VM, VMDisplay { } async Start(): Promise { - this.logger.Info('Connecting'); + this.logger.info('Connecting'); if (this.def.startCmd) await execaCommand(this.def.startCmd, { shell: true }); this.Connect(); } async Stop(): Promise { - this.logger.Info('Disconnecting'); + this.logger.info('Disconnecting'); this.Disconnect(); if (this.def.stopCmd) await execaCommand(this.def.stopCmd, { shell: true }); } diff --git a/cvmts/src/WebSocket/WSClient.ts b/cvmts/src/WebSocket/WSClient.ts index 5b4db9b..c2b0cbf 100644 --- a/cvmts/src/WebSocket/WSClient.ts +++ b/cvmts/src/WebSocket/WSClient.ts @@ -1,7 +1,6 @@ import { WebSocket } from 'ws'; import NetworkClient from '../NetworkClient.js'; import EventEmitter from 'events'; -import { Logger } from '@cvmts/shared'; export default class WSClient extends EventEmitter implements NetworkClient { socket: WebSocket; diff --git a/cvmts/src/WebSocket/WSServer.ts b/cvmts/src/WebSocket/WSServer.ts index 807c51e..1808a0d 100644 --- a/cvmts/src/WebSocket/WSServer.ts +++ b/cvmts/src/WebSocket/WSServer.ts @@ -8,20 +8,19 @@ import { isIP } from 'net'; import { IPDataManager } from '../IPData.js'; import WSClient from './WSClient.js'; import { User } from '../User.js'; -import { Logger } from '@cvmts/shared'; +import pino from 'pino'; export default class WSServer extends EventEmitter implements NetworkServer { private httpServer: http.Server; private wsServer: WebSocketServer; private clients: WSClient[]; private Config: IConfig; - private logger: Logger; + private logger = pino({ name: 'CVMTS.WSServer' }); constructor(config: IConfig) { super(); this.Config = config; this.clients = []; - this.logger = new Logger('CVMTS.WSServer'); this.httpServer = http.createServer(); this.wsServer = new WebSocketServer({ noServer: true }); this.httpServer.on('upgrade', (req: http.IncomingMessage, socket: internal.Duplex, head: Buffer) => this.httpOnUpgrade(req, socket, head)); @@ -34,7 +33,7 @@ export default class WSServer extends EventEmitter implements NetworkServer { start(): void { this.httpServer.listen(this.Config.http.port, this.Config.http.host, () => { - this.logger.Info(`WebSocket server listening on ${this.Config.http.host}:${this.Config.http.port}`); + this.logger.info(`WebSocket server listening on ${this.Config.http.host}:${this.Config.http.port}`); }); } @@ -94,7 +93,7 @@ export default class WSServer extends EventEmitter implements NetworkServer { // Make sure x-forwarded-for is set if (req.headers['x-forwarded-for'] === undefined) { killConnection(); - this.logger.Error('X-Forwarded-For header not set. This is most likely a misconfiguration of your reverse proxy.'); + this.logger.error('X-Forwarded-For header not set. This is most likely a misconfiguration of your reverse proxy.'); return; } try { @@ -102,7 +101,7 @@ export default class WSServer extends EventEmitter implements NetworkServer { ip = req.headers['x-forwarded-for']?.toString().replace(/\ /g, '').split(',')[0]; } catch { // If we can't get the IP, kill the connection - this.logger.Error('Invalid X-Forwarded-For header. This is most likely a misconfiguration of your reverse proxy.'); + this.logger.error('Invalid X-Forwarded-For header. This is most likely a misconfiguration of your reverse proxy.'); killConnection(); return; } @@ -135,10 +134,10 @@ export default class WSServer extends EventEmitter implements NetworkServer { this.emit('connect', user); ws.on('error', (e) => { - this.logger.Error(`${e} (caused by connection ${ip})`); + this.logger.error(`${e} (caused by connection ${ip})`); ws.close(); }); - this.logger.Info(`New WebSocket connection from ${user.IP.address}`); + this.logger.info(`New WebSocket connection from ${user.IP.address}`); } } diff --git a/cvmts/src/index.ts b/cvmts/src/index.ts index 4aefa80..001cf7c 100644 --- a/cvmts/src/index.ts +++ b/cvmts/src/index.ts @@ -3,9 +3,8 @@ import IConfig from './IConfig.js'; import * as fs from 'fs'; import CollabVMServer from './CollabVMServer.js'; -import { QemuVM, QemuVmDefinition } from '@cvmts/qemu'; +import { QemuVM, QemuVmDefinition } from '@computernewb/superqemu'; -import * as Shared from '@cvmts/shared'; import AuthManager from './AuthManager.js'; import WSServer from './WebSocket/WSServer.js'; import { User } from './User.js'; @@ -13,24 +12,25 @@ import TCPServer from './TCP/TCPServer.js'; import VM from './VM.js'; import VNCVM from './VNCVM/VNCVM.js'; import GeoIPDownloader from './GeoIPDownloader.js'; +import pino from 'pino'; -let logger = new Shared.Logger('CVMTS.Init'); +let logger = pino(); -logger.Info('CollabVM Server starting up'); +logger.info('CollabVM Server starting up'); // Parse the config file let Config: IConfig; if (!fs.existsSync('config.toml')) { - logger.Error('Fatal error: Config.toml not found. Please copy config.example.toml and fill out fields'); + logger.error('Fatal error: Config.toml not found. Please copy config.example.toml and fill out fields'); process.exit(1); } try { var configRaw = fs.readFileSync('config.toml').toString(); Config = toml.parse(configRaw); } catch (e) { - logger.Error('Fatal error: Failed to read or parse the config file: {0}', (e as Error).message); + logger.error('Fatal error: Failed to read or parse the config file: {0}', (e as Error).message); process.exit(1); } @@ -54,15 +54,6 @@ async function start() { let auth = Config.auth.enabled ? new AuthManager(Config.auth.apiEndpoint, Config.auth.secretKey) : null; switch (Config.vm.type) { case 'qemu': { - // Print a warning if qmpSockDir is set - // and the host OS is Windows, as this - // configuration will very likely not work. - if (process.platform === 'win32' && Config.qemu.qmpSockDir !== null) { - logger.Warning("You appear to have the option 'qmpSockDir' enabled in the config."); - logger.Warning('This is not supported on Windows, and you will likely run into issues.'); - logger.Warning('To remove this warning, use the qmpHost and qmpPort options instead.'); - } - // Fire up the VM let def: QemuVmDefinition = { id: Config.collabvm.node, @@ -78,7 +69,7 @@ async function start() { break; } default: { - logger.Error('Invalid VM type in config: {0}', Config.vm.type); + logger.error(`Invalid VM type in config: ${Config.vm.type}`); process.exit(1); return; } diff --git a/nodejs-rfb b/nodejs-rfb deleted file mode 160000 index 55e9e6c..0000000 --- a/nodejs-rfb +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 55e9e6cd65988dce59f53a3ea8701a90073b55a4 diff --git a/package.json b/package.json index 0cbe009..c1a3c67 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,6 @@ "workspaces": [ "shared", "cvm-rs", - "nodejs-rfb", - "qemu", "cvmts", "collab-vm-1.2-binary-protocol" ], diff --git a/qemu/package.json b/qemu/package.json deleted file mode 100644 index a65b021..0000000 --- a/qemu/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "@cvmts/qemu", - "version": "1.0.0", - "description": "A simple and easy to use QEMU supervision runtime", - "exports": "./dist/index.js", - "types": "./dist/index.d.ts", - "type": "module", - "scripts": { - "build": "parcel build src/index.ts --target node --target types" - }, - "author": "", - "license": "MIT", - "targets": { - "types": {}, - "node": { - "context": "node", - "isLibrary": true, - "outputFormat": "esmodule" - } - }, - "dependencies": { - "@computernewb/nodejs-rfb": "*", - "@cvmts/shared": "*", - "execa": "^8.0.1" - }, - "devDependencies": { - "parcel": "^2.12.0" - } -} diff --git a/qemu/src/QemuDisplay.ts b/qemu/src/QemuDisplay.ts deleted file mode 100644 index 8bfcbaa..0000000 --- a/qemu/src/QemuDisplay.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { VncClient } from '@computernewb/nodejs-rfb'; -import { EventEmitter } from 'node:events'; -import { BatchRects } from './QemuUtil.js'; -import { Size, Rect, Clamp } from '@cvmts/shared'; - -const kQemuFps = 60; - -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 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.emit('resize', { width: this.displayVnc.clientWidth, height: this.displayVnc.clientHeight }); - //this.emit('rect', { x: 0, y: 0, width: this.displayVnc.clientWidth, height: this.displayVnc.clientHeight }); - this.emit('frame'); - }); - - this.displayVnc.on('desktopSizeChanged', (size: Size) => { - this.emit('resize', size); - }); - - let rects: Rect[] = []; - - this.displayVnc.on('rectUpdateProcessed', (rect: Rect) => { - rects.push(rect); - }); - - this.displayVnc.on('frameUpdated', (fb: Buffer) => { - // use the cvmts batcher - let batched = BatchRects(this.Size(), rects); - this.emit('rect', batched); - - // unbatched (watch the performace go now) - //for(let rect of rects) - // this.emit('rect', rect); - - 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(); - - // bye bye! - this.displayVnc.removeAllListeners(); - this.removeAllListeners(); - } - - Connected() { - return this.displayVnc.connected; - } - - Buffer(): Buffer { - return this.displayVnc.fb; - } - - Size(): Size { - if (!this.displayVnc.connected) - return { - width: 0, - height: 0 - }; - - return { - width: this.displayVnc.clientWidth, - height: this.displayVnc.clientHeight - }; - } - - MouseEvent(x: number, y: number, buttons: number) { - if (this.displayVnc.connected) this.displayVnc.sendPointerEvent(Clamp(x, 0, this.displayVnc.clientWidth), Clamp(y, 0, this.displayVnc.clientHeight), buttons); - } - - KeyboardEvent(keysym: number, pressed: boolean) { - if (this.displayVnc.connected) this.displayVnc.sendKeyEvent(keysym, pressed); - } -} diff --git a/qemu/src/QemuUtil.ts b/qemu/src/QemuUtil.ts deleted file mode 100644 index b6b4715..0000000 --- a/qemu/src/QemuUtil.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Size, Rect } from '@cvmts/shared'; - -export function BatchRects(size: Size, rects: Array): Rect { - var mergedX = size.width; - var mergedY = size.height; - var mergedHeight = 0; - var mergedWidth = 0; - - // can't batch these - if (rects.length == 0) { - return { - x: 0, - y: 0, - width: size.width, - height: size.height - }; - } - - if (rects.length == 1) { - if (rects[0].width == size.width && rects[0].height == size.height) { - return rects[0]; - } - } - - rects.forEach((r) => { - if (r.x < mergedX) mergedX = r.x; - if (r.y < mergedY) mergedY = r.y; - }); - - rects.forEach((r) => { - if (r.height + r.y - mergedY > mergedHeight) mergedHeight = r.height + r.y - mergedY; - if (r.width + r.x - mergedX > mergedWidth) mergedWidth = r.width + r.x - mergedX; - }); - - return { - x: mergedX, - y: mergedY, - width: mergedWidth, - height: mergedHeight - }; -} diff --git a/qemu/src/QemuVM.ts b/qemu/src/QemuVM.ts deleted file mode 100644 index dc2b01e..0000000 --- a/qemu/src/QemuVM.ts +++ /dev/null @@ -1,271 +0,0 @@ -import { execaCommand, ExecaChildProcess } from 'execa'; -import { EventEmitter } from 'events'; -import { QmpClient, IQmpClientWriter, QmpEvent } from './QmpClient.js'; -import { QemuDisplay } from './QemuDisplay.js'; -import { unlink } from 'node:fs/promises'; - -import * as Shared from '@cvmts/shared'; -import { Readable, Writable } from 'stream'; - -export enum VMState { - Stopped, - Starting, - Started, - Stopping -} - -export type QemuVmDefinition = { - id: string; - command: string; - snapshot: boolean; -}; - -/// Temporary path base (for UNIX sockets/etc.) -const kVmTmpPathBase = `/tmp`; - -// writer implementation for process standard I/O -class StdioWriter implements IQmpClientWriter { - stdout; - stdin; - client; - - constructor(stdout: Readable, stdin: Writable, client: QmpClient) { - this.stdout = stdout; - this.stdin = stdin; - this.client = client; - - this.stdout.on('data', (data) => { - this.client.feed(data); - }); - } - - writeSome(buffer: Buffer) { - if(!this.stdin.closed) - this.stdin.write(buffer); - } -} - -export class QemuVM extends EventEmitter { - private state = VMState.Stopped; - - // QMP stuff. - private qmpInstance: QmpClient = new QmpClient(); - - private qemuProcess: ExecaChildProcess | null = null; - - private display: QemuDisplay | null = null; - private definition: QemuVmDefinition; - private addedAdditionalArguments = false; - - private logger: Shared.Logger; - - constructor(def: QemuVmDefinition) { - super(); - this.definition = def; - this.logger = new Shared.Logger(`CVMTS.QEMU.QemuVM/${this.definition.id}`); - - let self = this; - - // Handle the STOP event sent when using -no-shutdown - this.qmpInstance.on(QmpEvent.Stop, async () => { - await self.qmpInstance.execute('system_reset'); - }); - - this.qmpInstance.on(QmpEvent.Reset, async () => { - await self.qmpInstance.execute('cont'); - }); - - this.qmpInstance.on('connected', async () => { - self.VMLog().Info('QMP ready'); - - this.display = new QemuDisplay(this.GetVncPath()); - - self.display?.on('connected', () => { - // The VM can now be considered started - self.VMLog().Info('Display connected'); - self.SetState(VMState.Started); - }); - - // now that QMP has connected, connect to the display - self.display?.Connect(); - }); - } - - async Start() { - // Don't start while either trying to start or starting. - //if (this.state == VMState.Started || this.state == VMState.Starting) return; - if (this.qemuProcess) return; - - let cmd = this.definition.command; - - // Build additional command line statements to enable qmp/vnc over unix sockets - if (!this.addedAdditionalArguments) { - cmd += ' -no-shutdown'; - if (this.definition.snapshot) cmd += ' -snapshot'; - cmd += ` -qmp stdio -vnc unix:${this.GetVncPath()}`; - this.definition.command = cmd; - this.addedAdditionalArguments = true; - } - - await this.StartQemu(cmd); - } - - SnapshotsSupported(): boolean { - return this.definition.snapshot; - } - - async Reboot(): Promise { - await this.MonitorCommand('system_reset'); - } - - async Stop() { - this.AssertState(VMState.Started, 'cannot use QemuVM#Stop on a non-started VM'); - - // Indicate we're stopping, so we don't erroneously start trying to restart everything we're going to tear down. - this.SetState(VMState.Stopping); - - // Stop the QEMU process, which will bring down everything else. - await this.StopQemu(); - } - - async Reset() { - this.AssertState(VMState.Started, 'cannot use QemuVM#Reset on a non-started VM'); - await this.StopQemu(); - } - - 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'); - let result = await this.QmpCommand('human-monitor-command', { - 'command-line': command - }); - if (result == null) result = ''; - return result; - } - - 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!; - } - - GetState() { - return this.state; - } - - /// Private fun bits :) - - private VMLog() { - return this.logger; - } - - private AssertState(stateShouldBe: VMState, message: string) { - if (this.state !== stateShouldBe) throw new Error(message); - } - - private SetState(state: VMState) { - this.state = state; - this.emit('statechange', this.state); - } - - private GetVncPath() { - return `${kVmTmpPathBase}/cvmts-${this.definition.id}-vnc`; - } - - private async StartQemu(split: string) { - let self = this; - - this.SetState(VMState.Starting); - - this.VMLog().Info(`Starting QEMU with command \"${split}\"`); - - // Start QEMU - this.qemuProcess = execaCommand(split, { - stdin: 'pipe', - stdout: 'pipe', - stderr: 'pipe' - }); - - this.qemuProcess.stderr?.on('data', (data) => { - self.VMLog().Error('QEMU stderr: {0}', data.toString('utf8')); - }); - - this.qemuProcess.on('spawn', async () => { - self.VMLog().Info('QEMU started'); - await self.QmpStdioInit(); - }); - - this.qemuProcess.on('exit', async (code) => { - self.VMLog().Info('QEMU process exited'); - - // Disconnect from the display and QMP connections. - await self.DisconnectDisplay(); - - self.qmpInstance.reset(); - self.qmpInstance.setWriter(null); - - // Remove the VNC UDS socket. - try { - await unlink(this.GetVncPath()); - } catch (_) {} - - if (self.state != VMState.Stopping) { - if (code == 0) { - await self.StartQemu(split); - } else { - self.VMLog().Error('QEMU exited with a non-zero exit code. This usually means an error in the command line. Stopping VM.'); - // Note that we've already tore down everything upon entry to this event handler; therefore - // we can simply set the state and move on. - this.SetState(VMState.Stopped); - } - } else { - // Indicate we have stopped. - this.SetState(VMState.Stopped); - } - }); - } - - private async StopQemu() { - if (this.qemuProcess) { - this.qemuProcess?.kill('SIGTERM'); - this.qemuProcess = null; - } - } - - private async QmpStdioInit() { - let self = this; - - self.VMLog().Info('Initializing QMP over stdio'); - - // Setup the QMP client. - let writer = new StdioWriter(this.qemuProcess?.stdout!, this.qemuProcess?.stdin!, self.qmpInstance); - self.qmpInstance.reset(); - self.qmpInstance.setWriter(writer); - } - - private async DisconnectDisplay() { - try { - this.display?.Disconnect(); - this.display = null; - } catch (err) { - // oh well lol - } - } -} diff --git a/qemu/src/QmpClient.ts b/qemu/src/QmpClient.ts deleted file mode 100644 index 3a31cf0..0000000 --- a/qemu/src/QmpClient.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { EventEmitter } from 'node:events'; - -enum QmpClientState { - Handshaking, - Connected -} - -function qmpStringify(obj: any) { - return JSON.stringify(obj) + '\r\n'; -} - -// this writer interface is used to poll back to a higher level -// I/O layer that we want to write some data. -export interface IQmpClientWriter { - writeSome(data: Buffer): void; -} - -export type QmpClientCallback = (err: Error | null, res: any | null) => void; - -type QmpClientCallbackEntry = { - id: number; - callback: QmpClientCallback | null; -}; - -export enum QmpEvent { - BlockIOError = 'BLOCK_IO_ERROR', - Reset = 'RESET', - Resume = 'RESUME', - RtcChange = 'RTC_CHANGE', - Shutdown = 'SHUTDOWN', - Stop = 'STOP', - VncConnected = 'VNC_CONNECTED', - VncDisconnected = 'VNC_DISCONNECTED', - VncInitialized = 'VNC_INITIALIZED', - Watchdog = 'WATCHDOG' -} - -class LineStream extends EventEmitter { - // The given line seperator for the stream - lineSeperator = '\r\n'; - buffer = ''; - - constructor() { - super(); - } - - push(data: Buffer) { - this.buffer += data.toString('utf-8'); - - let lines = this.buffer.split(this.lineSeperator); - if (lines.length > 1) { - this.buffer = lines.pop()!; - lines = lines.filter((l) => !!l); - lines.forEach(l => this.emit('line', l)); - } - } - - reset() { - this.buffer = ''; - } -} - -// A QMP client -export class QmpClient extends EventEmitter { - private state = QmpClientState.Handshaking; - private writer: IQmpClientWriter | null = null; - - private lastID = 0; - private callbacks = new Array(); - - private lineStream = new LineStream(); - - constructor() { - super(); - - let self = this; - this.lineStream.on('line', (line: string) => { - self.handleQmpLine(line); - }); - } - - setWriter(writer: IQmpClientWriter|null) { - this.writer = writer; - } - - feed(data: Buffer): void { - // Forward to the line stream. It will generate 'line' events - // as it is able to split out lines automatically. - this.lineStream.push(data); - } - - private handleQmpLine(line: string) { - let obj = JSON.parse(line); - - switch (this.state) { - case QmpClientState.Handshaking: - if (obj['return'] != undefined) { - // Once we get a return from our handshake execution, - // we have exited handshake state. - this.state = QmpClientState.Connected; - this.emit('connected'); - return; - } else if(obj['QMP'] != undefined) { - // Send a `qmp_capabilities` command, to exit handshake state. - // We do not support any of the supported extended QMP capabilities currently, - // and probably never will (due to their relative uselessness.) - let capabilities = qmpStringify({ - execute: 'qmp_capabilities' - }); - - this.writer?.writeSome(Buffer.from(capabilities, 'utf8')); - } - break; - - case QmpClientState.Connected: - if (obj['return'] != undefined || obj['error'] != undefined) { - if (obj['id'] == null) return; - - let cb = this.callbacks.find((v) => v.id == obj['id']); - if (cb == undefined) return; - - let error: Error | null = obj.error ? new Error(obj.error.desc) : null; - - if (cb.callback) cb.callback(error, obj.return || null); - - this.callbacks.slice(this.callbacks.indexOf(cb)); - } else if (obj['event']) { - this.emit(obj.event, { - timestamp: obj.timestamp, - data: obj.data - }); - } - break; - } - } - - // Executes a QMP command, using a user-provided callback for completion notification - executeCallback(command: string, args: any | undefined, callback: QmpClientCallback | null) { - let entry = { - callback: callback, - id: ++this.lastID - }; - - let qmpOut: any = { - execute: command, - id: entry.id - }; - - if (args !== undefined) qmpOut['arguments'] = args; - - this.callbacks.push(entry); - this.writer?.writeSome(Buffer.from(qmpStringify(qmpOut), 'utf8')); - } - - // Executes a QMP command asynchronously. - async execute(command: string, args: any | undefined = undefined): Promise { - return new Promise((res, rej) => { - this.executeCallback(command, args, (err, result) => { - if (err) rej(err); - res(result); - }); - }); - } - - reset() { - // Reset the line stream so it doesn't go awry - this.lineStream.reset(); - this.state = QmpClientState.Handshaking; - } -} diff --git a/qemu/src/index.ts b/qemu/src/index.ts deleted file mode 100644 index a3deb21..0000000 --- a/qemu/src/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -/// - -export * from './QemuDisplay.js'; -export * from './QemuUtil.js'; -export * from './QemuVM.js'; diff --git a/qemu/tsconfig.json b/qemu/tsconfig.json deleted file mode 120000 index 4ec6ff6..0000000 --- a/qemu/tsconfig.json +++ /dev/null @@ -1 +0,0 @@ -../tsconfig.json \ No newline at end of file diff --git a/shared/package.json b/shared/package.json deleted file mode 100644 index 1733d65..0000000 --- a/shared/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "@cvmts/shared", - "version": "1.0.0", - "description": "cvmts shared util bits", - "type": "module", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "files": [ - "dist" - ], - "targets": { - "types": {}, - "shared": { - "context": "browser", - "isLibrary": true, - "outputFormat": "esmodule" - } - }, - "devDependencies": { - "@protobuf-ts/plugin": "^2.9.4", - "parcel": "^2.12.0" - }, - "scripts": { - "build": "parcel build src/index.ts --target shared --target types" - }, - "author": "", - "license": "ISC" -} diff --git a/shared/src/Logger.ts b/shared/src/Logger.ts deleted file mode 100644 index 15cd6be..0000000 --- a/shared/src/Logger.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Format } from "./format"; -import { StringLike } from "./StringLike"; - -export enum LogLevel { - VERBOSE = 0, - INFO, - WARNING, - ERROR -}; - - -let gLogLevel = LogLevel.INFO; - -export function SetLogLevel(level: LogLevel) { - gLogLevel = level; -} - -export class Logger { - private _component: string; - - constructor(component: string) { - this._component = component; - } - - - // TODO: use js argments stuff. - - Verbose(pattern: string, ...args: Array) { - if(gLogLevel <= LogLevel.VERBOSE) - console.log(`[${this._component}] [VERBOSE] ${Format(pattern, ...args)}`); - } - - Info(pattern: string, ...args: Array) { - if(gLogLevel <= LogLevel.INFO) - console.log(`[${this._component}] [INFO] ${Format(pattern, ...args)}`); - } - - Warning(pattern: string, ...args: Array) { - if(gLogLevel <= LogLevel.WARNING) - console.warn(`[${this._component}] [WARNING] ${Format(pattern, ...args)}`); - } - - Error(pattern: string, ...args: Array) { - if(gLogLevel <= LogLevel.ERROR) - console.error(`[${this._component}] [ERROR] ${Format(pattern, ...args)}`); - } - - - -} diff --git a/shared/src/StringLike.ts b/shared/src/StringLike.ts deleted file mode 100644 index ecc6989..0000000 --- a/shared/src/StringLike.ts +++ /dev/null @@ -1,8 +0,0 @@ -// TODO: `Object` has a toString(), but we should probably gate that off -/// Interface for things that can be turned into strings -export interface ToStringable { - toString(): string; -} - -/// A type for strings, or things that can (in a valid manner) be turned into strings -export type StringLike = string | ToStringable; diff --git a/shared/src/format.ts b/shared/src/format.ts deleted file mode 100644 index ae95f9d..0000000 --- a/shared/src/format.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { StringLike } from './StringLike'; - -function isalpha(char: number) { - return RegExp(/^\p{L}/, 'u').test(String.fromCharCode(char)); -} - -/// A simple function for formatting strings in a more expressive manner. -/// While JavaScript *does* have string interpolation, it's not a total replacement -/// for just formatting strings, and a method like this is better for data independent formatting. -/// -/// ## Example usage -/// -/// ```typescript -/// let hello = Format("Hello, {0}!", "World"); -/// ``` -export function Format(pattern: string, ...args: Array) { - let argumentsAsStrings: Array = [...args].map((el) => { - // This catches cases where the thing already is a string - if (typeof el == 'string') return el as string; - return el.toString(); - }); - - let pat = pattern; - - // Handle pattern ("{0} {1} {2} {3} {4} {5}") syntax if found - for (let i = 0; i < pat.length; ++i) { - if (pat[i] == '{') { - let replacementStart = i; - let foundSpecifierEnd = false; - - // Make sure the specifier is not cut off (the last character of the string) - if (i + 3 > pat.length) { - throw new Error(`Error in format pattern "${pat}": Cutoff/invalid format specifier`); - } - - // Try and find the specifier end ('}'). - // Whitespace and a '{' are considered errors. - for (let j = i + 1; j < pat.length; ++j) { - switch (pat[j]) { - case '}': - foundSpecifierEnd = true; - i = j; - break; - - case '{': - throw new Error(`Error in format pattern "${pat}": Cannot start a format specifier in an existing replacement`); - case ' ': - throw new Error(`Error in format pattern "${pat}": Whitespace inside format specifier`); - - case '-': - throw new Error(`Error in format pattern "${pat}": Malformed format specifier`); - - default: - if (isalpha(pat.charCodeAt(j))) throw new Error(`Error in format pattern "${pat}": Malformed format specifier`); - break; - } - - if (foundSpecifierEnd) break; - } - - if (!foundSpecifierEnd) throw new Error(`Error in format pattern "${pat}": No terminating "}" character found`); - - // Get the beginning and trailer - let beginning = pat.substring(0, replacementStart); - let trailer = pat.substring(replacementStart + 3); - - let argumentIndex = parseInt(pat.substring(replacementStart + 1, i)); - if (Number.isNaN(argumentIndex) || argumentIndex > argumentsAsStrings.length) throw new Error(`Error in format pattern "${pat}": Argument index out of bounds`); - - // This is seriously the only decent way to do this in javascript - // thanks brendan eich (replace this thanking with more choice words in your head) - pat = beginning + argumentsAsStrings[argumentIndex] + trailer; - } - } - - return pat; -} diff --git a/shared/src/index.ts b/shared/src/index.ts deleted file mode 100644 index 82d7877..0000000 --- a/shared/src/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -// public modules -export * from './StringLike.js'; -export * from './Logger.js'; -export * from './format.js'; - -export function Clamp(input: number, min: number, max: number) { - return Math.min(Math.max(input, min), max); -} - -export async function Sleep(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -export type Size = { - width: number; - height: number; -}; - -export type Rect = { - x: number; - y: number; - width: number; - height: number; -}; diff --git a/shared/tsconfig.json b/shared/tsconfig.json deleted file mode 120000 index 4ec6ff6..0000000 --- a/shared/tsconfig.json +++ /dev/null @@ -1 +0,0 @@ -../tsconfig.json \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 2dc7fb3..087e547 100644 --- a/yarn.lock +++ b/yarn.lock @@ -34,18 +34,23 @@ __metadata: languageName: node linkType: hard -"@computernewb/nodejs-rfb@npm:*, @computernewb/nodejs-rfb@workspace:nodejs-rfb": - version: 0.0.0-use.local - resolution: "@computernewb/nodejs-rfb@workspace:nodejs-rfb" +"@computernewb/nodejs-rfb@npm:^0.3.0": + version: 0.3.0 + resolution: "@computernewb/nodejs-rfb@npm:0.3.0" + checksum: 10c0/915a76118011a4a64e28fcc91fafcffe659e5e86db2505554578f1e5dff4883dbaf5fcc013ac377719fe9c798e730b178aa5030027da0f5a9583355ec2ac8af2 + languageName: node + linkType: hard + +"@computernewb/superqemu@npm:^0.1.0": + version: 0.1.0 + resolution: "@computernewb/superqemu@npm:0.1.0" dependencies: - "@parcel/packager-ts": "npm:2.12.0" - "@parcel/transformer-typescript-types": "npm:2.12.0" - "@types/node": "npm:^20.12.7" - parcel: "npm:^2.12.0" - prettier: "npm:^3.2.5" - typescript: "npm:>=3.0.0" - languageName: unknown - linkType: soft + "@computernewb/nodejs-rfb": "npm:^0.3.0" + execa: "npm:^8.0.1" + pino: "npm:^9.3.1" + checksum: 10c0/7177b46c1093345cc3cbcc09450b8b8b09f09eb74ba5abd283aae39e9d1dbc0780f54187da075e44c78a7b683d47367010a473406c2817c36352edd0ddad2c1a + languageName: node + linkType: hard "@cvmts/collab-vm-1.2-binary-protocol@workspace:collab-vm-1.2-binary-protocol": version: 0.0.0-use.local @@ -70,14 +75,17 @@ __metadata: version: 0.0.0-use.local resolution: "@cvmts/cvmts@workspace:cvmts" dependencies: + "@computernewb/nodejs-rfb": "npm:^0.3.0" + "@computernewb/superqemu": "npm:^0.1.0" "@cvmts/cvm-rs": "npm:*" - "@cvmts/qemu": "npm:*" "@maxmind/geoip2-node": "npm:^5.0.0" "@types/node": "npm:^20.12.5" "@types/ws": "npm:^8.5.5" execa: "npm:^8.0.1" mnemonist: "npm:^0.39.5" msgpackr: "npm:^1.10.2" + pino: "npm:^9.3.1" + pino-pretty: "npm:^11.2.1" prettier: "npm:^3.2.5" sharp: "npm:^0.33.3" toml: "npm:^3.0.0" @@ -86,26 +94,6 @@ __metadata: languageName: unknown linkType: soft -"@cvmts/qemu@npm:*, @cvmts/qemu@workspace:qemu": - version: 0.0.0-use.local - resolution: "@cvmts/qemu@workspace:qemu" - dependencies: - "@computernewb/nodejs-rfb": "npm:*" - "@cvmts/shared": "npm:*" - execa: "npm:^8.0.1" - parcel: "npm:^2.12.0" - languageName: unknown - linkType: soft - -"@cvmts/shared@npm:*, @cvmts/shared@workspace:shared": - version: 0.0.0-use.local - resolution: "@cvmts/shared@workspace:shared" - dependencies: - "@protobuf-ts/plugin": "npm:^2.9.4" - parcel: "npm:^2.12.0" - languageName: unknown - linkType: soft - "@emnapi/runtime@npm:^1.1.0": version: 1.1.1 resolution: "@emnapi/runtime@npm:1.1.1" @@ -1335,57 +1323,6 @@ __metadata: languageName: node linkType: hard -"@protobuf-ts/plugin-framework@npm:^2.9.4": - version: 2.9.4 - resolution: "@protobuf-ts/plugin-framework@npm:2.9.4" - dependencies: - "@protobuf-ts/runtime": "npm:^2.9.4" - typescript: "npm:^3.9" - checksum: 10c0/2923852ab1d2d46090245a858fd362fffccd4f556963b01d153d4c5568dfa33d34101dacca0b1f38f23516e5a4d1f765e14be1d885dc1159d5ec37d25000f065 - languageName: node - linkType: hard - -"@protobuf-ts/plugin@npm:^2.9.4": - version: 2.9.4 - resolution: "@protobuf-ts/plugin@npm:2.9.4" - dependencies: - "@protobuf-ts/plugin-framework": "npm:^2.9.4" - "@protobuf-ts/protoc": "npm:^2.9.4" - "@protobuf-ts/runtime": "npm:^2.9.4" - "@protobuf-ts/runtime-rpc": "npm:^2.9.4" - typescript: "npm:^3.9" - bin: - protoc-gen-dump: bin/protoc-gen-dump - protoc-gen-ts: bin/protoc-gen-ts - checksum: 10c0/dbf1506e656d4d8ca91ace656cf3e238aed93d6539747c72c140fb0be29af61ccafae4e8c9f1e6f8369ac20508263d718ccb411dcf2d15276672c8ad7ba8194c - languageName: node - linkType: hard - -"@protobuf-ts/protoc@npm:^2.9.4": - version: 2.9.4 - resolution: "@protobuf-ts/protoc@npm:2.9.4" - bin: - protoc: protoc.js - checksum: 10c0/4ce4380cdab5560d13dd3b8d3538e6aee508a10b6b43dbd649d2ffe0a774129d59bd0e270ce7f643a95b9703e19088a5c725f68939913f2187fdeb1d6b42d4b5 - languageName: node - linkType: hard - -"@protobuf-ts/runtime-rpc@npm:^2.9.4": - version: 2.9.4 - resolution: "@protobuf-ts/runtime-rpc@npm:2.9.4" - dependencies: - "@protobuf-ts/runtime": "npm:^2.9.4" - checksum: 10c0/91fa7037b669dc92073d393dbe6bb109307d7b884506f6e5a310c6bde43b3920154b1176c826e9739c81ecd108090516b826e94354d58e454df2eef7f50f3a12 - languageName: node - linkType: hard - -"@protobuf-ts/runtime@npm:^2.9.4": - version: 2.9.4 - resolution: "@protobuf-ts/runtime@npm:2.9.4" - checksum: 10c0/78a10c0e2ee33fe98b3e30d15f8a52fe1a9505de3a8c056339bc01a0a076d4108a4efe93b578dc034c91c1b8c85996643b3f4d45f95c7e2bd5c151455b4fd23f - languageName: node - linkType: hard - "@swc/core-darwin-arm64@npm:1.4.17": version: 1.4.17 resolution: "@swc/core-darwin-arm64@npm:1.4.17" @@ -1534,7 +1471,7 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:^20.12.5, @types/node@npm:^20.12.7": +"@types/node@npm:*, @types/node@npm:^20.12.5": version: 20.12.7 resolution: "@types/node@npm:20.12.7" dependencies: @@ -1577,6 +1514,15 @@ __metadata: languageName: node linkType: hard +"abort-controller@npm:^3.0.0": + version: 3.0.0 + resolution: "abort-controller@npm:3.0.0" + dependencies: + event-target-shim: "npm:^5.0.0" + checksum: 10c0/90ccc50f010250152509a344eb2e71977fbf8db0ab8f1061197e3275ddf6c61a41a6edfd7b9409c664513131dd96e962065415325ef23efa5db931b382d24ca5 + languageName: node + linkType: hard + "abortcontroller-polyfill@npm:^1.1.9": version: 1.7.5 resolution: "abortcontroller-polyfill@npm:1.7.5" @@ -1666,6 +1612,13 @@ __metadata: languageName: node linkType: hard +"atomic-sleep@npm:^1.0.0": + version: 1.0.0 + resolution: "atomic-sleep@npm:1.0.0" + checksum: 10c0/e329a6665512736a9bbb073e1761b4ec102f7926cce35037753146a9db9c8104f5044c1662e4a863576ce544fb8be27cd2be6bc8c1a40147d03f31eb1cfb6e8a + languageName: node + linkType: hard + "balanced-match@npm:^1.0.0": version: 1.0.2 resolution: "balanced-match@npm:1.0.2" @@ -1682,6 +1635,13 @@ __metadata: languageName: node linkType: hard +"base64-js@npm:^1.3.1": + version: 1.5.1 + resolution: "base64-js@npm:1.5.1" + checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf + languageName: node + linkType: hard + "binary-extensions@npm:^2.0.0": version: 2.3.0 resolution: "binary-extensions@npm:2.3.0" @@ -1735,6 +1695,16 @@ __metadata: languageName: node linkType: hard +"buffer@npm:^6.0.3": + version: 6.0.3 + resolution: "buffer@npm:6.0.3" + dependencies: + base64-js: "npm:^1.3.1" + ieee754: "npm:^1.2.1" + checksum: 10c0/2a905fbbcde73cc5d8bd18d1caa23715d5f83a5935867c2329f0ac06104204ba7947be098fe1317fbd8830e26090ff8e764f08cd14fefc977bb248c3487bcbd0 + languageName: node + linkType: hard + "cacache@npm:^18.0.0": version: 18.0.2 resolution: "cacache@npm:18.0.2" @@ -1898,6 +1868,13 @@ __metadata: languageName: node linkType: hard +"colorette@npm:^2.0.7": + version: 2.0.20 + resolution: "colorette@npm:2.0.20" + checksum: 10c0/e94116ff33b0ff56f3b83b9ace895e5bf87c2a7a47b3401b8c3f3226e050d5ef76cf4072fb3325f9dc24d1698f9b730baf4e05eeaf861d74a1883073f4c98a40 + languageName: node + linkType: hard + "commander@npm:^7.0.0, commander@npm:^7.2.0": version: 7.2.0 resolution: "commander@npm:7.2.0" @@ -2001,6 +1978,13 @@ __metadata: languageName: node linkType: hard +"dateformat@npm:^4.6.3": + version: 4.6.3 + resolution: "dateformat@npm:4.6.3" + checksum: 10c0/e2023b905e8cfe2eb8444fb558562b524807a51cdfe712570f360f873271600b5c94aebffaf11efb285e2c072264a7cf243eadb68f3eba0f8cc85fb86cd25df6 + languageName: node + linkType: hard + "debug@npm:4, debug@npm:^4.3.4": version: 4.3.4 resolution: "debug@npm:4.3.4" @@ -2190,6 +2174,20 @@ __metadata: languageName: node linkType: hard +"event-target-shim@npm:^5.0.0": + version: 5.0.1 + resolution: "event-target-shim@npm:5.0.1" + checksum: 10c0/0255d9f936215fd206156fd4caa9e8d35e62075d720dc7d847e89b417e5e62cf1ce6c9b4e0a1633a9256de0efefaf9f8d26924b1f3c8620cffb9db78e7d3076b + languageName: node + linkType: hard + +"events@npm:^3.3.0": + version: 3.3.0 + resolution: "events@npm:3.3.0" + checksum: 10c0/d6b6f2adbccbcda74ddbab52ed07db727ef52e31a61ed26db9feb7dc62af7fc8e060defa65e5f8af9449b86b52cc1a1f6a79f2eafcf4e62add2b7a1fa4a432f6 + languageName: node + linkType: hard + "execa@npm:^8.0.1": version: 8.0.1 resolution: "execa@npm:8.0.1" @@ -2245,6 +2243,27 @@ __metadata: languageName: node linkType: hard +"fast-copy@npm:^3.0.2": + version: 3.0.2 + resolution: "fast-copy@npm:3.0.2" + checksum: 10c0/02e8b9fd03c8c024d2987760ce126456a0e17470850b51e11a1c3254eed6832e4733ded2d93316c82bc0b36aeb991ad1ff48d1ba95effe7add7c3ab8d8eb554a + languageName: node + linkType: hard + +"fast-redact@npm:^3.1.1": + version: 3.5.0 + resolution: "fast-redact@npm:3.5.0" + checksum: 10c0/7e2ce4aad6e7535e0775bf12bd3e4f2e53d8051d8b630e0fa9e67f68cb0b0e6070d2f7a94b1d0522ef07e32f7c7cda5755e2b677a6538f1e9070ca053c42343a + languageName: node + linkType: hard + +"fast-safe-stringify@npm:^2.1.1": + version: 2.1.1 + resolution: "fast-safe-stringify@npm:2.1.1" + checksum: 10c0/d90ec1c963394919828872f21edaa3ad6f1dddd288d2bd4e977027afff09f5db40f94e39536d4646f7e01761d704d72d51dce5af1b93717f3489ef808f5f4e4d + languageName: node + linkType: hard + "fd-slicer@npm:~1.1.0": version: 1.1.0 resolution: "fd-slicer@npm:1.1.0" @@ -2406,6 +2425,13 @@ __metadata: languageName: node linkType: hard +"help-me@npm:^5.0.0": + version: 5.0.0 + resolution: "help-me@npm:5.0.0" + checksum: 10c0/054c0e2e9ae2231c85ab5e04f75109b9d068ffcc54e58fb22079822a5ace8ff3d02c66fd45379c902ad5ab825e5d2e1451fcc2f7eab1eb49e7d488133ba4cacb + languageName: node + linkType: hard + "htmlnano@npm:^2.0.0": version: 2.1.0 resolution: "htmlnano@npm:2.1.0" @@ -2498,6 +2524,13 @@ __metadata: languageName: node linkType: hard +"ieee754@npm:^1.2.1": + version: 1.2.1 + resolution: "ieee754@npm:1.2.1" + checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb + languageName: node + linkType: hard + "immutable@npm:^4.0.0": version: 4.3.5 resolution: "immutable@npm:4.3.5" @@ -2650,6 +2683,13 @@ __metadata: languageName: node linkType: hard +"joycon@npm:^3.1.1": + version: 3.1.1 + resolution: "joycon@npm:3.1.1" + checksum: 10c0/131fb1e98c9065d067fd49b6e685487ac4ad4d254191d7aa2c9e3b90f4e9ca70430c43cad001602bdbdabcf58717d3b5c5b7461c1bd8e39478c8de706b3fe6ae + languageName: node + linkType: hard + "js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" @@ -2950,6 +2990,13 @@ __metadata: languageName: node linkType: hard +"minimist@npm:^1.2.6": + version: 1.2.8 + resolution: "minimist@npm:1.2.8" + checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6 + languageName: node + linkType: hard + "minipass-collect@npm:^2.0.1": version: 2.0.1 resolution: "minipass-collect@npm:2.0.1" @@ -3265,6 +3312,13 @@ __metadata: languageName: node linkType: hard +"on-exit-leak-free@npm:^2.1.0": + version: 2.1.2 + resolution: "on-exit-leak-free@npm:2.1.2" + checksum: 10c0/faea2e1c9d696ecee919026c32be8d6a633a7ac1240b3b87e944a380e8a11dc9c95c4a1f8fb0568de7ab8db3823e790f12bda45296b1d111e341aad3922a0570 + languageName: node + linkType: hard + "once@npm:^1.3.1, once@npm:^1.4.0": version: 1.4.0 resolution: "once@npm:1.4.0" @@ -3396,6 +3450,68 @@ __metadata: languageName: node linkType: hard +"pino-abstract-transport@npm:^1.0.0, pino-abstract-transport@npm:^1.2.0": + version: 1.2.0 + resolution: "pino-abstract-transport@npm:1.2.0" + dependencies: + readable-stream: "npm:^4.0.0" + split2: "npm:^4.0.0" + checksum: 10c0/b4ab59529b7a91f488440147fc58ee0827a6c1c5ca3627292339354b1381072c1a6bfa9b46d03ad27872589e8477ecf74da12cf286e1e6b665ac64a3b806bf07 + languageName: node + linkType: hard + +"pino-pretty@npm:^11.2.1": + version: 11.2.1 + resolution: "pino-pretty@npm:11.2.1" + dependencies: + colorette: "npm:^2.0.7" + dateformat: "npm:^4.6.3" + fast-copy: "npm:^3.0.2" + fast-safe-stringify: "npm:^2.1.1" + help-me: "npm:^5.0.0" + joycon: "npm:^3.1.1" + minimist: "npm:^1.2.6" + on-exit-leak-free: "npm:^2.1.0" + pino-abstract-transport: "npm:^1.0.0" + pump: "npm:^3.0.0" + readable-stream: "npm:^4.0.0" + secure-json-parse: "npm:^2.4.0" + sonic-boom: "npm:^4.0.1" + strip-json-comments: "npm:^3.1.1" + bin: + pino-pretty: bin.js + checksum: 10c0/6c7f15b5bf8a007c8b7157eae445675b13cd95097ffa512d5ebd661f9e7abd328fa27592b25708756a09f098f87cb03ca81837518cd725c16e3f801129b941d4 + languageName: node + linkType: hard + +"pino-std-serializers@npm:^7.0.0": + version: 7.0.0 + resolution: "pino-std-serializers@npm:7.0.0" + checksum: 10c0/73e694d542e8de94445a03a98396cf383306de41fd75ecc07085d57ed7a57896198508a0dec6eefad8d701044af21eb27253ccc352586a03cf0d4a0bd25b4133 + languageName: node + linkType: hard + +"pino@npm:^9.3.1": + version: 9.3.1 + resolution: "pino@npm:9.3.1" + dependencies: + atomic-sleep: "npm:^1.0.0" + fast-redact: "npm:^3.1.1" + on-exit-leak-free: "npm:^2.1.0" + pino-abstract-transport: "npm:^1.2.0" + pino-std-serializers: "npm:^7.0.0" + process-warning: "npm:^3.0.0" + quick-format-unescaped: "npm:^4.0.3" + real-require: "npm:^0.2.0" + safe-stable-stringify: "npm:^2.3.1" + sonic-boom: "npm:^4.0.1" + thread-stream: "npm:^3.0.0" + bin: + pino: bin.js + checksum: 10c0/ab1e81b3e5a91852136d80a592939883eeb81442e5d3a2c070bdbdeb47c5aaa297ead246530b10eb6d5ff59445f4645d1333d342f255d9f002f73aea843e74ee + languageName: node + linkType: hard + "postcss-value-parser@npm:^4.2.0": version: 4.2.0 resolution: "postcss-value-parser@npm:4.2.0" @@ -3456,6 +3572,20 @@ __metadata: languageName: node linkType: hard +"process-warning@npm:^3.0.0": + version: 3.0.0 + resolution: "process-warning@npm:3.0.0" + checksum: 10c0/60f3c8ddee586f0706c1e6cb5aa9c86df05774b9330d792d7c8851cf0031afd759d665404d07037e0b4901b55c44a423f07bdc465c63de07d8d23196bb403622 + languageName: node + linkType: hard + +"process@npm:^0.11.10": + version: 0.11.10 + resolution: "process@npm:0.11.10" + checksum: 10c0/40c3ce4b7e6d4b8c3355479df77aeed46f81b279818ccdc500124e6a5ab882c0cc81ff7ea16384873a95a74c4570b01b120f287abbdd4c877931460eca6084b3 + languageName: node + linkType: hard + "promise-retry@npm:^2.0.1": version: 2.0.1 resolution: "promise-retry@npm:2.0.1" @@ -3476,6 +3606,13 @@ __metadata: languageName: node linkType: hard +"quick-format-unescaped@npm:^4.0.3": + version: 4.0.4 + resolution: "quick-format-unescaped@npm:4.0.4" + checksum: 10c0/fe5acc6f775b172ca5b4373df26f7e4fd347975578199e7d74b2ae4077f0af05baa27d231de1e80e8f72d88275ccc6028568a7a8c9ee5e7368ace0e18eff93a4 + languageName: node + linkType: hard + "react-error-overlay@npm:6.0.9": version: 6.0.9 resolution: "react-error-overlay@npm:6.0.9" @@ -3490,6 +3627,19 @@ __metadata: languageName: node linkType: hard +"readable-stream@npm:^4.0.0": + version: 4.5.2 + resolution: "readable-stream@npm:4.5.2" + dependencies: + abort-controller: "npm:^3.0.0" + buffer: "npm:^6.0.3" + events: "npm:^3.3.0" + process: "npm:^0.11.10" + string_decoder: "npm:^1.3.0" + checksum: 10c0/a2c80e0e53aabd91d7df0330929e32d0a73219f9477dbbb18472f6fdd6a11a699fc5d172a1beff98d50eae4f1496c950ffa85b7cc2c4c196963f289a5f39275d + languageName: node + linkType: hard + "readdirp@npm:~3.6.0": version: 3.6.0 resolution: "readdirp@npm:3.6.0" @@ -3499,6 +3649,13 @@ __metadata: languageName: node linkType: hard +"real-require@npm:^0.2.0": + version: 0.2.0 + resolution: "real-require@npm:0.2.0" + checksum: 10c0/23eea5623642f0477412ef8b91acd3969015a1501ed34992ada0e3af521d3c865bb2fe4cdbfec5fe4b505f6d1ef6a03e5c3652520837a8c3b53decff7e74b6a0 + languageName: node + linkType: hard + "regenerator-runtime@npm:^0.13.7": version: 0.13.11 resolution: "regenerator-runtime@npm:0.13.11" @@ -3520,13 +3677,20 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:^5.0.1": +"safe-buffer@npm:^5.0.1, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 languageName: node linkType: hard +"safe-stable-stringify@npm:^2.3.1": + version: 2.4.3 + resolution: "safe-stable-stringify@npm:2.4.3" + checksum: 10c0/81dede06b8f2ae794efd868b1e281e3c9000e57b39801c6c162267eb9efda17bd7a9eafa7379e1f1cacd528d4ced7c80d7460ad26f62ada7c9e01dec61b2e768 + languageName: node + linkType: hard + "safer-buffer@npm:>= 2.1.2 < 3.0.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -3547,6 +3711,13 @@ __metadata: languageName: node linkType: hard +"secure-json-parse@npm:^2.4.0": + version: 2.7.0 + resolution: "secure-json-parse@npm:2.7.0" + checksum: 10c0/f57eb6a44a38a3eeaf3548228585d769d788f59007454214fab9ed7f01fbf2e0f1929111da6db28cf0bcc1a2e89db5219a59e83eeaec3a54e413a0197ce879e4 + languageName: node + linkType: hard + "semver@npm:^7.3.5, semver@npm:^7.5.2, semver@npm:^7.6.0": version: 7.6.0 resolution: "semver@npm:7.6.0" @@ -3687,6 +3858,15 @@ __metadata: languageName: node linkType: hard +"sonic-boom@npm:^4.0.1": + version: 4.0.1 + resolution: "sonic-boom@npm:4.0.1" + dependencies: + atomic-sleep: "npm:^1.0.0" + checksum: 10c0/7b467f2bc8af7ff60bf210382f21c59728cc4b769af9b62c31dd88723f5cc472752d2320736cc366acc7c765ddd5bec3072c033b0faf249923f576a7453ba9d3 + languageName: node + linkType: hard + "source-map-js@npm:>=0.6.2 <2.0.0": version: 1.2.0 resolution: "source-map-js@npm:1.2.0" @@ -3701,6 +3881,13 @@ __metadata: languageName: node linkType: hard +"split2@npm:^4.0.0": + version: 4.2.0 + resolution: "split2@npm:4.2.0" + checksum: 10c0/b292beb8ce9215f8c642bb68be6249c5a4c7f332fc8ecadae7be5cbdf1ea95addc95f0459ef2e7ad9d45fd1064698a097e4eb211c83e772b49bc0ee423e91534 + languageName: node + linkType: hard + "sprintf-js@npm:^1.1.3": version: 1.1.3 resolution: "sprintf-js@npm:1.1.3" @@ -3753,6 +3940,15 @@ __metadata: languageName: node linkType: hard +"string_decoder@npm:^1.3.0": + version: 1.3.0 + resolution: "string_decoder@npm:1.3.0" + dependencies: + safe-buffer: "npm:~5.2.0" + checksum: 10c0/810614ddb030e271cd591935dcd5956b2410dd079d64ff92a1844d6b7588bf992b3e1b69b0f4d34a3e06e0bd73046ac646b5264c1987b20d0601f81ef35d731d + languageName: node + linkType: hard + "strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" @@ -3778,6 +3974,13 @@ __metadata: languageName: node linkType: hard +"strip-json-comments@npm:^3.1.1": + version: 3.1.1 + resolution: "strip-json-comments@npm:3.1.1" + checksum: 10c0/9681a6257b925a7fa0f285851c0e613cc934a50661fa7bb41ca9cbbff89686bb4a0ee366e6ecedc4daafd01e83eee0720111ab294366fe7c185e935475ebcecd + languageName: node + linkType: hard + "supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -3834,6 +4037,15 @@ __metadata: languageName: node linkType: hard +"thread-stream@npm:^3.0.0": + version: 3.1.0 + resolution: "thread-stream@npm:3.1.0" + dependencies: + real-require: "npm:^0.2.0" + checksum: 10c0/c36118379940b77a6ef3e6f4d5dd31e97b8210c3f7b9a54eb8fe6358ab173f6d0acfaf69b9c3db024b948c0c5fd2a7df93e2e49151af02076b35ada3205ec9a6 + languageName: node + linkType: hard + "timsort@npm:^0.3.0": version: 0.3.0 resolution: "timsort@npm:0.3.0" @@ -3888,16 +4100,6 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^3.9": - version: 3.9.10 - resolution: "typescript@npm:3.9.10" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/863cc06070fa18a0f9c6a83265fb4922a8b51bf6f2c6760fb0b73865305ce617ea4bc6477381f9f4b7c3a8cb4a455b054f5469e6e41307733fe6a2bd9aae82f8 - languageName: node - linkType: hard - "typescript@patch:typescript@npm%3A>=3.0.0#optional!builtin, typescript@patch:typescript@npm%3A^5.4.4#optional!builtin": version: 5.4.5 resolution: "typescript@patch:typescript@npm%3A5.4.5#optional!builtin::version=5.4.5&hash=5adc0c" @@ -3908,16 +4110,6 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^3.9#optional!builtin": - version: 3.9.10 - resolution: "typescript@patch:typescript@npm%3A3.9.10#optional!builtin::version=3.9.10&hash=3bd3d3" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/9041fb3886e7d6a560f985227b8c941d17a750f2edccb5f9b3a15a2480574654d9be803ad4a14aabcc2f2553c4d272a25fd698a7c42692f03f66b009fb46883c - languageName: node - linkType: hard - "undici-types@npm:~5.26.4": version: 5.26.5 resolution: "undici-types@npm:5.26.5"