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"