diff --git a/.gitignore b/.gitignore index 80ce78d..24eb63b 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,5 @@ **/.pnp.* node_modules/ **/dist/ -**/.parcel-cache/ \ No newline at end of file +**/.parcel-cache/ +server/config.toml \ No newline at end of file diff --git a/msagent.js/package.json b/msagent.js/package.json index 8d7317b..159806b 100644 --- a/msagent.js/package.json +++ b/msagent.js/package.json @@ -10,8 +10,14 @@ "scripts": { "build": "parcel build" }, +<<<<<<< HEAD "type": "module", "source": "src/index.ts", "module": "./dist/index.js", "types": "./dist/index.d.ts" +======= + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "type": "module" +>>>>>>> 2414658 (working chat and TTS) } diff --git a/protocol/package.json b/protocol/package.json index eed7b8b..3de5a67 100644 --- a/protocol/package.json +++ b/protocol/package.json @@ -8,5 +8,6 @@ "build": "tsc" }, "main": "./dist/protocol.js", - "types": "./dist/protocol.d.ts" + "types": "./dist/protocol.d.ts", + "type": "module" } diff --git a/protocol/src/protocol.ts b/protocol/src/protocol.ts index e4bcf39..a310015 100644 --- a/protocol/src/protocol.ts +++ b/protocol/src/protocol.ts @@ -6,7 +6,7 @@ export enum MSAgentProtocolMessageType { Init = "init", AddUser = "adduser", RemoveUser = "remuser", - Message = "msg" + Chat = "chat" } export interface MSAgentProtocolMessage { @@ -19,6 +19,7 @@ export interface MSAgentJoinMessage extends MSAgentProtocolMessage { op: MSAgentProtocolMessageType.Join, data: { username: string; + agent: string; } } @@ -34,7 +35,12 @@ export interface MSAgentTalkMessage extends MSAgentProtocolMessage { export interface MSAgentInitMessage extends MSAgentProtocolMessage { op: MSAgentProtocolMessageType.Init, data: { - users: string[] + username: string + agent: string + users: { + username: string, + agent: string + }[] } } @@ -42,6 +48,7 @@ export interface MSAgentAddUserMessage extends MSAgentProtocolMessage { op: MSAgentProtocolMessageType.AddUser, data: { username: string; + agent: string; } } @@ -50,4 +57,13 @@ export interface MSAgentRemoveUserMessage extends MSAgentProtocolMessage { data: { username: string; } +} + +export interface MSAgentChatMessage extends MSAgentProtocolMessage { + op: MSAgentProtocolMessageType.Chat, + data: { + username: string; + message: string; + audio? : string | undefined; + } } \ No newline at end of file diff --git a/server/config.example.toml b/server/config.example.toml new file mode 100644 index 0000000..7d535b3 --- /dev/null +++ b/server/config.example.toml @@ -0,0 +1,11 @@ +[http] +host = "127.0.0.1" +port = 3000 + +[tts] +enabled = true +# https://git.computernewb.com/computernewb/SAPIServer +server = "http://127.0.0.1:3001" +voice = "Microsoft Sam" +tempDir = "/tmp/msac-tts" +wavExpirySeconds = 60 \ No newline at end of file diff --git a/server/package.json b/server/package.json index 80ab3c9..e5d4679 100644 --- a/server/package.json +++ b/server/package.json @@ -11,8 +11,11 @@ "typescript": "^5.5.3" }, "dependencies": { + "@fastify/static": "^7.0.4", "@fastify/websocket": "^10.0.1", "fastify": "^4.28.1", + "html-entities": "^2.5.2", + "toml": "^3.0.0", "ws": "^8.17.1" }, "type": "module" diff --git a/server/src/client.ts b/server/src/client.ts index 23809b4..c79272f 100644 --- a/server/src/client.ts +++ b/server/src/client.ts @@ -1,10 +1,23 @@ import EventEmitter from "events"; import { WebSocket } from "ws"; -import { MSAgentProtocolMessage, MSAgentProtocolMessageType } from '@msagent-chat/protocol'; +import { MSAgentJoinMessage, MSAgentProtocolMessage, MSAgentProtocolMessageType, MSAgentTalkMessage } from '@msagent-chat/protocol'; import { MSAgentChatRoom } from "./room.js"; +import * as htmlentities from 'html-entities'; + +// Event types + +export interface Client { + on(event: 'join', listener: () => void): this; + on(event: 'close', listener: () => void): this; + on(event: 'talk', listener: (msg: string) => void): this; + + on(event: string, listener: Function): this; +} export class Client extends EventEmitter { username: string | null; + agent: string | null; + room: MSAgentChatRoom; socket: WebSocket; constructor(socket: WebSocket, room: MSAgentChatRoom) { @@ -12,6 +25,7 @@ export class Client extends EventEmitter { this.socket = socket; this.room = room; this.username = null; + this.agent = null; this.socket.on('message', (msg, isBinary) => { if (isBinary) { this.socket.close(); @@ -47,11 +61,22 @@ export class Client extends EventEmitter { } switch (msg.op) { case MSAgentProtocolMessageType.Join: { - + let joinMsg = msg as MSAgentJoinMessage; + if (!joinMsg.data || !joinMsg.data.username || !joinMsg.data.username) { + this.socket.close(); + return; + } + this.username = htmlentities.encode(joinMsg.data.username); + this.agent = htmlentities.encode(joinMsg.data.agent); + this.emit('join'); break; } case MSAgentProtocolMessageType.Talk: { - + let talkMsg = msg as MSAgentTalkMessage; + if (!talkMsg.data || !talkMsg.data.msg) { + return; + } + this.emit('talk', htmlentities.encode(talkMsg.data.msg)); break; } } diff --git a/server/src/config.ts b/server/src/config.ts new file mode 100644 index 0000000..8feadcf --- /dev/null +++ b/server/src/config.ts @@ -0,0 +1,15 @@ +export interface IConfig { + http: { + host: string; + port: number; + } + tts: TTSConfig +} + +export interface TTSConfig { + enabled: boolean; + server: string; + voice: string; + tempDir: string; + wavExpirySeconds: number; +} \ No newline at end of file diff --git a/server/src/index.ts b/server/src/index.ts index 5147e34..52eb36b 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,7 +1,32 @@ import Fastify from 'fastify'; import FastifyWS from '@fastify/websocket'; +import FastifyStatic from '@fastify/static'; import { Client } from './client.js'; import { MSAgentChatRoom } from './room.js'; +import * as toml from 'toml'; +import { IConfig } from './config.js'; +import * as fs from 'fs'; +import { TTSClient } from './tts.js'; + +let config: IConfig; +let configPath: string; +if (process.argv.length < 3) + configPath = "./config.toml"; +else + configPath = process.argv[2]; + +if (!fs.existsSync(configPath)) { + console.error(`${configPath} not found. Please copy config.example.toml and fill out fields.`); + process.exit(1); +} + +try { + let configRaw = fs.readFileSync(configPath, "utf-8"); + config = toml.parse(configRaw); +} catch (e) { + console.error(`Failed to read or parse ${configPath}: ${(e as Error).message}`); + process.exit(1); +} const app = Fastify({ logger: true, @@ -9,16 +34,24 @@ const app = Fastify({ app.register(FastifyWS); -let room = new MSAgentChatRoom(); +let tts = null; -app.get("/socket", {websocket: true}, (socket, req) => { - let client = new Client(socket, room); - room.addClient(client); +if (config.tts.enabled) { + tts = new TTSClient(config.tts); + app.register(FastifyStatic, { + root: config.tts.tempDir, + prefix: "/api/tts/" + }); +} + +let room = new MSAgentChatRoom(tts); + +app.register(async app => { + app.get("/socket", {websocket: true}, (socket, req) => { + let client = new Client(socket, room); + room.addClient(client); + }); }); -let port; -if (process.argv.length < 3 || isNaN(port = parseInt(process.argv[2]))) { - console.error("Usage: index.js [port]"); - process.exit(1); -} -app.listen({host: "127.0.0.1", port}); \ No newline at end of file + +app.listen({host: config.http.host, port: config.http.port}); \ No newline at end of file diff --git a/server/src/room.ts b/server/src/room.ts index 44ae164..8d4bdd0 100644 --- a/server/src/room.ts +++ b/server/src/room.ts @@ -1,11 +1,14 @@ -import { MSAgentAddUserMessage, MSAgentInitMessage, MSAgentProtocolMessage, MSAgentProtocolMessageType, MSAgentRemoveUserMessage } from "@msagent-chat/protocol"; +import { MSAgentAddUserMessage, MSAgentChatMessage, MSAgentInitMessage, MSAgentProtocolMessage, MSAgentProtocolMessageType, MSAgentRemoveUserMessage } from "@msagent-chat/protocol"; import { Client } from "./client.js"; +import { TTSClient } from "./tts.js"; export class MSAgentChatRoom { clients: Client[]; - - constructor() { + tts: TTSClient | null; + msgId : number = 0; + constructor(tts: TTSClient | null) { this.clients = []; + this.tts = tts; } addClient(client: Client) { @@ -24,26 +27,47 @@ export class MSAgentChatRoom { } }); client.on('join', () => { - client.send(this.getInitMsg()); + let initmsg : MSAgentInitMessage = { + op: MSAgentProtocolMessageType.Init, + data: { + username: client.username!, + agent: client.agent!, + users: this.clients.filter(c => c.username !== null).map(c => { + return { + username: c.username!, + agent: c.agent! + } + }) + } + }; + client.send(initmsg); let msg: MSAgentAddUserMessage = { op: MSAgentProtocolMessageType.AddUser, data: { - username: client.username! + username: client.username!, + agent: client.agent! } } for (const _client of this.getActiveClients().filter(c => c !== client)) { _client.send(msg); } }); - } - - private getInitMsg(): MSAgentInitMessage { - return { - op: MSAgentProtocolMessageType.Init, - data: { - users: this.clients.filter(c => c.username !== null).map(c => c.username!) + client.on('talk', async message => { + let msg: MSAgentChatMessage = { + op: MSAgentProtocolMessageType.Chat, + data: { + username: client.username!, + message + } + }; + if (this.tts !== null) { + let filename = await this.tts.synthesizeToFile(message, (++this.msgId).toString(10)); + msg.data.audio = "/api/tts/" + filename; } - } + for (const _client of this.getActiveClients()) { + _client.send(msg); + } + }); } private getActiveClients() { diff --git a/server/src/tts.ts b/server/src/tts.ts new file mode 100644 index 0000000..5b589c1 --- /dev/null +++ b/server/src/tts.ts @@ -0,0 +1,70 @@ +import path from "path"; +import * as fs from 'fs/promises'; +import { TTSConfig } from "./config.js"; +import { Readable } from "stream"; +import { ReadableStream } from 'node:stream/web'; +import { finished } from "node:stream/promises"; + +export class TTSClient { + private config: TTSConfig; + private deleteOps: Map + + constructor(config: TTSConfig) { + this.config = config; + if (!this.config.tempDir.endsWith('/')) this.config.tempDir += '/'; + this.deleteOps = new Map(); + } + + async ensureDirectoryExists() { + let stat; + try { + stat = await fs.stat(this.config.tempDir); + } catch (e) { + let error = e as NodeJS.ErrnoException; + switch (error.code) { + case "ENOTDIR": { + console.warn("File exists at TTS temp directory path. Unlinking..."); + await fs.unlink(this.config.tempDir.substring(0, this.config.tempDir.length - 1)); + // intentional fall-through + } + case "ENOENT": { + await fs.mkdir(this.config.tempDir, {recursive: true}); + break; + } + default: { + console.error(`Cannot access TTS Temp dir: ${error.message}`); + process.exit(1); + break; + } + } + } + } + + async synthesizeToFile(text: string, id: string) : Promise { + this.ensureDirectoryExists(); + let wavFilename = id + ".wav" + let wavPath = path.join(this.config.tempDir, wavFilename); + try { + await fs.unlink(wavPath); + } catch {} + let file = await fs.open(wavPath, fs.constants.O_CREAT | fs.constants.O_TRUNC | fs.constants.O_WRONLY); + let stream = file.createWriteStream(); + let res = await fetch(this.config.server + "/api/synthesize", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + text, + voice: this.config.voice + }) + }); + await finished(Readable.fromWeb(res.body as ReadableStream).pipe(stream)); + await file.close(); + this.deleteOps.set(wavPath, setTimeout(async () => { + await fs.unlink(wavPath); + this.deleteOps.delete(wavPath); + }, this.config.wavExpirySeconds * 1000)); + return wavFilename; + } +} \ No newline at end of file diff --git a/webapp/assets/fonts/micross.ttf b/webapp/assets/fonts/micross.ttf deleted file mode 100644 index 772d5e8..0000000 Binary files a/webapp/assets/fonts/micross.ttf and /dev/null differ diff --git a/webapp/package.json b/webapp/package.json index 0b2a61d..8d3850d 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -15,5 +15,8 @@ "run-script-os": "^1.1.6", "typescript": "^5.5.3" }, - "type": "module" + "type": "module", + "dependencies": { + "nanoevents": "^9.0.0" + } } diff --git a/webapp/src/css/style.css b/webapp/src/css/style.css index 30a1a24..67c881d 100644 --- a/webapp/src/css/style.css +++ b/webapp/src/css/style.css @@ -73,6 +73,7 @@ body { flex-grow: 1; margin-right: 4px; margin-left: 4px; + height: 100%; } #chatSentBtn { diff --git a/webapp/src/ts/client.ts b/webapp/src/ts/client.ts new file mode 100644 index 0000000..42e3665 --- /dev/null +++ b/webapp/src/ts/client.ts @@ -0,0 +1,144 @@ +import { createNanoEvents, Emitter, Unsubscribe } from 'nanoevents'; +import { MSAgentAddUserMessage, MSAgentChatMessage, MSAgentInitMessage, MSAgentJoinMessage, MSAgentProtocolMessage, MSAgentProtocolMessageType, MSAgentRemoveUserMessage, MSAgentTalkMessage } from '@msagent-chat/protocol'; +import { User } from './user'; + +export interface MSAgentClientEvents { + close: () => void; + join: () => void; + adduser: (user: User) => void; + remuser: (user: User) => void; + chat: (user: User, msg: string) => void; +} + +export class MSAgentClient { + private url: string; + private socket: WebSocket | null; + private events: Emitter; + private users: User[]; + + private username: string | null = null; + private agent: string | null = null; + + constructor(url: string) { + this.url = url; + this.socket = null; + this.events = createNanoEvents(); + this.users = []; + } + + on(event: E, callback: MSAgentClientEvents[E]): Unsubscribe { + return this.events.on(event, callback); + } + + connect(): Promise { + return new Promise(res => { + let url = new URL(this.url); + switch (url.protocol) { + case "http:": + url.protocol = "ws:"; + break; + case "https": + url.protocol = "wss:"; + break; + default: + throw new Error(`Unknown protocol ${url.protocol}`); + } + url.pathname = "/socket" + this.socket = new WebSocket(url); + this.socket.addEventListener('open', () => res()); + this.socket.addEventListener('message', e => { + if (e.data instanceof ArrayBuffer) { + // server should not send binary + return; + } + this.handleMessage(e.data); + }); + this.socket.addEventListener('close', () => { + this.events.emit('close'); + }) + }); + } + + send(msg: MSAgentProtocolMessage) { + if (this.socket === null || this.socket.readyState !== this.socket.OPEN) { + console.error("Tried to send data on a closed or uninitialized socket"); + return; + } + let data = JSON.stringify(msg); + this.socket!.send(data); + } + + join(username: string, agent: string) { + if (this.socket === null || this.socket.readyState !== this.socket.OPEN) { + throw new Error("Tried to join() on a closed or uninitialized socket"); + } + return new Promise(res => { + let msg: MSAgentJoinMessage = { + op: MSAgentProtocolMessageType.Join, + data: { + username, + agent + } + }; + let u = this.on('join', () => { + u(); + res(); + }); + this.send(msg); + }); + } + + talk(msg: string) { + let talkMsg: MSAgentTalkMessage = { + op: MSAgentProtocolMessageType.Talk, + data: { + msg + } + }; + this.send(talkMsg); + } + + private handleMessage(data: string) { + let msg: MSAgentProtocolMessage; + try { + msg = JSON.parse(data); + } catch (e) { + console.error(`Failed to parse message from server: ${(e as Error).message}`); + return; + } + switch (msg.op) { + case MSAgentProtocolMessageType.Init: { + let initMsg = msg as MSAgentInitMessage; + this.username = initMsg.data.username; + this.agent = initMsg.data.agent; + this.users.push(...initMsg.data.users.map(u => new User(u.username, u.agent))); + this.events.emit('join'); + break; + } + case MSAgentProtocolMessageType.AddUser: { + let addUserMsg = msg as MSAgentAddUserMessage + let user = new User(addUserMsg.data.username, addUserMsg.data.agent); + this.events.emit('adduser', user); + break; + } + case MSAgentProtocolMessageType.RemoveUser: { + let remUserMsg = msg as MSAgentRemoveUserMessage; + let user = this.users.find(u => u.username === remUserMsg.data.username); + if (!user) return; + this.users.splice(this.users.indexOf(user), 1); + this.events.emit('remuser', user); + break; + } + case MSAgentProtocolMessageType.Chat: { + let chatMsg = msg as MSAgentChatMessage; + let user = this.users.find(u => u.username === chatMsg.data.username); + this.events.emit('chat', user, chatMsg.data.message); + if (chatMsg.data.audio !== undefined) { + let audio = new Audio(this.url + chatMsg.data.audio); + audio.play(); + } + break; + } + } + } +} \ No newline at end of file diff --git a/webapp/src/ts/main.ts b/webapp/src/ts/main.ts index cea3361..c971cde 100644 --- a/webapp/src/ts/main.ts +++ b/webapp/src/ts/main.ts @@ -1,13 +1,18 @@ import { MSWindow, MSWindowStartPosition } from "./MSWindow.js"; - import { agentInit } from "@msagent-chat/msagent.js"; +import { MSAgentClient } from "./client.js"; + +let Room : MSAgentClient | null = null; const elements = { logonView: document.getElementById("logonView") as HTMLDivElement, logonWindow: document.getElementById("logonWindow") as HTMLDivElement, logonForm: document.getElementById("logonForm") as HTMLFormElement, + logonUsername: document.getElementById("logonUsername") as HTMLInputElement, chatView: document.getElementById("chatView") as HTMLDivElement, + chatInput: document.getElementById("chatInput") as HTMLInputElement, + chatSendBtn: document.getElementById("chatSendBtn") as HTMLButtonElement } let logonWindow = new MSWindow(elements.logonWindow, { @@ -21,7 +26,22 @@ logonWindow.show(); elements.logonForm.addEventListener('submit', e => { e.preventDefault(); + connectToRoom(); +}); +elements.chatInput.addEventListener('keypress', e => { + // enter + if (e.key === "Enter") talk(); +}); + +elements.chatSendBtn.addEventListener('click', () => { + talk(); +}); + +async function connectToRoom() { + Room = new MSAgentClient("http://127.0.0.1:3000"); + await Room.connect(); + await Room.join(elements.logonUsername.value, "test"); logonWindow.hide(); elements.logonView.style.display = "none"; elements.chatView.style.display = "block"; @@ -30,3 +50,10 @@ elements.logonForm.addEventListener('submit', e => { document.addEventListener('DOMContentLoaded', async () => { await agentInit(); }); +} + +function talk() { + if (Room === null) return; + Room.talk(elements.chatInput.value); + elements.chatInput.value = ""; +} diff --git a/webapp/src/ts/user.ts b/webapp/src/ts/user.ts new file mode 100644 index 0000000..6b5f588 --- /dev/null +++ b/webapp/src/ts/user.ts @@ -0,0 +1,9 @@ +export class User { + username: string; + agent: string; + + constructor(username: string, agent: string) { + this.username = username; + this.agent = agent; + } +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index d6e7142..e9cd2f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -34,6 +34,13 @@ __metadata: languageName: node linkType: hard +"@fastify/accept-negotiator@npm:^1.0.0": + version: 1.1.0 + resolution: "@fastify/accept-negotiator@npm:1.1.0" + checksum: 10c0/1cb9a298c992b812869158ddc6093557a877b30e5f77618a7afea985a0667c50bc7113593bf0f7f9dc9b82b94c16e8ab127a0afc3efde6677fd645539f6d08e5 + languageName: node + linkType: hard + "@fastify/ajv-compiler@npm:^3.5.0": version: 3.6.0 resolution: "@fastify/ajv-compiler@npm:3.6.0" @@ -70,6 +77,33 @@ __metadata: languageName: node linkType: hard +"@fastify/send@npm:^2.0.0": + version: 2.1.0 + resolution: "@fastify/send@npm:2.1.0" + dependencies: + "@lukeed/ms": "npm:^2.0.1" + escape-html: "npm:~1.0.3" + fast-decode-uri-component: "npm:^1.0.1" + http-errors: "npm:2.0.0" + mime: "npm:^3.0.0" + checksum: 10c0/0e1c10022660fa1f1959b7c414d1be2c47ab42be1da8e21cd72a4df3104c516fdf7b590ee67f897037dd4c85b716fac63929e894d7699623549646604f6db14b + languageName: node + linkType: hard + +"@fastify/static@npm:^7.0.4": + version: 7.0.4 + resolution: "@fastify/static@npm:7.0.4" + dependencies: + "@fastify/accept-negotiator": "npm:^1.0.0" + "@fastify/send": "npm:^2.0.0" + content-disposition: "npm:^0.5.3" + fastify-plugin: "npm:^4.0.0" + fastq: "npm:^1.17.0" + glob: "npm:^10.3.4" + checksum: 10c0/4ca29a37226880fcc8e711b4a3b93858d4e23dd0458a9f65373b63b3df4768610c8cfbd9e51b749ab52d65b9139142a5c69571740265c9ace1660e268ebb38f6 + languageName: node + linkType: hard + "@fastify/websocket@npm:^10.0.1": version: 10.0.1 resolution: "@fastify/websocket@npm:10.0.1" @@ -153,6 +187,13 @@ __metadata: languageName: node linkType: hard +"@lukeed/ms@npm:^2.0.1": + version: 2.0.2 + resolution: "@lukeed/ms@npm:2.0.2" + checksum: 10c0/843b922717313bcde4943f478145d8bc13115b9b91d83bbaed53739b5644122984412310aed574792f4e6b492f2cbda178175f601856d310f67e14834c3f17a0 + languageName: node + linkType: hard + "@mischnic/json-sourcemap@npm:^0.1.0": version: 0.1.1 resolution: "@mischnic/json-sourcemap@npm:0.1.1" @@ -164,12 +205,19 @@ __metadata: languageName: node linkType: hard +<<<<<<< HEAD "@msagent-chat/msagent.js@npm:*, @msagent-chat/msagent.js@workspace:msagent.js": version: 0.0.0-use.local resolution: "@msagent-chat/msagent.js@workspace:msagent.js" dependencies: "@parcel/core": "npm:^2.12.0" parcel: "npm:^2.12.0" +======= +"@msagent-chat/msagent.js@workspace:msagent.js": + version: 0.0.0-use.local + resolution: "@msagent-chat/msagent.js@workspace:msagent.js" + dependencies: +>>>>>>> 2414658 (working chat and TTS) typescript: "npm:^5.5.3" languageName: unknown linkType: soft @@ -186,10 +234,13 @@ __metadata: version: 0.0.0-use.local resolution: "@msagent-chat/server@workspace:server" dependencies: + "@fastify/static": "npm:^7.0.4" "@fastify/websocket": "npm:^10.0.1" "@types/node": "npm:^20.14.9" "@types/ws": "npm:^8.5.10" fastify: "npm:^4.28.1" + html-entities: "npm:^2.5.2" + toml: "npm:^3.0.0" typescript: "npm:^5.5.3" ws: "npm:^8.17.1" languageName: unknown @@ -201,6 +252,7 @@ __metadata: dependencies: "@msagent-chat/msagent.js": "npm:*" "@parcel/core": "npm:^2.12.0" + nanoevents: "npm:^9.0.0" parcel: "npm:^2.12.0" run-script-os: "npm:^1.1.6" typescript: "npm:^5.5.3" @@ -1660,6 +1712,15 @@ __metadata: languageName: node linkType: hard +"content-disposition@npm:^0.5.3": + version: 0.5.4 + resolution: "content-disposition@npm:0.5.4" + dependencies: + safe-buffer: "npm:5.2.1" + checksum: 10c0/bac0316ebfeacb8f381b38285dc691c9939bf0a78b0b7c2d5758acadad242d04783cee5337ba7d12a565a19075af1b3c11c728e1e4946de73c6ff7ce45f3f1bb + languageName: node + linkType: hard + "cookie@npm:^0.6.0": version: 0.6.0 resolution: "cookie@npm:0.6.0" @@ -1746,6 +1807,13 @@ __metadata: languageName: node linkType: hard +"depd@npm:2.0.0": + version: 2.0.0 + resolution: "depd@npm:2.0.0" + checksum: 10c0/58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c + languageName: node + linkType: hard + "detect-libc@npm:^1.0.3": version: 1.0.3 resolution: "detect-libc@npm:1.0.3" @@ -1916,6 +1984,13 @@ __metadata: languageName: node linkType: hard +"escape-html@npm:~1.0.3": + version: 1.0.3 + resolution: "escape-html@npm:1.0.3" + checksum: 10c0/524c739d776b36c3d29fa08a22e03e8824e3b2fd57500e5e44ecf3cc4707c34c60f9ca0781c0e33d191f2991161504c295e98f68c78fe7baa6e57081ec6ac0a3 + languageName: node + linkType: hard + "escape-string-regexp@npm:^1.0.5": version: 1.0.5 resolution: "escape-string-regexp@npm:1.0.5" @@ -2034,7 +2109,7 @@ __metadata: languageName: node linkType: hard -"fastq@npm:^1.17.1": +"fastq@npm:^1.17.0, fastq@npm:^1.17.1": version: 1.17.1 resolution: "fastq@npm:1.17.1" dependencies: @@ -2105,7 +2180,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.2.2, glob@npm:^10.3.10": +"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.4": version: 10.4.2 resolution: "glob@npm:10.4.2" dependencies: @@ -2151,6 +2226,13 @@ __metadata: languageName: node linkType: hard +"html-entities@npm:^2.5.2": + version: 2.5.2 + resolution: "html-entities@npm:2.5.2" + checksum: 10c0/f20ffb4326606245c439c231de40a7c560607f639bf40ffbfb36b4c70729fd95d7964209045f1a4e62fe17f2364cef3d6e49b02ea09016f207fde51c2211e481 + languageName: node + linkType: hard + "htmlnano@npm:^2.0.0": version: 2.1.1 resolution: "htmlnano@npm:2.1.1" @@ -2207,6 +2289,19 @@ __metadata: languageName: node linkType: hard +"http-errors@npm:2.0.0": + version: 2.0.0 + resolution: "http-errors@npm:2.0.0" + dependencies: + depd: "npm:2.0.0" + inherits: "npm:2.0.4" + setprototypeof: "npm:1.2.0" + statuses: "npm:2.0.1" + toidentifier: "npm:1.0.1" + checksum: 10c0/fc6f2715fe188d091274b5ffc8b3657bd85c63e969daa68ccb77afb05b071a4b62841acb7a21e417b5539014dff2ebf9550f0b14a9ff126f2734a7c1387f8e19 + languageName: node + linkType: hard + "http-proxy-agent@npm:^7.0.0": version: 7.0.2 resolution: "http-proxy-agent@npm:7.0.2" @@ -2267,7 +2362,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:^2.0.3": +"inherits@npm:2.0.4, inherits@npm:^2.0.3": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 @@ -2623,6 +2718,15 @@ __metadata: languageName: node linkType: hard +"mime@npm:^3.0.0": + version: 3.0.0 + resolution: "mime@npm:3.0.0" + bin: + mime: cli.js + checksum: 10c0/402e792a8df1b2cc41cb77f0dcc46472b7944b7ec29cb5bbcd398624b6b97096728f1239766d3fdeb20551dd8d94738344c195a6ea10c4f906eb0356323b0531 + languageName: node + linkType: hard + "minimatch@npm:^9.0.4": version: 9.0.5 resolution: "minimatch@npm:9.0.5" @@ -2785,6 +2889,13 @@ __metadata: languageName: node linkType: hard +"nanoevents@npm:^9.0.0": + version: 9.0.0 + resolution: "nanoevents@npm:9.0.0" + checksum: 10c0/af74c96d0d54ef0ddf6fd163d961b23ec4219ac51d0b70135a2cae49fac1107ddccdcbf7a120601e0bfdd38b15de811f3ce43da5bab967fdee9684e52e4cb5b7 + languageName: node + linkType: hard + "negotiator@npm:^0.6.3": version: 0.6.3 resolution: "negotiator@npm:0.6.3" @@ -3253,7 +3364,7 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:^5.0.1, safe-buffer@npm:~5.2.0": +"safe-buffer@npm:5.2.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 @@ -3306,6 +3417,13 @@ __metadata: languageName: node linkType: hard +"setprototypeof@npm:1.2.0": + version: 1.2.0 + resolution: "setprototypeof@npm:1.2.0" + checksum: 10c0/68733173026766fa0d9ecaeb07f0483f4c2dc70ca376b3b7c40b7cda909f94b0918f6c5ad5ce27a9160bdfb475efaa9d5e705a11d8eaae18f9835d20976028bc + languageName: node + linkType: hard + "shebang-command@npm:^2.0.0": version: 2.0.0 resolution: "shebang-command@npm:2.0.0" @@ -3410,6 +3528,13 @@ __metadata: languageName: node linkType: hard +"statuses@npm:2.0.1": + version: 2.0.1 + resolution: "statuses@npm:2.0.1" + checksum: 10c0/34378b207a1620a24804ce8b5d230fea0c279f00b18a7209646d5d47e419d1cc23e7cbf33a25a1e51ac38973dc2ac2e1e9c647a8e481ef365f77668d72becfd0 + languageName: node + linkType: hard + "stream-shift@npm:^1.0.2": version: 1.0.3 resolution: "stream-shift@npm:1.0.3" @@ -3554,6 +3679,20 @@ __metadata: languageName: node linkType: hard +"toidentifier@npm:1.0.1": + version: 1.0.1 + resolution: "toidentifier@npm:1.0.1" + checksum: 10c0/93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1 + languageName: node + linkType: hard + +"toml@npm:^3.0.0": + version: 3.0.0 + resolution: "toml@npm:3.0.0" + checksum: 10c0/8d7ed3e700ca602e5419fca343e1c595eb7aa177745141f0761a5b20874b58ee5c878cd045c408da9d130cb2b611c639912210ba96ce2f78e443569aa8060c18 + languageName: node + linkType: hard + "tslib@npm:^2.4.0": version: 2.6.3 resolution: "tslib@npm:2.6.3"