diff --git a/.env.example b/.env.example index e56c3da83..f8ab37bcd 100644 --- a/.env.example +++ b/.env.example @@ -12,3 +12,4 @@ PRIVATE_KEY_FILE= PUBLIC_KEY_FILE= SENTRY_DSN= MCO_LOG_LEVEL= +MCO_LOGGING_GROUPS= diff --git a/.vscode/launch.json b/.vscode/launch.json index e7f2ca974..660e4a961 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,25 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "name": "Typescript", + "type": "node", + "runtimeExecutable": "pnpx", + "runtimeVersion": "23.5.0", + "request": "launch", + "args": [ + "src/server.ts" + ], + "runtimeArgs": [ + "tsx", + "--import", + "./instrument.mjs", + "--openssl-legacy-provider" + ], + "cwd": "${workspaceRoot}", + "envFile": "${workspaceFolder}/.env", + "internalConsoleOptions": "openOnSessionStart" + }, { "name": "Stop Prod", "request": "launch", diff --git a/docker-compose.yml b/docker-compose.yml index 5f0894731..7e07b582d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,16 @@ name: rustyserver services: + haproxy: + build: + context: services/haproxy + ports: + - "80:80" + - "443:443" + volumes: + - ./services/haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg + - ./services/haproxy/ssl:/usr/local/etc/haproxy/ssl + extra_hosts: + - "host.docker.internal:host-gateway" nginx: build: context: services/sslProxy diff --git a/instrument.mjs b/instrument.mjs index 5b86a5ca1..e13fa43ff 100644 --- a/instrument.mjs +++ b/instrument.mjs @@ -2,16 +2,14 @@ import * as Sentry from '@sentry/node'; import { nodeProfilingIntegration } from '@sentry/profiling-node'; Sentry.init({ - dsn: process.env['SENTRY_DSN'], - integrations: [ - // Add our Profiling integration - nodeProfilingIntegration(), - ], - - // We recommend adjusting this value in production, or using tracesSampler - // for finer control - tracesSampleRate: 1.0, - profilesSampleRate: 1.0, // Profiling sample rate is relative to tracesSampleRate + dsn: process.env['SENTRY_DSN'], + integrations: [ + // Add our Profiling integration + nodeProfilingIntegration(), + ], + + // We recommend adjusting this value in production, or using tracesSampler + // for finer control + tracesSampleRate: 1.0, + profilesSampleRate: 1.0, // Profiling sample rate is relative to tracesSampleRate }); - - diff --git a/libs/@rustymotors/binary/src/index.ts b/libs/@rustymotors/binary/src/index.ts index 6bd67cd7c..489a2e9ce 100644 --- a/libs/@rustymotors/binary/src/index.ts +++ b/libs/@rustymotors/binary/src/index.ts @@ -1,5 +1,14 @@ -export * from "./lib/Bytable"; -export * from "./lib/BytableContainer"; -export * from "./lib/BytableDword"; -export * from "./lib/BytableHeader"; -export * from "./lib/BytableMessage"; +export * from "./lib/Bytable.js"; +export * from "./lib/BytableContainer.js"; +export * from "./lib/BytableDword.js"; +export * from "./lib/BytableServerHeader.js"; +export * from "./lib/BytableHeader.js"; +export * from "./lib/BytableServerMessage.js"; +export * from "./lib/BytableMessage.js"; +export * from "./lib/BytableData.js"; +export * from "./lib/BytableByte.js"; +export * from "./lib/BytableWord.js"; +export {deserialize, serialize, createEmptyField, serializeSize } from "./lib/Serializer.js"; +export * from "./lib/types.js"; +export * from "./lib/BinaryMember.js"; +export { GameMessage } from "./lib/GameMessage.js"; diff --git a/libs/@rustymotors/binary/src/lib/BinaryMember.ts b/libs/@rustymotors/binary/src/lib/BinaryMember.ts new file mode 100644 index 000000000..03777f4e7 --- /dev/null +++ b/libs/@rustymotors/binary/src/lib/BinaryMember.ts @@ -0,0 +1,350 @@ +import { SerializableInterface } from "rusty-motors-shared-packets"; + +export const BINARY_ALIGNMENT = 4; + +/** + * Converts a 16-bit number from host byte order to network byte order. + * + * @param {number} n - The 16-bit number to convert. + * @returns {number} The converted 16-bit number in network byte order. + */ +export function htons(n: number): number { + return ((n & 0xff) << 8) | ((n >> 8) & 0xff); +} +/** + * Converts a 32-bit number from host byte order to network byte order. + * + * @param {number} n - The 32-bit number to be converted. + * @returns {number} - The converted 32-bit number in network byte order. + */ +export function htonl(n: number): number { + return ((n & 0xff) << 24) | ((n & 0xff00) << 8) | ((n & 0xff0000) >> 8) | ((n >> 24) & 0xff); +} +/** + * Converts a 16-bit number from network byte order to host byte order. + * + * @param {number} n - The 16-bit number in network byte order. + * @returns {number} - The 16-bit number in host byte order. + */ +export function ntohs(n: number): number { + return htons(n); +} +/** + * Converts a network byte order integer to host byte order. + * + * @param {number} n - The number in network byte order. + * @returns {number} - The number in host byte order. + */ +export function ntohl(n: number): number { + return htonl(n); +} + +/** + * Aligns a given number to the specified alignment. + * + * @param {number} n - The number to be aligned. + * @param {number} alignment - The alignment boundary. + * @returns {number} - The aligned number. + */ +export function align(n: number, alignment: number): number { + return (n + alignment - 1) & ~(alignment - 1); +} +/** + * Adds padding to a buffer to align its length to the specified alignment. + * + * @param {Uint8Array} buffer - The buffer to which padding will be added. + * @param {number} alignment - The alignment boundary to which the buffer length should be aligned. + * @returns {Uint8Array} A new buffer with the original buffer's content and the added padding. + */ +export function addAlignementPadding(buffer: Uint8Array, alignment: number): Uint8Array { + const padding = new Uint8Array(align(buffer.length, alignment) - buffer.length); + return new Uint8Array([...buffer, ...padding]); +} +/** + * Verifies that the length of the buffer is aligned to the specified alignment. + * + * @param {Uint8Array} buffer - The buffer to verify + * @param {number} alignment - The alignment value to check against. + * @throws {Error} If the buffer length is not aligned to the specified alignment. + */ +export function verifyAlignment(buffer: Uint8Array, alignment: number) { + if (buffer.length % alignment !== 0) { + throw new Error(`Buffer size is not aligned to ${alignment}, got ${buffer.length}`); + } +} + + +export class BinaryMember implements SerializableInterface { + protected value: Uint8Array; + protected maxSize: number; + protected shouldPad: boolean; + + constructor(size = 0, shouldPad = true) { + this.value = new Uint8Array(size); + this.maxSize = size; + this.shouldPad = shouldPad; + } + set(v: Uint8Array) { + if (v.length > this.maxSize) { + throw new Error(`Value exceeds maximum size of ${this.maxSize}`); + } + if (this.shouldPad) { + this.value = addAlignementPadding(v, BINARY_ALIGNMENT); + } + this.value = v; + } + get() { + return this.value; + } + size() { + return this.value.length; + } + + toString() { + return this.value.toString(); + } + + serialize(): Buffer { + return Buffer.from(this.get()); + } + + deserialize(data: Buffer) { + this.set(new Uint8Array(data)); + } + + getByteSize(): number { + return this.size(); + } + + toHexString(): string { + return this.get().reduce((acc, v) => acc + v.toString(16).padStart(2, "0"), ""); + } +} + +export class Uint8_t extends BinaryMember { + constructor(shouldPad = true) { + super(1, shouldPad); + } +} + +export class Uint16_t extends BinaryMember { + constructor() { + super(2); + } + + getShort(endian: "LE" | "BE" = "LE"): number { + if (endian === "BE") { + return this.getBE(); + } + return this.getLE(); + } + + /** + * Converts the first two bytes of the `value` array to a 16-bit little-endian integer. + * + * @returns {number} The 16-bit little-endian integer representation of the first two bytes. + */ + getLE(): number { + const byte0 = this.value[0] || 0; + const byte1 = this.value[1] || 0 << 8; + return byte0 | byte1; + } + + /** + * Converts the first two bytes of the `value` array to a 16-bit big-endian integer. + * + * @returns {number} The 16-bit big-endian integer representation of the first two bytes. + */ + getBE(): number { + const byte0 = this.value[0] || 0 << 8; + const byte1 = this.value[1] || 0; + return byte0 | byte1; + } + + setShort(value: number, endian: "LE" | "BE" = "LE") { + if (value < 0 || value > 0xffff) { + throw new Error(`Value ${value} is not a 16-bit integer`); + } + + if (endian === "BE") { + this.setBE(value); + } else { + this.setLE(value); + } + } + + /** + * Sets the value of the binary member to a 16-bit little-endian integer. + * + * @param {number} value - The 16-bit little-endian integer value to set. + */ + setLE(value: number) { + this.value = new Uint8Array([ + value & 0xff, + (value >> 8) & 0xff, + ]); + } + + /** + * Sets the value of the binary member to a 16-bit big-endian integer. + * + * @param {number} value - The 16-bit big-endian integer value to set. + */ + setBE(value: number) { + this.value = new Uint8Array([ + (value >> 8) & 0xff, + value & 0xff, + ]); + } +} + +export class Uint32_t extends BinaryMember { + constructor() { + super(4); + } + + getInt(endian: "LE" | "BE" = "LE"): number { + if (endian === "BE") { + return this.getBE(); + } + return this.getLE(); + } + + /** + * Converts the first four bytes of the `value` array to a 32-bit little-endian integer. + * + * @returns {number} The 32-bit little-endian integer representation of the first four bytes. + */ + getLE(): number { + const byte0 = this.value[0] || 0; + const byte1 = (this.value[1] || 0) << 8; + const byte2 = (this.value[2] || 0) << 16; + const byte3 = (this.value[3] || 0) << 24; + return byte0 | byte1 | byte2 | byte3; + } + + /** + * Converts the first four bytes of the `value` array to a 32-bit big-endian integer. + * + * @returns {number} The 32-bit big-endian integer representation of the first four bytes. + */ + getBE(): number { + const byte0 = (this.value[0] || 0) << 24; + const byte1 = (this.value[1] || 0) << 16; + const byte2 = (this.value[2] || 0) << 8; + const byte3 = this.value[3] || 0; + return byte0 | byte1 | byte2 | byte3; + } + + setInt(value: number, endian: "LE" | "BE" = "LE") { + if (value < 0 || value > 0xffffffff) { + throw new Error(`Value ${value} is not a 32-bit integer`); + } + + if (endian === "BE") { + this.setBE(value); + } else { + this.setLE(value); + } + } + + /** + * Sets the value of the binary member to a 32-bit little-endian integer. + * + * @param {number} value - The 32-bit little-endian integer value to set. + */ + setLE(value: number) { + this.value = new Uint8Array([ + value & 0xff, + (value >> 8) & 0xff, + (value >> 16) & 0xff, + (value >> 24) & 0xff, + ]); + } + + /** + * Sets the value of the binary member to a 32-bit big-endian integer. + * + * @param {number} value - The 32-bit big-endian integer value to set. + */ + setBE(value: number) { + this.value = new Uint8Array([ + (value >> 24) & 0xff, + (value >> 16) & 0xff, + (value >> 8) & 0xff, + value & 0xff, + ]); + } +} + +export class Uint8_tArray extends BinaryMember { + constructor(size: number) { + super(size); + } +} + +/** + * A class representing a string of characters. + * The string is stored as a sequence of characters followed by a null terminator. + * It is prefixed with a 32-bit integer representing the length of the string. + * The length includes the null terminator. + * The prefix is in network byte order. + */ +export class CString extends BinaryMember { + constructor(size: number) { + super(size); + } + + + override set(v: Uint8Array) { + if (v.length > this.maxSize + 4) { + throw new Error(`CString exceeds maximum size of ${this.maxSize + 4}, got ${v.length}`); + } + + if (v.length <= 4) { + throw new Error("CString must be at least 5 bytes long"); + } + + const length = new Uint32_t(); + length.set(v.slice(0, 4)); + const stringLength = length.getInt("BE"); + if (stringLength + 4 > v.length) { + throw new Error(`CString length is ${stringLength} but only ${v.length - 4} bytes are available`); + } + + this.value = v.slice(4, stringLength + 4); + } + /** + * Returns the string as a sequence of characters followed by a null terminator. + * The string is prefixed with a 32-bit integer representing the length of the string. + * The prefix is in network byte order. + * @returns {Uint8Array} The string as a sequence of characters followed by a null terminator. + */ + override get(): Uint8Array { + const length = new Uint32_t(); + length.set(new Uint8Array([this.value.length])); + return new Uint8Array([...length.get(), ...this.value]); + } + + + /** + * Calculates the size of the binary member. + * + * @returns {number} The size of the binary member, which is the length of the value plus 4. + */ + override size(): number { + return this.value.length + 4; + } + + /** + * Returns the string as a sequence of characters without the null terminator. + * @returns {string} The string as a sequence of characters. + */ + override toString(): string { + return this.value.toString(); + } + +} + + + diff --git a/libs/@rustymotors/binary/src/lib/Bytable.ts b/libs/@rustymotors/binary/src/lib/Bytable.ts index 2373ac027..34193e07c 100644 --- a/libs/@rustymotors/binary/src/lib/Bytable.ts +++ b/libs/@rustymotors/binary/src/lib/Bytable.ts @@ -1,43 +1,30 @@ -import { get } from "http"; -import { BytableBase } from "./BytableBase"; -import { BytableObject } from "./types"; -import { getServerLogger } from "rusty-motors-shared"; +import { BytableBase } from "./BytableBase.js"; +import { BytableObject } from "./types.js"; export class Bytable extends BytableBase implements BytableObject { protected name_: string = ""; protected value_: string | number | Buffer = ""; - static fromBuffer(buffer: Buffer, offset: number) { - const bytable = new this(); - if (buffer.length === 4 && offset === 4) { - // Some messages only consist of a id and a length - getServerLogger().warn(`Buffer length is 4, skipping deserialization`); - return bytable; - } - - if (!buffer || offset < 0 || offset >= buffer.length) { - getServerLogger().error(`Cannot deserialize buffer with invalid offset: ${offset}`); - return bytable; - } - bytable.deserialize(buffer.subarray(offset)); - return bytable; + protected deserializeFields(buffer: Buffer) { + this.buffer = new DataView(Uint8Array.from(buffer).buffer); + } + + override deserialize(buffer: Buffer) { + validateBuffer(buffer, 'deserialize'); + return this.deserializeFields(buffer); } - override serialize() { - if (!this.buffer || this.buffer.byteLength === 0) { - throw new Error('Cannot serialize empty buffer'); - } + protected serializeFields(): Buffer { return Buffer.from(this.buffer.buffer); } - - override deserialize(buffer: Buffer) { - if (!buffer || buffer.length === 0) { - throw new Error('Cannot deserialize empty buffer'); - } - this.buffer = new DataView(Uint8Array.from(buffer).buffer); + + override serialize(): Buffer { + validateBuffer(this.buffer, 'serialize'); + return this.serializeFields(); } - + + get json() { return { name: this.name_, @@ -45,7 +32,7 @@ export class Bytable extends BytableBase implements BytableObject { }; } - get serializeSize() { + override get serializeSize(): number { return this.buffer.byteLength; } @@ -67,5 +54,18 @@ export class Bytable extends BytableBase implements BytableObject { this.validateValue(value); this.value_ = value; } + + override toString() { + return `BytableBase { name: ${this.name_}, value: ${this.value_} }`; + } } +export function validateBuffer(buf: DataView | ArrayBufferLike, direction: string) { + if (typeof buf === 'undefined') { + throw new Error(`Cannot ${direction} undefined buffer`); + } + + if (buf.byteLength === 0) { + throw new Error(`Cannot ${direction} empty buffer`); + } +} diff --git a/libs/@rustymotors/binary/src/lib/BytableBase.ts b/libs/@rustymotors/binary/src/lib/BytableBase.ts index d63223212..15a50789d 100644 --- a/libs/@rustymotors/binary/src/lib/BytableBase.ts +++ b/libs/@rustymotors/binary/src/lib/BytableBase.ts @@ -1,71 +1,106 @@ export class BytableBase { - protected buffer: DataView = new DataView(new ArrayBuffer(0)); + protected buffer: DataView = new DataView(new ArrayBuffer(1)); - getUint16( - this: BytableBase, - offset: number, - littleEndian: boolean = false, - ) { - return this.buffer.getUint16(offset, littleEndian); - } + protected getUint16( + this: BytableBase, + offset: number, + littleEndian: boolean = false, + ) { + return this.buffer.getUint16(offset, littleEndian); + } - getUint32(this: BytableBase, offset: number, littleEndian: boolean = false) { - return this.buffer.getUint32(offset, littleEndian); - } + protected getUint32( + this: BytableBase, + offset: number, + littleEndian: boolean = false, + ) { + return this.buffer.getUint32(offset, littleEndian); + } - toString(this: BytableBase) { - return this.buffer.toString(); - } + toString(this: BytableBase) { + throw new Error("Method should be implemented by subclass"); + } - deserialize(this: BytableBase, buffer: Buffer) { - if (buffer.length === 0) { - throw new Error("Cannot deserialize empty buffer"); - } - this.buffer = new DataView(Uint8Array.from(buffer).buffer); - } + deserialize(this: BytableBase, _buffer: Buffer) { + throw new Error("Method should be implemented by subclass"); + } + + serialize(this: BytableBase) { + throw new Error("Method should be implemented by subclass"); + } - serialize(this: BytableBase) { - return Buffer.from(this.buffer.buffer); + /** + * Validate the value of the container. + * @param value - The value to validate. + * @returns void + * @throws Error if the value is NaN or an empty buffer + */ + protected validateValue(value: string | number | Buffer) { + if (typeof value === "number" && Number.isNaN(value)) { + throw new Error("Cannot set NaN value"); } + if (value instanceof Buffer && value.length === 0) { + throw new Error("Cannot set empty buffer"); + } + } - /** - * Validate the value of the container. - * @param value - The value to validate. - * @returns void - * @throws Error if the value is NaN or an empty buffer - */ - protected validateValue(value: string | number | Buffer) { - if (typeof value === "number" && Number.isNaN(value)) { - throw new Error("Cannot set NaN value"); - } - if (value instanceof Buffer && value.length === 0) { - throw new Error("Cannot set empty buffer"); - } + /** + * Get the byte length of the value. + * @param value - The value to get the byte length of. + * @returns The byte length of the value. + */ + protected getByteLength(value: string | number | Buffer) { + if (value instanceof Buffer) { + return value.byteLength; } + // Convert strings and numbers to Buffer to get correct byte length + return Buffer.from(String(value)).byteLength; + } - /** - * Get the byte length of the value. - * @param value - The value to get the byte length of. - * @returns The byte length of the value. - */ - protected getByteLength(value: string | number | Buffer) { - if (value instanceof Buffer) { - return value.byteLength; - } - // Convert strings and numbers to Buffer to get correct byte length - return Buffer.from(String(value)).byteLength; + protected validateString(value: string) { + if (value.length === 0) { + throw new Error("Cannot set empty string"); } + } - protected validateString(value: string) { - if (value.length === 0) { - throw new Error("Cannot set empty string"); - } + protected toBuffer(value: string | number | Buffer) { + if (value instanceof Buffer) { + return value; } + return Buffer.from(String(value)); + } + + protected align8(value: number) { + return value + (8 - (value % 8)); + } + + get serializeSize(): number { + throw new Error("Method should be implemented by subclass"); + } +} - protected toBuffer(value: string | number | Buffer) { - if (value instanceof Buffer) { - return value; - } - return Buffer.from(String(value)); - } +/** + * Coerces a given value to a Buffer. + * + * This function takes a value of type string, number, or Buffer and converts it to a Buffer. + * - If the value is a string, it creates a Buffer from the string. + * - If the value is a number, it creates a 4-byte Buffer and writes the number as a 32-bit unsigned integer in little-endian format. + * - If the value is already a Buffer, it returns the value as is. + * + * @param value - The value to be coerced to a Buffer. It can be a string, number, or Buffer. + * @returns The coerced Buffer. + */ +export function coerceValueToBuffer( + value: string | number | Buffer, +) { + let coercedValue: Buffer; + if (typeof value === "string") { + coercedValue = Buffer.from(value); + } else if (typeof value === "number") { + coercedValue = Buffer.alloc(4); + coercedValue.writeUInt32LE(value, 0); + } else { + coercedValue = value; } + return coercedValue; +} diff --git a/libs/@rustymotors/binary/src/lib/BytableBuffer.ts b/libs/@rustymotors/binary/src/lib/BytableBuffer.ts new file mode 100644 index 000000000..866c253ca --- /dev/null +++ b/libs/@rustymotors/binary/src/lib/BytableBuffer.ts @@ -0,0 +1,50 @@ +import { BytableObject } from "./types.js"; + +export class BytableBuffer implements BytableObject { + protected name_: string = ""; + protected value_: Buffer = Buffer.alloc(0); + + deserialize(buffer: Buffer) { + this.value_ = buffer; + } + + get serializeSize() { + return this.value_.length; + } + + serialize() { + return this.value_; + } + + get json() { + return { + name: this.name_, + serializeSize: this.serializeSize, + value: this.value_.toString("hex"), + }; + } + + setName(name: string) { + this.name_ = name; + } + + get name() { + return this.name_; + } + + get value() { + return this.value_; + } + + setValue(value: Buffer) { + this.value_ = value; + } + + getUint16(offset: number, _littleEndian: boolean): number { + return this.value_.readUInt16BE(offset); + } + + getUint32(offset: number, _littleEndian: boolean): number { + return this.value_.readUInt32BE(offset); + } +} diff --git a/libs/@rustymotors/binary/src/lib/BytableByte.ts b/libs/@rustymotors/binary/src/lib/BytableByte.ts new file mode 100644 index 000000000..fa9793192 --- /dev/null +++ b/libs/@rustymotors/binary/src/lib/BytableByte.ts @@ -0,0 +1,53 @@ +import { BytableBase } from "./BytableBase.js"; +import { BytableObject } from "./types.js"; + +export class BytableByte extends BytableBase implements BytableObject { + protected value_: number = 0; + protected name_: string = ""; + + override deserialize(buffer: Buffer) { + this.value_ = buffer.readUInt8(0); + } + + override get serializeSize() { + return 1; + } + + override serialize() { + const buffer = Buffer.alloc(1); + buffer.writeUInt8(this.value_, 0); + return buffer; + } + + get json() { + return { + name: this.name_, + serializeSize: this.serializeSize, + value: this.value_, + }; + } + + setName(name: string) { + this.name_ = name; + } + + override toString() { + return JSON.stringify(this.json); + } + + get name() { + return this.name_; + } + + get value() { + return this.value_; + } + + setValue(value: string | number | Buffer) { + if (typeof value === "number") { + this.value_ = value; + } else { + throw new Error("Invalid value type"); + } + } +} diff --git a/libs/@rustymotors/binary/src/lib/BytableCString.ts b/libs/@rustymotors/binary/src/lib/BytableCString.ts new file mode 100644 index 000000000..8f2315822 --- /dev/null +++ b/libs/@rustymotors/binary/src/lib/BytableCString.ts @@ -0,0 +1,121 @@ +import { BytableBase } from "./BytableBase.js"; +import { BytableObject } from "./types.js"; + + +export class BytableCString extends BytableBase implements BytableObject { + private value_: string | number | Buffer = ""; + private nullTerminated: boolean = true; + private length: number = 0; + private name_: string = ""; + + /** + * Set the value of the container. + * @param value - The value to set. + * @returns void + */ + setValue(value: string | number | Buffer) { + this.value_ = value; + this.length = this.getByteLength(value); + } + + getValue() { + return this.value_; + } + + setNullTerminated(_nullTerminated: boolean) { + throw new Error("Cannot set null termination for CString"); + } + + getNullTerminated() { + return this.nullTerminated; + } + + /** + * Set the length of the container. + * @param length - The length of the container. + * @returns void + * @throws Error if the container is set to null terminated + */ + setLength(length: number) { + if (this.nullTerminated) { + throw new Error("Cannot set length for null terminated container"); + } else { + this.length = length; + } + } + + getLength() { + return this.length; + } + + override get serializeSize() { + if (this.nullTerminated) { + return this.length + 1; + } else { + throw new Error("Cannot get serialize size for CString"); + } + } + + + + /** + * Serialize the container. + * @returns Buffer - The serialized container. + */ + override serialize() { + const value = this.toBuffer(this.value_); + if (this.nullTerminated) { + return value.length === 0 ? Buffer.from("\0") : Buffer.concat([value, Buffer.from("\0")]); + } else { + throw new Error("Cannot serialize CString"); + } + } + + /** + * Deserialize the container. + * @param buffer - The buffer to deserialize. + * @returns void + */ + override deserialize(buffer: Buffer) { + const offset = 0; + if (this.nullTerminated) { + let length = 0; + let cursor = 0; + do { + this.setValue( + buffer.subarray(offset, offset + length).toString("utf-8") + ); + cursor++; + } while (buffer[offset + cursor] !== 0); + this.setValue(buffer.subarray(offset, offset + length).toString("utf-8")); + this.length = length + 1; + } else { + throw new Error("Cannot deserialize CString"); + } + } + + get json() { + return { + value: this.value_, + length: this.length, + nullTerminated: this.nullTerminated, + serializeSize: this.serializeSize, + }; + } + + setName(name: string) { + this.name_ = name; + } + + get name() { + return this.name_; + } + + get value() { + return this.value_; + } + + override toString(): string { + return this.value_.toString(); + } +} diff --git a/libs/@rustymotors/binary/src/lib/BytableContainer.ts b/libs/@rustymotors/binary/src/lib/BytableContainer.ts index 1e0484882..55c8c534e 100644 --- a/libs/@rustymotors/binary/src/lib/BytableContainer.ts +++ b/libs/@rustymotors/binary/src/lib/BytableContainer.ts @@ -1,23 +1,11 @@ -import { BytableBase } from "./BytableBase"; -import { BytableObject } from "./types"; +import { BytableBase } from "./BytableBase.js"; +import { BytableObject } from "./types.js"; -export class BytableContainer extends BytableBase implements BytableObject { +export class BytableShortContainer extends BytableBase implements BytableObject { private value_: string | number | Buffer = ""; private nullTerminated: boolean = false; private length: number = 0; private name_: string = ""; - static fromBuffer( - buffer: Buffer, - offset: number, - nullTerminated: boolean = false, - length: number = 0, - ) { - const container = new this(); - container.setNullTerminated(nullTerminated); - container.setLength(length); - container.deserialize(buffer.subarray(offset)); - return container; - } /** * Set the value of the container. @@ -38,8 +26,8 @@ export class BytableContainer extends BytableBase implements BytableObject { return this.value_; } - setNullTerminated(nullTerminated: boolean) { - this.nullTerminated = nullTerminated; + setNullTerminated(_nullTerminated: boolean) { + throw new Error("Method not implemented."); } getNullTerminated() { @@ -64,7 +52,7 @@ export class BytableContainer extends BytableBase implements BytableObject { return this.length; } - get serializeSize() { + override get serializeSize() { if (this.nullTerminated) { return this.length + 1; } else { @@ -116,6 +104,137 @@ export class BytableContainer extends BytableBase implements BytableObject { } } + get json() { + return { + name: this.name_, + value: this.value_, + length: this.length, + nullTerminated: this.nullTerminated, + serializeSize: this.serializeSize, + }; + } + + setName(name: string) { + this.name_ = name; + } + + get name() { + return this.name_; + } + + get value() { + return this.value_; + } + + override toString(): string { + throw new Error("Method not implemented."); + } +} + + +export class BytableContainer extends BytableBase implements BytableObject { + private value_: string | number | Buffer = ""; + private nullTerminated: boolean = false; + private length: number = 0; + private name_: string = ""; + + /** + * Set the value of the container. + * @param value - The value to set. + * @returns void + * @throws Error if the container is null terminated and the value is an empty string + */ + setValue(value: string | number | Buffer) { + this.validateValue(value); + if (this.nullTerminated && typeof value === "string") { + this.validateString(value); + } + this.value_ = value; + this.length = this.getByteLength(value); + } + + getValue() { + return this.value_; + } + + setNullTerminated(nullTerminated: boolean) { + this.nullTerminated = nullTerminated; + } + + getNullTerminated() { + return this.nullTerminated; + } + + /** + * Set the length of the container. + * @param length - The length of the container. + * @returns void + * @throws Error if the container is set to null terminated + */ + setLength(length: number) { + if (this.nullTerminated) { + throw new Error("Cannot set length for null terminated container"); + } else { + this.length = length; + } + } + + getLength() { + return this.length; + } + + override get serializeSize() { + if (this.nullTerminated) { + return this.length + 1; + } else { + return this.length + 4; + } + } + + + + /** + * Serialize the container. + * @returns Buffer - The serialized container. + */ + override serialize() { + const value = this.toBuffer(this.value_); + if (this.nullTerminated) { + return value.length === 0 ? Buffer.from("\0") : Buffer.concat([value, Buffer.from("\0")]); + } else { + const lengthPrefix = Buffer.alloc(4); + lengthPrefix.writeUInt32BE(this.length, 0); + return Buffer.concat([lengthPrefix, value]); + } + } + + /** + * Deserialize the container. + * @param buffer - The buffer to deserialize. + * @returns void + */ + override deserialize(buffer: Buffer) { + const offset = 0; + if (this.nullTerminated) { + let length = 0; + let cursor = 0; + do { + this.setValue( + buffer.subarray(offset, offset + length).toString("utf-8"), + ); + cursor++; + } while (buffer[offset + cursor] !== 0); + this.setValue(buffer.subarray(offset, offset + length).toString("utf-8")); + this.length = length + 1; + } else { + const length = buffer.readUInt32BE(offset); + this.setValue( + buffer.subarray(offset + 4, offset + length + 4).toString("utf-8"), + ); + this.length = length; + } + } + get json() { return { value: this.value_, @@ -136,4 +255,8 @@ export class BytableContainer extends BytableBase implements BytableObject { get value() { return this.value_; } + + override toString(): string { + throw new Error("Method not implemented."); + } } diff --git a/libs/@rustymotors/binary/src/lib/BytableData.ts b/libs/@rustymotors/binary/src/lib/BytableData.ts new file mode 100644 index 000000000..bacb554c8 --- /dev/null +++ b/libs/@rustymotors/binary/src/lib/BytableData.ts @@ -0,0 +1,119 @@ +import { BytableBase } from "./BytableBase.js"; +import { BytableFieldTypes } from "./BytableMessage.js"; +import { BytableObject } from "./types.js"; + +export class BytableData extends BytableBase implements BytableObject { + protected name_ = "BytableData"; + protected fields_: Array = []; + protected serializeOrder_: Array<{ + name: string; + field: keyof typeof BytableFieldTypes; + }> = []; + + constructor() { + super(); + } + + + + override deserialize(buffer: Buffer) { + try { + let offset = 0; + for (const field of this.serializeOrder_) { + if (!(field.field in BytableFieldTypes)) { + throw new Error(`Unknown field type: ${field.field}`); + } + + const fieldType = BytableFieldTypes[field.field]; + const fieldInstance = new fieldType(); + fieldInstance.setName(field.name); + fieldInstance.deserialize(buffer.subarray(offset)); + this.fields_.push(fieldInstance); + offset += fieldInstance.serializeSize; + } + } catch (error) { + const err = new Error( + `Error deserializing message: ${(error as Error).message}`, + { + cause: error, + }, + ); + throw err; + } + } + + override get serializeSize() { + const fieldSizes = this.fields_.map((field) => field.serializeSize); + return fieldSizes.reduce((a, b) => a + b, 0); + } + + override serialize() { + const buffer = Buffer.alloc(this.serializeSize); + let offset = 0; + + for (const field of this.fields_) { + buffer.set(field.serialize(), offset); + offset += field.serializeSize; + } + return buffer; + } + + get json() { + return { + name: this.name, + serializeSize: this.serializeSize, + fields: this.fields_.map((field) => field.json), + }; + } + + setName(name: string) { + this.name_ = name; + } + + override toString() { + return JSON.stringify(this.json); + } + + setSerializeOrder( + serializeOrder: Array<{ + name: string; + field: keyof typeof BytableFieldTypes; + }>, + ) { + this.serializeOrder_ = serializeOrder; + } + + getFieldValueByName(name: string) { + if (name === "") { + return undefined; + } + const field = this.fields_.find((field) => field.name === name); + if (!field) { + throw new Error(`Field ${name} not found`); + } + return field.value; + } + + setFieldValueByName(name: string, value: string | number | Buffer) { + if (name === "") { + return; + } + const field = this.fields_.find((field) => field.name === name); + if (!field) { + throw new Error(`Field ${name} not found`); + } + field.setValue(value); + } + + get name() { + return this.name_; + } + + get value(): string | number { + throw new Error("Not implemented"); + } + + setValue(_value: string | number | Buffer) { + throw new Error("Not implemented"); + } +} diff --git a/libs/@rustymotors/binary/src/lib/BytableDword.ts b/libs/@rustymotors/binary/src/lib/BytableDword.ts index e3989ee2c..b044eb752 100644 --- a/libs/@rustymotors/binary/src/lib/BytableDword.ts +++ b/libs/@rustymotors/binary/src/lib/BytableDword.ts @@ -1,4 +1,4 @@ -import { Bytable } from "./Bytable"; +import { Bytable } from "./Bytable.js"; export class BytableDword extends Bytable { private static validateBufferLength( @@ -11,29 +11,28 @@ export class BytableDword extends Bytable { } } - static override fromBuffer(buffer: Buffer, offset: number) { - BytableDword.validateBufferLength(buffer, 4, offset); - const dword = new BytableDword(); - dword.deserialize(buffer.subarray(offset, offset + 4)); - - return dword; + override get serializeSize(): number { + return 4; } override deserialize(buffer: Buffer) { BytableDword.validateBufferLength(buffer, 4); - super.deserialize(buffer.subarray(0, 4)); + this.setValue(buffer.readUInt32BE(0)); + } + + override serialize() { + return this.value as Buffer; } override get json() { return { - name: this.name, - value: this.buffer.getUint32(0, true), - valueString: Buffer.from(this.buffer.buffer).toString("utf-8"), + name: this.name_, + value: this.value, serializeSize: this.serializeSize, }; } override toString() { - return this.buffer.toString(); + return this.value.toString(); } } diff --git a/libs/@rustymotors/binary/src/lib/BytableHeader.ts b/libs/@rustymotors/binary/src/lib/BytableHeader.ts index e6d9a9553..d844113f6 100644 --- a/libs/@rustymotors/binary/src/lib/BytableHeader.ts +++ b/libs/@rustymotors/binary/src/lib/BytableHeader.ts @@ -1,4 +1,4 @@ -import { Bytable } from "./Bytable"; +import { Bytable } from "./Bytable.js"; export class BytableHeader extends Bytable { protected messageId_: number = 0; @@ -8,13 +8,6 @@ export class BytableHeader extends Bytable { protected checksum_: number = 0; protected data_: Buffer = Buffer.alloc(0); - static override fromBuffer(buffer: Buffer, offset: number) { - const header = new this(); - header.deserialize(buffer.subarray(offset)); - - return header; - } - override get json() { return { name: this.name, @@ -86,20 +79,19 @@ export class BytableHeader extends Bytable { } override deserialize(buffer: Buffer) { - super.deserialize(buffer); - this.setMessageId(this.getUint16(0)); - this.setMessageLength(this.getUint16(2)); + this.setMessageId(buffer.readUInt16BE(0)); + this.setMessageLength(buffer.readUInt16BE(2)); // If the length is less than 12, there is no room for the message, so we assume version 0 - if (buffer.byteLength >= 12 && this.getUint16(4) === 257) { + if (buffer.byteLength >= 12 && buffer.readUInt16BE(4) === 257) { this.setMessageVersion(1); } else { this.setMessageVersion(0); } if (this.messageVersion === 1) { - this.setReserved(this.getUint16(6)); - this.setChecksum(this.getUint32(8)); + this.setReserved(buffer.readUInt16BE(6)); + this.setChecksum(buffer.readUInt32BE(8)); } } } diff --git a/libs/@rustymotors/binary/src/lib/BytableMessage.ts b/libs/@rustymotors/binary/src/lib/BytableMessage.ts index a10549c89..711cc147f 100644 --- a/libs/@rustymotors/binary/src/lib/BytableMessage.ts +++ b/libs/@rustymotors/binary/src/lib/BytableMessage.ts @@ -1,40 +1,185 @@ -import { Bytable } from "./Bytable"; -import { BytableContainer } from "./BytableContainer"; -import { BytableDword } from "./BytableDword"; -import { BytableHeader } from "./BytableHeader"; -import { BytableObject } from "./types"; +import { Bytable } from "./Bytable.js"; +import { BytableBase } from "./BytableBase.js"; +import { BytableBuffer } from "./BytableBuffer.js"; +import { BytableByte } from "./BytableByte.js"; +import { BytableContainer, BytableShortContainer } from "./BytableContainer.js"; +import { BytableCString } from "./BytableCString.js"; +import { BytableData } from "./BytableData.js"; +import { BytableDword } from "./BytableDword.js"; +import { BytableHeader } from "./BytableHeader.js"; +import { BytableWord } from "./BytableWord.js"; +import { BytableObject } from "./types.js"; import { getServerLogger } from "rusty-motors-shared"; +export class BytableStructure extends BytableBase implements BytableObject { + protected fields_: Array = []; + protected serializeOrder_: Array<{ + name: string; + field: keyof typeof BytableFieldTypes; + }> = []; + protected name_: string = ""; + + override deserialize(buffer: Buffer) { + let offset = 0; + for (const field of this.serializeOrder_) { + if (!(field.field in BytableFieldTypes)) { + throw new Error(`Unknown field type: ${field.field}`); + } + + const fieldType = BytableFieldTypes[field.field]; + const fieldInstance = new fieldType(); + fieldInstance.setName(field.name); + fieldInstance.deserialize(buffer.subarray(offset)); + this.fields_.push(fieldInstance); + offset += fieldInstance.serializeSize; + } + } + + override get serializeSize() { + const fieldSizes = this.fields_.map((field) => field.serializeSize); + return fieldSizes.reduce((a, b) => a + b, 0); + } + + override serialize() { + const buffer = Buffer.alloc(this.serializeSize); + let offset = 0; + for (const field of this.fields_) { + const fieldBuffer = field.serialize(); + buffer.set(fieldBuffer, offset); + offset += field.serializeSize; + } + return buffer; + } + + get json() { + return { + name: this.name_, + serializeSize: this.serializeSize, + fields: this.fields_.map((field) => field.json), + }; + } + + setName(name: string) { + this.name_ = name; + } + + override toString() { + return JSON.stringify(this.json); + } + + setSerializeOrder( + serializeOrder: Array<{ + name: string; + field: keyof typeof BytableFieldTypes; + }>, + ) { + this.serializeOrder_ = serializeOrder; + } + + getField(name: string) { + return this.fields_.find((field) => field.name === name); + } + + getFieldValueByName(name: string) { + if (name === "") { + return undefined; + } + const field = this.fields_.find((field) => field.name === name); + if (!field) { + throw new Error(`Field ${name} not found`); + } + return field.value; + } + + setFieldValueByName(name: string, value: string | number | Buffer) { + if (name === "") { + return; + } + + let coercedValue; + + if (typeof value === "string") { + coercedValue = Buffer.from(value); + } else { + coercedValue = typeof value === "number" ? Buffer.from([value]) : value; + } + + const serializedFormat = this.serializeOrder_.find( + (field) => field.name === name, + ); + + if (!serializedFormat) { + throw new Error(`Field ${name} not found in serialized format`); + } + + const field = this.fields_.find((field) => field.name === name); + if (!field) { + const field = new BytableFieldTypes[serializedFormat.field](); + field.setName(name); + field.setValue(coercedValue); + + this.fields_.push(field); + return; + } + + field.setValue(value); + } + + get name(): string { + return this.name_; + } + + get value(): string | number | Buffer { + throw new Error("This object is a container"); + } + + setValue() { + throw new Error("This object is a container"); + } + } + export const BytableFieldTypes = { ZeroTerminatedString: BytableContainer, + String: BytableContainer, Dword: BytableDword, Container: BytableContainer, - Raw: Bytable, + PrefixedString2: BytableShortContainer, + Raw: BytableData, + Structure: BytableStructure, + Boolean: BytableByte, + Short: BytableWord, + Buffer: BytableBuffer, + CString: BytableCString, }; export class BytableMessage extends Bytable { - protected header_: BytableHeader = new BytableHeader(); - protected fields_: Array = []; - protected serializeOrder_: Array<{ - name: string; - field: keyof typeof BytableFieldTypes; - }> = []; - - constructor(version: 0 | 1 = 1) { - super(); - this.header_.setMessageVersion(version); - } + protected header_: BytableHeader = new BytableHeader(); + protected fields_: Array = []; + protected serializeOrder_: Array<{ + name: string; + field: keyof typeof BytableFieldTypes; + }> = []; - static override fromBuffer(buffer: Buffer, offset: number) { - const message = new this(1); - message.deserialize(buffer.subarray(offset)); - return message; - } + constructor(version: 0 | 1 = 1) { + super(); + this.header_.setMessageVersion(version); + this.setSerializeOrder([ + { + name: "data", + field: "Buffer", + }, + ]); + } + + protected override deserializeFields(buffer: Buffer) { + let offset = 0; - override deserialize(buffer: Buffer) { - try { - const header = BytableHeader.fromBuffer(buffer, this.header_.messageVersion); - this.header_.deserialize(buffer.subarray(0, header.serializeSize)); - let offset = header.serializeSize; + // It's posible that this message is only a header + if ( + this.header_.messageVersion === 0 && + this.header_.messageLength === 4 + ) { + this.fields_.push(new BytableFieldTypes.Buffer()); + } for (const field of this.serializeOrder_) { if (!(field.field in BytableFieldTypes)) { @@ -42,98 +187,235 @@ export class BytableMessage extends Bytable { } const fieldType = BytableFieldTypes[field.field]; - const fieldInstance = fieldType.fromBuffer(buffer, offset); + const fieldInstance = new fieldType(); fieldInstance.setName(field.name); + + try { + fieldInstance.deserialize(buffer.subarray(offset)); + } catch (error) { + const err = new Error( + `Error deserializing field ${field.name} ${error}`, + { + cause: error, + }, + ); + getServerLogger("BytableMessage/deserializeFields").error( + { field, offset, fieldsSoFar: this.fields_ }, + String(err), + ); + throw err; + } this.fields_.push(fieldInstance); offset += fieldInstance.serializeSize; } - } catch (error) { - const err = new Error( - `Error deserializing message: ${(error as Error).message}`, - { + } + + override deserialize(buffer: Buffer) { + try { + this.header_.deserialize(buffer); + this.deserializeFields(buffer.subarray(this.header.serializeSize)); + } catch (error) { + const err = new Error(`Error deserializing message ${error}`, { cause: error, - }, + }); + throw err; + } + } + + get header() { + return this.header_; + } + + override get serializeSize() { + const fieldSizes = this.fields_.map((field) => field.serializeSize); + return this.header_.serializeSize + fieldSizes.reduce((a, b) => a + b, 0); + // return this.align8(this.header_.serializeSize + fieldSizes.reduce((a, b) => a + b, 0)); + } + + protected override serializeFields() { + const buffer = Buffer.alloc( + this.serializeSize - this.header_.serializeSize, ); - throw err; + let offset = 0; + + // It's posible that this message is only a header + if ( + this.header_.messageVersion === 0 && + this.header_.messageLength === 4 + ) { + this.fields_.push(new BytableFieldTypes.Buffer()); + } + + for (const field of this.fields_) { + try { + buffer.set(field.serialize(), offset); + } catch (error) { + const err = new Error(`Error serializing field ${field.name}`, { + cause: error, + }); + throw err; + } + offset += field.serializeSize; + } + return buffer; } - } - get header() { - return this.header_; - } + override serialize() { + const buffer = Buffer.alloc(this.serializeSize); + this.header_.setMessageLength(this.serializeSize); + buffer.set(this.header_.serialize(), 0); - override get serializeSize() { - const fieldSizes = this.fields_.map((field) => field.serializeSize); - return this.header_.serializeSize + fieldSizes.reduce((a, b) => a + b, 0); - } + buffer.set(this.serializeFields(), this.header_.serializeSize); - override serialize() { - const buffer = Buffer.alloc(this.serializeSize); - buffer.set(this.header_.serialize(), 0); - let offset = this.header_.serializeSize; + return buffer; + } + + override get json() { + return { + name: this.name, + serializeSize: this.serializeSize, + header: this.header_.json, + fields: this.fields_.map((field) => field.json), + }; + } + + override setName(name: string) { + this.header_.setName(name); + } + + override toString() { + return JSON.stringify(this.json); + } + + setSerializeOrder( + serializeOrder: Array<{ + name: string; + field: keyof typeof BytableFieldTypes; + }>, + ) { + this.serializeOrder_ = serializeOrder; + } - // It's posible that this message is only a header - if ( this.header_.messageVersion === 0 && this.header_.messageLength === 4) { - getServerLogger().warn(`Message has no fields, or no body`); + getField(name: string) { + return this.fields_.find((field) => field.name === name); + } + + getFieldValueByName(name: string) { + if (name === "") { + return undefined; + } + const field = this.fields_.find((field) => field.name === name); + if (!field) { + throw new Error(`Field ${name} not found`); + } + return field.value; + } + + htonl(value: number) { + const buffer = Buffer.alloc(4); + buffer.writeUInt32BE(value, 0); return buffer; } - for (const field of this.fields_) { - buffer.set(field.serialize(), offset); - offset += field.serializeSize; + coerceValue(value: string | number | Buffer) { + if (typeof value === "string") { + return Buffer.from(value); + } + if (typeof value === "number") { + return Buffer.from(this.htonl(value)); + } + return value; } - return buffer; - } - override get json() { - return { - name: this.name, - serializeSize: this.serializeSize, - header: this.header_.json, - fields: this.fields_.map((field) => field.json), - }; - } + setFieldValueByName(name: string, value: string | number | Buffer) { + if (name === "") { + return; + } - override setName(name: string) { - this.header_.setName(name); - } + const serializedFormat = this.serializeOrder_.find( + (field) => field.name === name, + ); - override toString() { - return JSON.stringify(this.json); - } + if (!serializedFormat) { + throw new Error(`Field ${name} not found in serialized format`); + } - setSerializeOrder( - serializeOrder: Array<{ - name: string; - field: keyof typeof BytableFieldTypes; - }>, - ) { - this.serializeOrder_ = serializeOrder; - } + const field = this.fields_.find((field) => field.name === name); + if (!field) { + const field = new BytableFieldTypes[serializedFormat.field](); + field.setName(name); + field.setValue(this.coerceValue(value)); + + this.fields_.push(field); + return; + } + + if (!field) { + throw new Error(`Field ${name} not found, and could not be created`); + } - getFieldValueByName(name: string) { - if (name === "") { - return undefined; + field.setValue(value); } - const field = this.fields_.find((field) => field.name === name); - if (!field) { - throw new Error(`Field ${name} not found`); + + setVersion(version: 0 | 1) { + this.header_.setMessageVersion(version); + } + + getBody() { + return this.serializeFields(); } - return field.value; - } - setFieldValueByName(name: string, value: string | number | Buffer) { - if (name === "") { - return; + setBody(buffer: Buffer) { + this.deserializeFields(buffer); } - const field = this.fields_.find((field) => field.name === name); - if (!field) { - throw new Error(`Field ${name} not found`); + + get data() { + return this.getBody(); + } + + set data(buffer: Buffer) { + this.setBody(buffer); + } + + toHexString() { + return this.serialize().toString("hex"); + } + + getByteSize() { + return this.serializeSize; } - field.setValue(value); } - setVersion(version: 0 | 1) { - this.header_.setMessageVersion(version); +export function createRawMessage(buffer?: Buffer) { + const message = new BytableMessage(0); + message.setSerializeOrder([ + { + name: "data", + field: "Buffer", + }, + ]); + + if (buffer) { + message.deserialize(buffer); } + + return message; +} + +export function createGameMessage(buffer?: Buffer) { + const message = new BytableMessage(1); + message.setSerializeOrder([ + { + name: "data", + field: "Buffer", + }, + ]); + + if (buffer) { + message.deserialize(buffer); + } + + return message; } + + diff --git a/libs/@rustymotors/binary/src/lib/BytableServerHeader.ts b/libs/@rustymotors/binary/src/lib/BytableServerHeader.ts new file mode 100644 index 000000000..17c0689bd --- /dev/null +++ b/libs/@rustymotors/binary/src/lib/BytableServerHeader.ts @@ -0,0 +1,86 @@ +import { BytableBase } from "./BytableBase.js"; +import { BytableObject } from "./types.js"; + +export class BytableServerHeader extends BytableBase implements BytableObject { + // All fields are in Little Endian + protected messageLength_: number = 0; // 2 bytes + protected messageSignature_ = "TOMC"; // 4 bytes + protected messageSequence_: number = 0; // 4 bytes + protected messageFlags_: number = 0; // 1 byte bitfield + + get name(): string { + return "ServerHeader"; + } + + setName(_name: string): void { + throw new Error("Method not implemented."); + } + + get value(): string | number | Buffer { + throw new Error("Method not implemented."); + } + + setValue(_value: string | number | Buffer): void { + throw new Error("Method not implemented."); + } + + get json() { + return { + name: this.name, + len: this.messageLength, + signature: this.messageSignature_, + sequence: this.messageSequence_, + flags: this.messageFlags_, + serializeSize: this.serializeSize, + }; + } + + override toString(): string { + return `Message Length: ${this.messageLength_}, Message Signature: ${this.messageSignature_}, Message Sequence: ${this.messageSequence_}, Message Flags: ${this.messageFlags_}`; + } + + setMessageLength(messageLength: number) { + this.messageLength_ = messageLength; + } + + get messageLength() { + return this.messageLength_; + } + + override get serializeSize() { + return 11; + } + + override serialize() { + const buffer = Buffer.alloc(this.serializeSize); + buffer.writeUInt16LE(this.messageLength, 0); + buffer.write(this.messageSignature_, 2); + buffer.writeUInt32LE(this.messageSequence_, 6); + buffer.writeUInt8(this.messageFlags_, 10); + return buffer; + } + + override deserialize(buffer: Buffer) { + super.deserialize(buffer); + this.messageLength_ = buffer.readUInt16LE(0); + this.messageSignature_ = buffer.toString("utf8", 2, 6); + this.messageSequence_ = buffer.readUInt32LE(6); + this.messageFlags_ = buffer.readUInt8(10); + } + + get sequence() { + return this.messageSequence_; + } + + get flags() { + return this.messageFlags_; + } + + set sequence(sequence: number) { + this.messageSequence_ = sequence; + } + + set flags(flags: number) { + this.messageFlags_ = flags; + } +} diff --git a/libs/@rustymotors/binary/src/lib/BytableServerMessage.ts b/libs/@rustymotors/binary/src/lib/BytableServerMessage.ts new file mode 100644 index 000000000..0d7131151 --- /dev/null +++ b/libs/@rustymotors/binary/src/lib/BytableServerMessage.ts @@ -0,0 +1,197 @@ +import { Bytable } from "./Bytable.js"; +import { BytableFieldTypes } from "./BytableMessage.js"; +import { BytableServerHeader } from "./BytableServerHeader.js"; +import { BytableObject } from "./types.js"; + +export class BytableServerMessage extends Bytable { + protected header_: BytableServerHeader = new BytableServerHeader(); + protected fields_: Array = []; + protected serializeOrder_: Array<{ + name: string; + field: keyof typeof BytableFieldTypes; + }> = []; + + constructor() { + super(); + } + + + + protected override deserializeFields(buffer: Buffer) { + let offset = 0; + + if (this.fields_.length === 0) { + this.setSerializeOrder([ + { + name: "data", + field: "Buffer", + }, + ]); + } + + for (const field of this.serializeOrder_) { + if (!(field.field in BytableFieldTypes)) { + throw new Error(`Unknown field type: ${field.field}`); + } + + const fieldType = BytableFieldTypes[field.field]; + const fieldInstance = new fieldType(); + fieldInstance.setName(field.name); + fieldInstance.deserialize(buffer.subarray(offset)); + this.fields_.push(fieldInstance); + offset += fieldInstance.serializeSize; + } + } + + override deserialize(buffer: Buffer) { + try { + const header = new BytableServerHeader(); + header.deserialize(buffer.subarray(0, header.serializeSize)); + this.deserializeFields(buffer.subarray(header.serializeSize)); + } catch (error) { + const err = new Error( + `Error deserializing message: ${(error as Error).message}`, + { + cause: error, + }, + ); + throw err; + } + } + + get header() { + return this.header_; + } + + override get serializeSize() { + const fieldSizes = this.fields_.map((field) => field.serializeSize); + return this.header_.serializeSize + fieldSizes.reduce((a, b) => a + b, 0); + // return this.align8(this.header_.serializeSize + fieldSizes.reduce((a, b) => a + b, 0)); + } + + protected override serializeFields() { + const buffer = Buffer.alloc( + this.serializeSize - this.header_.serializeSize, + ); + let offset = 0; + + for (const field of this.fields_) { + buffer.set(field.serialize(), offset); + offset += field.serializeSize; + } + return buffer; + } + + override serialize() { + const buffer = Buffer.alloc(this.serializeSize); + this.header_.setMessageLength(this.serializeSize - 2); + buffer.set(this.header_.serialize(), 0); + + buffer.set(this.serializeFields(), this.header_.serializeSize); + + return buffer; + } + + override get json() { + return { + name: this.name, + serializeSize: this.serializeSize, + header: this.header_.json, + fields: this.fields_.map((field) => field.json), + }; + } + + override setName(name: string) { + this.header_.setName(name); + } + + override toString() { + return JSON.stringify(this.json); + } + + setSerializeOrder( + serializeOrder: Array<{ + name: string; + field: keyof typeof BytableFieldTypes; + }>, + ) { + this.serializeOrder_ = serializeOrder; + } + + get sequenceNumber() { + return this.header_.sequence; + } + + setSequenceNumber(sequence: number) { + this.header_.sequence = sequence; + } + + get flags() { + return this.header_.flags; + } + + setFlags(flags: number) { + this.header_.flags = flags; + } + + getFieldValueByName(name: string) { + if (name === "") { + return undefined; + } + const field = this.fields_.find((field) => field.name === name); + if (!field) { + throw new Error(`Field ${name} not found`); + } + return field.value; + } + + setFieldValueByName(name: string, value: string | number | Buffer) { + if (name === "") { + return; + } + + const serializedFormat = this.serializeOrder_.find( + (field) => field.name === name, + ); + + if (!serializedFormat) { + throw new Error(`Field ${name} not found in serialized format`); + } + + const field = this.fields_.find((field) => field.name === name); + if (!field) { + const field = new BytableFieldTypes[serializedFormat.field](); + field.setName(name); + field.setValue(Buffer.from(this.toBuffer(value))); + + this.fields_.push(field); + return; + } + + if (!field) { + throw new Error(`Field ${name} not found, and could not be created`); + } + + field.setValue(value); + } + + getBody() { + return this.serializeFields(); + } + + setBody(buffer: Buffer) { + this.deserializeFields(buffer); + } + + get data() { + return this.getBody(); + } + + set data(buffer: Buffer) { + this.setBody(buffer); + } + + toHexString() { + return this.serialize().toString("hex"); + } +} diff --git a/libs/@rustymotors/binary/src/lib/BytableWord.ts b/libs/@rustymotors/binary/src/lib/BytableWord.ts new file mode 100644 index 000000000..bf9dc6527 --- /dev/null +++ b/libs/@rustymotors/binary/src/lib/BytableWord.ts @@ -0,0 +1,37 @@ +import { Bytable } from "./Bytable.js"; + +export class BytableWord extends Bytable { + private static validateBufferLength( + buffer: Buffer, + minLength: number, + offset: number = 0, + ) { + if (buffer.length < offset + minLength) { + throw new Error("Cannot deserialize buffer with insufficient length"); + } + } + + + + override deserialize(buffer: Buffer) { + BytableWord.validateBufferLength(buffer, 2); + super.deserialize(buffer.subarray(0, 2)); + } + + override get json() { + return { + name: this.name, + value: this.buffer.getUint16(0, true), + valueString: Buffer.from(this.buffer.buffer).toString("utf-8"), + serializeSize: this.serializeSize, + }; + } + + override toString() { + return this.buffer.toString(); + } + + override get serializeSize(): number { + return 2; + } +} diff --git a/libs/@rustymotors/binary/src/lib/GameMessage.ts b/libs/@rustymotors/binary/src/lib/GameMessage.ts new file mode 100644 index 000000000..e84b034dd --- /dev/null +++ b/libs/@rustymotors/binary/src/lib/GameMessage.ts @@ -0,0 +1,63 @@ +import { BinaryMember, Uint16_t, Uint8_tArray } from "./BinaryMember.js"; + +export class GameMessage extends BinaryMember { + private _messageId: Uint16_t = new Uint16_t(); + // The length of the message data, including the message ID and length fields + private _messageLength: Uint16_t = new Uint16_t(); + private _messageData: Uint8_tArray = new Uint8_tArray(2048); + + constructor() { + super(); + this._messageLength.setShort(2048); + } + + override deserialize(v: Uint8Array): void { + if (v.length < this.size()) { + throw new Error( + `GameMessage must be at least ${this.size()} bytes long, got ${v.length}`, + ); + } + + this._messageId.set(v.slice(0, 2)); + this._messageLength.set(v.slice(2, 4)); + this._messageData.set(v.slice(4, 2052)); + + // Trim the message data to the actual message length + const messageLength = this._messageLength.getShort(); + this._messageData.set(this._messageData.get().slice(0, messageLength - 4)); + } + + override serialize(): Buffer { + return Buffer.concat([ + this._messageId.get(), + this._messageLength.get(), + this._messageData.get(), + ]); + } + + override size(): number { + return this._messageLength.getShort("BE"); + } + + setMessageId(messageId: number): void { + this._messageId.setShort(messageId, "BE"); + } + + getMessageId(): number { + return this._messageId.getShort("BE"); + } + + setMessageData(data: Uint8Array): void { + this._messageData.set(data); + this._messageLength.setShort(data.length + 4, "BE"); + } + + getMessageData(): Uint8Array { + return this._messageData.get(); + } + + override toHexString(): string { + return this.serialize().toString("hex"); + } +} + diff --git a/libs/@rustymotors/binary/src/lib/Memory.test.ts b/libs/@rustymotors/binary/src/lib/Memory.test.ts new file mode 100644 index 000000000..e5b45ced5 --- /dev/null +++ b/libs/@rustymotors/binary/src/lib/Memory.test.ts @@ -0,0 +1,126 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import { Memory } from "./Memory.js"; + +describe("Memory", () => { + let memory: Memory; + + beforeEach(() => { + memory = new Memory(1024); + }); + + it("should initialize with the correct size", () => { + expect(memory.size).toBe(1024); + }); + + it("should correctly get locked status", () => { + expect(memory.locked).toBe(false); + memory.lock(); + expect(memory.locked).toBe(true); + memory.unlock(); + expect(memory.locked).toBe(false); + }); + + it("should read data correctly", () => { + memory.write(0, new Uint8Array([1, 2])); + const data = memory.read(0, 2); + expect(data[0]).toBe(1); + expect(data[1]).toBe(2); + }); + + it("should throw error on read out of bounds", () => { + expect(() => memory.read(1023, 2)).toThrow( + "Memory read out of bounds: 1023 + 2 > 1024", + ); + }); + + it("should write data correctly", () => { + const data = new Uint8Array([1, 2]); + memory.write(0, data); + expect(memory.read(0, 2)[0]).toBe(1); + }); + + it("should throw error on write out of bounds", () => { + const data = new Uint8Array([1, 2]); + expect(() => memory.write(1023, data)).toThrow( + "Memory write out of bounds: 1023 + 2 > 1024", + ); + }); + + it("should throw error when writing to locked memory", () => { + memory.lock(); + const data = new Uint8Array([1, 2]); + expect(() => memory.write(0, data)).toThrow("Memory is locked"); + }); + + it("should compare data correctly", () => { + const data = new Uint8Array([1, 2]); + memory.write(0, data); + expect(memory.compare(0, data)).toBe(true); + expect(memory.compare(0, new Uint8Array([1, 3]))).toBe(false); + }); + + it("should throw error on compare out of bounds", () => { + const data = new Uint8Array([1, 2]); + expect(() => memory.compare(1023, data)).toThrow( + "Memory compare out of bounds: 1023 + 2 > 1024", + ); + }); + + it("should get unsigned 16-bit value correctly", () => { + memory.write(0, new Uint8Array([1, 2])); + expect(memory.fetchInt16(0)).toBe(513); // little endian + expect(memory.fetchInt16(0, false, "big")).toBe(258); // big endian + }); + + it("should get signed 16-bit value correctly", () => { + memory.write(0, new Uint8Array([255, 255])); + expect(memory.fetchInt16(0, true)).toBe(-1); // little endian + expect(memory.fetchInt16(0, true, "big")).toBe(-1); // big endian + }); + + it("should throw error on get16 out of bounds", () => { + expect(() => memory.fetchInt16(1023)).toThrow( + "Memory word out of bounds: 1023 + 2 > 1024", + ); + }); + + it("should get unsigned 32-bit value correctly", () => { + memory.write(0, new Uint8Array([1, 2, 3, 4])); + expect(memory.get32(0)).toBe(67305985); // little endian + expect(memory.get32(0, false, "big")).toBe(16909060); // big endian + }); + + it("should get signed 32-bit value correctly", () => { + memory.write(0, new Uint8Array([255, 255, 255, 255])); + expect(memory.get32(0, true)).toBe(-1); // little endian + expect(memory.get32(0, true, "big")).toBe(-1); // big endian + }); + + it("should throw error on get32 out of bounds", () => { + expect(() => memory.get32(1021)).toThrow( + "Memory word out of bounds: 1021 + 4 > 1024", + ); + }); + + it("should lock and unlock memory correctly", () => { + memory.lock(); + expect(() => memory.write(0, new Uint8Array([1]))).toThrow( + "Memory is locked", + ); + memory.unlock(); + expect(() => memory.write(0, new Uint8Array([1]))).not.toThrow(); + }); + + it("should throw error on get16 with invalid address", () => { + expect(() => memory.fetchInt16(1023)).toThrow( + "Memory word out of bounds: 1023 + 2 > 1024", + ); + }); + + it("should throw error on get16 with undefined bytes", () => { + memory = new Memory(2); + expect(() => memory.fetchInt16(0)).toThrow( + "Memory word out of bounds: 0 + 2 > 2", + ); + }); +}); diff --git a/libs/@rustymotors/binary/src/lib/Memory.ts b/libs/@rustymotors/binary/src/lib/Memory.ts new file mode 100644 index 000000000..5d27d354d --- /dev/null +++ b/libs/@rustymotors/binary/src/lib/Memory.ts @@ -0,0 +1,103 @@ +export class Memory { + private memorySize: number; + private memory: Uint8Array = new Uint8Array(0); + private isMemoryLocked: boolean = false; + + constructor(size: number) { + this.memorySize = size; + this.memory = new Uint8Array(size); + } + read(address: number, length: number) { + if (address + length > this.memorySize) { + throw new Error( + `Memory read out of bounds: ${address} + ${length} > ${this.memorySize}`, + ); + } + + const readOnlyCopy = new Uint8Array(length); + readOnlyCopy.set(this.memory.slice(address, address + length)); + return readOnlyCopy; + } + + write(address: number, data: Uint8Array) { + if (this.isMemoryLocked) { + throw new Error("Memory is locked"); + } + + this.isMemoryLocked = true; + + if (address + data.length > this.memorySize) { + throw new Error( + `Memory write out of bounds: ${address} + ${data.length} > ${this.memorySize}`, + ); + } + + this.memory.set(data, address); + + this.isMemoryLocked = false; + } + + compare(address: number, data: Uint8Array) { + if (address + data.length > this.memorySize) { + throw new Error( + `Memory compare out of bounds: ${address} + ${data.length} > ${this.memorySize}`, + ); + } + + for (let i = 0; i < data.length; i++) { + if (this.memory[address + i] !== data[i]) { + return false; + } + } + + return true; + } + + fetchInt16(address: number, signed = false, endianness: "little" | "big" = "little") { + if (address + 4 > this.memorySize) { + throw new Error( + `Memory word out of bounds: ${address} + 2 > ${this.memorySize}`, + ); + } + + const data = new DataView(this.memory.buffer); + + if (signed) { + return data.getInt16(address, endianness === "little"); + } else { + return data.getUint16(address, endianness === "little"); + } + } + + get32(address: number, signed = false, endianness: "little" | "big" = "little") { + if (address + 8 > this.memorySize) { + throw new Error( + `Memory word out of bounds: ${address} + 4 > ${this.memorySize}`, + ); + } + + const data = new DataView(this.memory.buffer); + + if (signed) { + return data.getInt32(address, endianness === "little"); + } else { + return data.getUint32(address, endianness === "little"); + } + } + + lock() { + this.isMemoryLocked = true; + } + + unlock() { + this.isMemoryLocked = false; + } + + get locked() { + return this.isMemoryLocked; + } + + get size() { + return this.memorySize; + } +} diff --git a/libs/@rustymotors/binary/src/lib/Serializer.ts b/libs/@rustymotors/binary/src/lib/Serializer.ts new file mode 100644 index 000000000..615b4be64 --- /dev/null +++ b/libs/@rustymotors/binary/src/lib/Serializer.ts @@ -0,0 +1,53 @@ +import { BytableBuffer } from "./BytableBuffer.js"; +import { Field } from "./types.js"; + +/** + * Given a buffer, and a sequence of fields, deserialize the buffer into the fields + * @param buffer The buffer to deserialize + * @param fields The fields to deserialize into + */ +export function deserialize(buffer: Buffer, fields: Field[]): void { +try { + let offset = 0; + for (const field of fields) { + field.deserialize(buffer.subarray(offset, offset + field.serializeSize)); + offset += field.serializeSize; + } + +} catch (error) { + throw new Error(`Error deserializing message: ${(error as Error).message}`); + +}} + +/** + * Serialize a set of fields into a buffer + * @param fields The fields to serialize + * @returns The serialized buffer + */ +export function serialize(fields: Field[]): Buffer { +try { + const buffer = Buffer.alloc(serializeSize(fields)); + let offset = 0; + for (const field of fields) { + buffer.set(field.serialize(), offset); + offset += field.serializeSize; + } + return buffer; +} catch (error) { + throw new Error(`Error serializing message ${error}`); + +} +} + +/** + * Get the size of a set of fields when serialized + * @param fields The fields to get the size of + * @returns The size of the fields when serialized + */ +export function serializeSize(fields: Field[]): number { + return fields.reduce((a, b) => a + b.serializeSize, 0); +} + +export function createEmptyField() { + return new BytableBuffer() as Field; +} diff --git a/libs/@rustymotors/binary/src/lib/binary.test.ts b/libs/@rustymotors/binary/src/lib/binary.test.ts deleted file mode 100644 index b999e3b2f..000000000 --- a/libs/@rustymotors/binary/src/lib/binary.test.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { describe, it, expect, beforeEach } from "vitest"; -import { BytableMessage } from "./BytableMessage"; -import { BytableHeader } from "./BytableHeader"; -import { BytableContainer } from "./BytableContainer"; -import { BytableDword } from "./BytableDword"; -import { BytableBase } from "./BytableBase"; - -describe("Binary Message Parsing", () => { - const testHex = - "0501013e010100000000013e0022643331366364326464366266383730383933646662616166313766393635383834650000010032323841413331423743423530463233343933323539323730413842334430353233333534463146323936413132343430444541343942353031453843363346443841394141453143463534304242333535354245363437324641423230443632303634304239394645453837414139373332363938304231303430323341314132354233453635314534413434453445394242313341453644303033383233303544434336393035434135393032363435443332463138413644414434443334363937304531443931313944424534363746373333364443334333444439413043443942463741313945453331323631443945444146413139344445443846000432313736fea31c19"; - const testBuffer = Buffer.from(testHex, "hex"); - - describe("BytableBase", () => { - it("should throw error when deserializing empty buffer", () => { - const base = new BytableBase(); - expect(() => base.deserialize(Buffer.alloc(0))).toThrowError("Cannot deserialize empty buffer"); - }); - }); - - describe("BytableHeader", () => { - it("should correctly parse message header", () => { - const header = BytableHeader.fromBuffer(testBuffer, 0); - - expect(header.messageId).toBe(0x0501); - expect(header.messageLength).toBe(0x013e); - expect(header.messageVersion).toBe(1); - expect(header.serializeSize).toBe(12); // Version 1 header size - }); - }); - - describe("BytableMessage", () => { - let message: BytableMessage; - - beforeEach(() => { - message = BytableMessage.fromBuffer(testBuffer, 0); - message.setSerializeOrder([ - { name: "ContextId", field: "Container" }, - { name: "", field: "Container" }, - { name: "SessionKey", field: "Container" }, - { name: "GameId", field: "Container" }, - { name: "", field: "Dword" }, - ]); - message.deserialize(testBuffer); - }); - - it("should correctly parse the full message", () => { - expect(message.header.messageId).toBe(0x0501); - expect(message.header.messageVersion).toBe(1); - }); - - it("should retrieve field values by name", () => { - const sessionKey = message.getFieldValueByName("SessionKey"); - expect(sessionKey).toBe( - "228AA31B7CB50F23493259270A8B3D0523354F1F296A12440DEA49B501E8C63FD8A9AAE1CF540BB3555BE6472FAB20D620640B99FEE87AA97326980B104023A1A25B3E651E4A44E4E9BB13AE6D00382305DCC6905CA5902645D32F18A6DAD4D346970E1D9119DBE467F7336DC3C3DD9A0CD9BF7A19EE31261D9EDAFA194DED8F", - ); - }); - - it("should serialize back to the original hex string", () => { - const serialized = message.serialize(); - expect(serialized.toString("hex")).toBe(testHex); - }); - - it("should throw error when accessing non-existent field", () => { - expect(() => { - message.getFieldValueByName("NonExistentField"); - }).toThrowError("Field NonExistentField not found"); - }); - }); - - describe("BytableContainer", () => { - describe("Non-null-terminated mode", () => { - let container: BytableContainer; - - beforeEach(() => { - container = new BytableContainer(); - container.setNullTerminated(false); - }); - - it("should dynamically adjust length to match content", () => { - container.setValue("hello"); - expect(container.getLength()).toBe(5); - expect(container.getValue()).toBe("hello"); - - container.setValue("longer string"); - expect(container.getLength()).toBe(13); - expect(container.getValue()).toBe("longer string"); - }); - - it("should serialize with length prefix", () => { - container.setValue("test"); - const serialized = container.serialize(); - // First 2 bytes should be length (4), followed by "test" - expect(serialized.length).toBe(6); // 2 bytes length + 4 bytes content - expect(serialized.readUInt16BE(0)).toBe(4); // Length prefix - expect(serialized.subarray(2).toString()).toBe("test"); - }); - - it("should calculate correct serializeSize", () => { - container.setValue("hello"); - expect(container.serializeSize).toBe(7); // 5 (content) + 2 (length prefix) - }); - }); - - describe("Null-terminated mode", () => { - let container: BytableContainer; - - beforeEach(() => { - container = new BytableContainer(); - container.setNullTerminated(true); - }); - - it("should return a single null terminator when serializing empty string", () => { - container.setNullTerminated(true); - const serialized = container.serialize(); - expect(serialized.toString("hex")).toBe("00"); - }); - - it("should serialize without length prefix", () => { - container.setNullTerminated(true); - container.setValue("test"); - const serialized = container.serialize(); - expect(serialized.toString("hex")).toBe("7465737400"); - }); - - it("should calculate correct serializeSize", () => { - container.setNullTerminated(true); - container.setValue("hello"); - expect(container.serializeSize).toBe(6); // Just the content length (including null terminator) - }); - - it("should throw when setting length for null-terminated container", () => { - container.setNullTerminated(true); - - // Can't change length after setting value - expect(() => container.setLength(5)).toThrowError( - "Cannot set length for null terminated container", - ); - }); - }); - - describe("Edge cases", () => { - let container: BytableContainer; - - beforeEach(() => { - container = new BytableContainer(); - }); - - it("should handle empty strings", () => { - container.setValue(""); - expect(container.getLength()).toBe(0); - expect(container.getValue()).toBe(""); - }); - - it("should throw error when setting empty buffer in null-terminated mode", () => { - container.setNullTerminated(true); - expect(() => container.setValue(Buffer.alloc(0))).toThrowError( - "Cannot set empty buffer", - ); - }); - }); - }); - - describe("BytableDword", () => { - it("should throw error when deserializing buffer with insufficient length", () => { - const dword = new BytableDword(); - expect(() => dword.deserialize(Buffer.alloc(3))).toThrowError("Cannot deserialize buffer with insufficient length"); - }); - - it("should correctly parse 32-bit values", () => { - const dword = BytableDword.fromBuffer(testBuffer, testBuffer.length - 4); - expect(dword.serializeSize).toBe(4); - // Add specific value checks based on your expected data - }); - }); -}); \ No newline at end of file diff --git a/libs/@rustymotors/binary/src/lib/serializer.test.ts b/libs/@rustymotors/binary/src/lib/serializer.test.ts new file mode 100644 index 000000000..add935b02 --- /dev/null +++ b/libs/@rustymotors/binary/src/lib/serializer.test.ts @@ -0,0 +1,120 @@ +import { describe, it, expect } from "vitest"; +import { deserialize, serialize, serializeSize } from "./Serializer.js"; +import { Field } from "./types.js"; + +describe("Serializer", () => { + describe("deserialize", () => { + it("should correctly deserialize a buffer into fields", () => { + const buffer = Buffer.from([0x01, 0x02, 0x03, 0x04]); + const fields: Field[] = [ + { + name: "field1", + serializeSize: 2, + deserialize: (buf: Buffer) => { + expect(buf).toEqual(Buffer.from([0x01, 0x02])); + }, + serialize: () => Buffer.from([]), + }, + { + name: "field2", + serializeSize: 2, + deserialize: (buf: Buffer) => { + expect(buf).toEqual(Buffer.from([0x03, 0x04])); + }, + serialize: () => Buffer.from([]), + }, + ]; + + deserialize(buffer, fields); + }); + + it("should throw an error if deserialization fails", () => { + const buffer = Buffer.from([0x01, 0x02]); + const fields: Field[] = [ + { + name: "field1", + serializeSize: 4, + deserialize: () => { + throw new Error("Test error"); + }, + serialize: () => Buffer.from([]), + }, + ]; + + expect(() => deserialize(buffer, fields)).toThrow( + "Error deserializing message: Test error", + ); + }); + }); + + describe("serialize", () => { + it("should correctly serialize fields into a buffer", () => { + const fields: Field[] = [ + { + name: "field1", + serializeSize: 2, + deserialize: () => { + // Do nothing + }, + serialize: () => Buffer.from([0x01, 0x02]), + }, + { + name: "field2", + serializeSize: 2, + deserialize: () => { + // Do nothing + }, + serialize: () => Buffer.from([0x03, 0x04]), + }, + ]; + + const buffer = serialize(fields); + expect(buffer).toEqual(Buffer.from([0x01, 0x02, 0x03, 0x04])); + }); + + it("should throw an error if serialization fails", () => { + const fields: Field[] = [ + { + name: "field1", + serializeSize: 2, + deserialize: () => { + // Do nothing + }, + serialize: () => { + throw new Error("Test error"); + }, + }, + ]; + + expect(() => serialize(fields)).toThrow( + "Error serializing message Error: Test error", + ); + }); + }); + + describe("serializeSize", () => { + it("should correctly calculate the size of serialized fields", () => { + const fields: Field[] = [ + { + name: "field1", + serializeSize: 2, + deserialize: () => { + // Do nothing + }, + serialize: () => Buffer.from([]), + }, + { + name: "field2", + serializeSize: 3, + deserialize: () => { + // Do nothing + }, + serialize: () => Buffer.from([]), + }, + ]; + + const size = serializeSize(fields); + expect(size).toBe(5); + }); + }); +}); diff --git a/libs/@rustymotors/binary/src/lib/types.ts b/libs/@rustymotors/binary/src/lib/types.ts index f1c403915..d57304f3f 100644 --- a/libs/@rustymotors/binary/src/lib/types.ts +++ b/libs/@rustymotors/binary/src/lib/types.ts @@ -1,13 +1,35 @@ +/** + * A field that can be serialized and deserialized + */ +export interface Field { + /** + * The name of the field + */ + name: string; + /** + * The size of the field when serialized + */ + get serializeSize(): number; + /** + * Serialize the field into a buffer + */ + serialize(): Buffer; + /** + * Deserialize the field from a buffer + * @param buffer The buffer to deserialize from + */ + deserialize(buffer: Buffer): void; +} + export interface BytableObject { serialize(): Buffer; deserialize(buffer: Buffer): void; - json: any; + json: Record; toString(): string; get serializeSize(): number; - getUint16(offset: number, littleEndian: boolean): number; - getUint32(offset: number, littleEndian: boolean): number; get name(): string; get value(): string | number | Buffer; setName(name: string): void; setValue(value: string | number | Buffer): void; -} \ No newline at end of file +} + diff --git a/libs/@rustymotors/protocol/README.md b/libs/@rustymotors/protocol/README.md new file mode 100644 index 000000000..f91dfdb03 --- /dev/null +++ b/libs/@rustymotors/protocol/README.md @@ -0,0 +1,7 @@ +# protocol + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build binary` to build the library. diff --git a/libs/@rustymotors/protocol/package.json b/libs/@rustymotors/protocol/package.json new file mode 100644 index 000000000..93707d223 --- /dev/null +++ b/libs/@rustymotors/protocol/package.json @@ -0,0 +1,14 @@ +{ + "name": "@rustymotors/protocol", + "version": "0.0.1", + "private": true, + "dependencies": { + "@mikro-orm/core": "^6.4.3", + "rusty-motors-shared": "workspace:1.0.0-next.0", + "rusty-motors-shared-packets": "workspace:1.0.0-next.0", + "tslib": "^2.8.1" + }, + "type": "module", + "main": "src/index.js", + "typings": "src/index.d.ts" +} diff --git a/libs/@rustymotors/protocol/project.json b/libs/@rustymotors/protocol/project.json new file mode 100644 index 000000000..66e4fafbf --- /dev/null +++ b/libs/@rustymotors/protocol/project.json @@ -0,0 +1,20 @@ +{ + "name": "@rustymotors/protocol", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/@rustymotors/protocol/src", + "projectType": "library", + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/libs/@rustymotors/protocol", + "tsConfig": "libs/@rustymotors/protocol/tsconfig.json", + "packageJson": "libs/@rustymotors/protocol/package.json", + "main": "libs/@rustymotors/protocol/src/index.ts", + "assets": ["libs/@rustymotors/protocol/*.md"] + } + } + } +} diff --git a/libs/@rustymotors/protocol/src/index.ts b/libs/@rustymotors/protocol/src/index.ts new file mode 100644 index 000000000..13df37290 --- /dev/null +++ b/libs/@rustymotors/protocol/src/index.ts @@ -0,0 +1,2 @@ +export { getMCOProtocolInstance } from "./lib/MCOProtocol.js"; +export * from "./lib/types.js"; \ No newline at end of file diff --git a/libs/@rustymotors/protocol/src/lib/MCOProtocol.ts b/libs/@rustymotors/protocol/src/lib/MCOProtocol.ts new file mode 100644 index 000000000..7199b11e2 --- /dev/null +++ b/libs/@rustymotors/protocol/src/lib/MCOProtocol.ts @@ -0,0 +1,136 @@ +import { Socket } from "node:net"; +import { BytableMessage, createRawMessage } from "@rustymotors/binary"; +import { getServerLogger, ServerLogger } from "rusty-motors-shared"; +import { serverLoginMessageHandler } from "./serverLoginMessageHandler.js"; +import { writePacket } from "./writePacket.js"; +import { defaultMessageHandler } from "./defaultMessageHandler.js"; + +/** + * Aligns the given value to the nearest multiple of 8. + * + * @param value - The number to be aligned. + * @returns The aligned value, which is the smallest multiple of 8 that is greater than or equal to the input value. + */ +export function align8(value: number) { + return value + (8 - (value % 8)); +} + +/** + * An array of message handler objects, each containing an operation code, a name, and a handler function. + * + * Each handler function processes a message and returns a promise that resolves to an object containing + * the connection ID and the processed message (or null if no message is returned). + * + * @type {Array<{ opCode: number; name: string; handler: (args: { connectionId: string; message: BytableMessage; log: ServerLogger; }) => Promise<{ connectionId: string; message: BytableMessage | null; }> }>} + */ +const messageHandlers: { + opCode: number; + name: string; + handler: (args: { + connectionId: string; + message: BytableMessage; + log?: ServerLogger; + }) => Promise<{ + connectionId: string; + message: BytableMessage | null; + }>; +}[] = []; + +class MCOProtocol { + private connections: Map = + new Map(); + + constructor() { + messageHandlers.push({ + opCode: 0x501, + name: "server login", + handler: serverLoginMessageHandler, + }); + messageHandlers.push({ + opCode: 0x532, + name: "get personas", + handler: defaultMessageHandler, + }); + } + + getConnection(connectionId: string) { + return this.connections.get(connectionId); + } + + acceptIncomingSocket({ + connectionId, + port, + socket, + log = getServerLogger("MCOProtocol/acceptIncomingSocket"), + }: { + connectionId: string; + port: number; + socket: Socket; + log?: ServerLogger; + }) { + log.debug({ connectionId, port }, "Accepting incoming socket"); + this.connections.set(connectionId, { socket, port }); + socket.on("data", (data) => { + this.receivePacket({ connectionId, data }); + }); + socket.on("close", () => { + this.connections.delete(connectionId); + }); + socket.on("error", (error) => { + if (error.message === "read ECONNRESET") { + log.debug({ connectionId }, "Connection reset by client"); + } else { + log.error({ connectionId }, `Socket error: ${error}`); + } + }); + } + + receivePacket({ + connectionId, + data, + log = getServerLogger("MCOProtocol/receivePacket"), + }: { + connectionId: string; + data: Buffer; + log?: ServerLogger; + }) { + const incomingPacket = createRawMessage(data); + log.debug( + { connectionId }, + `Received packet: ${incomingPacket.serialize().toString("hex")}`, + ); + + const opCode = incomingPacket.header.messageId; + const handler = messageHandlers.find( + (handler) => handler.opCode === opCode, + ); + if (handler) { + handler + .handler({ connectionId, message: incomingPacket }) + .then(({ connectionId, message }) => { + if (message) { + writePacket({ connectionId, data: message.serialize() }); + } + }); + } else { + log.warn({ connectionId, opCode }, "No handler found for message"); + } + + log.debug({ connectionId }, "Packet processed"); + } +} + +let instance: MCOProtocol | null = null; + +/** + * Returns a singleton instance of the MCOProtocol class. + * If the instance does not already exist, it creates a new one. + * + * @returns {MCOProtocol} The singleton instance of the MCOProtocol class. + */ +export function getMCOProtocolInstance() { + if (!instance) { + instance = new MCOProtocol(); + } + return instance; +} diff --git a/libs/@rustymotors/protocol/src/lib/defaultMessageHandler.ts b/libs/@rustymotors/protocol/src/lib/defaultMessageHandler.ts new file mode 100644 index 000000000..0d8904161 --- /dev/null +++ b/libs/@rustymotors/protocol/src/lib/defaultMessageHandler.ts @@ -0,0 +1,21 @@ +import { BytableMessage } from "@rustymotors/binary"; +import { getServerLogger, ServerLogger } from "rusty-motors-shared"; + + +export async function defaultMessageHandler({ + connectionId, message, log = getServerLogger("MCOProtocol/defaultMessageHandler"), +}: { + connectionId: string; + message: BytableMessage; + log?: ServerLogger; +}): Promise<{ + connectionId: string; + message: BytableMessage | null; +}> { + const messageId = message.header.messageId; + log.debug( + { connectionId, messageId: messageId.toString(16) }, + "Not yet implemented" + ); + return { connectionId, message: null }; +} diff --git a/libs/@rustymotors/protocol/src/lib/parseNPSSessionKey.ts b/libs/@rustymotors/protocol/src/lib/parseNPSSessionKey.ts new file mode 100644 index 000000000..e227be426 --- /dev/null +++ b/libs/@rustymotors/protocol/src/lib/parseNPSSessionKey.ts @@ -0,0 +1,25 @@ +/** + * Parses a buffer to extract the NPS session key and its expiration time. + * + * @param buffer - The buffer containing the session key data. + * @returns An object containing the session key length, session key (in hex format), and expiration time. + */ + +export function parseNPSSessionKey(buffer: Buffer): { + sessionKeyLength: number; + sessionKey: string; + expires: number; +} { + try { + const sessionKeyLength = buffer.readInt16BE(0); + const sessionKey = buffer.subarray(2, sessionKeyLength + 2).toString("hex"); + const expires = buffer.readInt32BE(sessionKeyLength + 2); + return { sessionKeyLength, sessionKey, expires }; + } catch (error) { + const err = new Error( + `Error parsing session key: ${(error as Error).message}` + ); + err.cause = error; + throw err; + } +} diff --git a/libs/@rustymotors/protocol/src/lib/serverLoginMessageHandler.ts b/libs/@rustymotors/protocol/src/lib/serverLoginMessageHandler.ts new file mode 100644 index 000000000..5b7f27b64 --- /dev/null +++ b/libs/@rustymotors/protocol/src/lib/serverLoginMessageHandler.ts @@ -0,0 +1,174 @@ +import { + BytableMessage, + createEmptyField, + createGameMessage, + createRawMessage, + serialize, +} from "@rustymotors/binary"; +import { privateDecrypt } from "crypto"; +import { readFileSync } from "fs"; +import { + getServerLogger, + ServerLogger, + getServerConfiguration, +} from "rusty-motors-shared"; +import { parseNPSSessionKey } from "./parseNPSSessionKey.js"; + +export async function serverLoginMessageHandler({ + connectionId, + message, + log = getServerLogger("MCOProtocol/serverLoginMessageHandler"), +}: { + connectionId: string; + message: BytableMessage; + log?: ServerLogger; +}): Promise<{ + connectionId: string; + message: BytableMessage | null; +}> { + try { + const inboundMessage = createGameMessage(); + inboundMessage.setSerializeOrder([ + { name: "ticket", field: "PrefixedString2" }, + { name: "val2", field: "Short" }, + { name: "encryptedSessionKey", field: "PrefixedString2" }, + { name: "gameServiceId", field: "PrefixedString2" }, + { name: "exeChecksum", field: "Dword" }, + ]); + inboundMessage.deserialize(message.serialize()); + const messageId = inboundMessage.header.messageId; + log.debug( + { connectionId, messageId: messageId.toString(16) }, + "Processing server login message", + ); + + // TODO: verify ticket + const encryptedSessionKey = inboundMessage.getFieldValueByName( + "encryptedSessionKey", + ); + if (!encryptedSessionKey) { + throw Error("No encrypted session key found"); + } + + const sessionkeyString = Buffer.from(encryptedSessionKey as String, "hex"); + + const config = getServerConfiguration(); + + if (config.privateKeyFile === "") { + throw Error("No private key file specified"); + } + + let sessionKey: string | null = null; + + const privatekeyContents = readFileSync(config.privateKeyFile); + + try { + const decrypted = privateDecrypt( + { + key: privatekeyContents, + }, + sessionkeyString, + ); // length of decrypted should be 128 bytes + + const parsedSessionKey = parseNPSSessionKey(decrypted); + + if (parsedSessionKey.sessionKeyLength !== 32) { + throw Error( + `Session key length is not 32 bytes: ${parsedSessionKey.sessionKeyLength}`, + ); + } + + sessionKey = parsedSessionKey.sessionKey; // length of session key should be 12 bytes + } catch (error: unknown) { + log.trace( + { + connectionId, + key: sessionkeyString.toString("utf8"), + }, + "Session key", + ); // 128 bytes + log.trace( + { + connectionId, + decrypted: sessionKey, + }, + "Decrypted", + ); // 12 bytes + log.fatal( + { + connectionId, + }, + `Error decrypting session key: ${(error as Error).message}`, + ); + const err = new Error( + `Error decrypting session key: ${(error as Error).message}`, + ); + err.cause = error; + throw err; + } + + if (!sessionKey) { + throw Error("No session key found"); + } + + log.debug( + { + connectionId, + }, + "Session key decrypted", + ); + + log.debug( + { + connectionId, + }, + "Creating outbound message", + ); + + const responseCode = 0x601; + + const loginResponseMessage = createRawMessage(); + loginResponseMessage.header.setMessageId(responseCode); + loginResponseMessage.setSerializeOrder([ + { name: "ban", field: "Dword" }, + { name: "gag", field: "Dword" }, + { name: "customerId", field: "Dword" }, + { name: "isCacheHit", field: "Dword" }, + { name: "profileId", field: "Dword" }, + { name: "sessionKey", field: "PrefixedString2" }, + ]); + loginResponseMessage.setFieldValueByName("profileId", 2); + loginResponseMessage.setFieldValueByName("isCacheHit", 3); + loginResponseMessage.setFieldValueByName("customerId", 1); + loginResponseMessage.setFieldValueByName("ban", 0); + loginResponseMessage.setFieldValueByName("gag", 0); + loginResponseMessage.setFieldValueByName("sessionKey", sessionKey); + + const fields = [ + loginResponseMessage.getField("profileId") ?? createEmptyField(), + loginResponseMessage.getField("isCacheHit") ?? createEmptyField(), + loginResponseMessage.getField("customerId") ?? createEmptyField(), + loginResponseMessage.getField("ban") ?? createEmptyField(), + loginResponseMessage.getField("gag") ?? createEmptyField(), + loginResponseMessage.getField("sessionKey") ?? createEmptyField(), + ] + + const body = serialize(fields); + + const responseMessage = createRawMessage(); + responseMessage.header.setMessageId(responseCode); + responseMessage.setBody(body); + + log.debug( + { + connectionId, + }, + "Outbound message created", + ); + + return { connectionId, message: responseMessage }; + } catch (error) { + log.error({ connectionId }, `Error creating game message: ${error}`); + } + return { connectionId, message: null }; +} diff --git a/libs/@rustymotors/protocol/src/lib/types.ts b/libs/@rustymotors/protocol/src/lib/types.ts new file mode 100644 index 000000000..e69de29bb diff --git a/libs/@rustymotors/protocol/src/lib/writePacket.ts b/libs/@rustymotors/protocol/src/lib/writePacket.ts new file mode 100644 index 000000000..8d36385cc --- /dev/null +++ b/libs/@rustymotors/protocol/src/lib/writePacket.ts @@ -0,0 +1,23 @@ +import { getServerLogger, ServerLogger } from "rusty-motors-shared"; +import { getMCOProtocolInstance } from "./MCOProtocol.js"; + +export function writePacket({ + connectionId, data, log = getServerLogger("MCOProtocol/writePacket"), +}: { + connectionId: string; + data: Buffer; + log?: ServerLogger; +}) { + log.debug({ connectionId }, `Writing packet: ${data.toString("hex")}`); + try { + const connection = getMCOProtocolInstance().getConnection(connectionId); + if (connection) { + connection.socket.write(data); + connection.socket.write(data); + } else { + log.error({ connectionId }, "Connection not found"); + } + } catch (error) { + log.error({ connectionId }, `Error writing packet: ${error}`); + } +} diff --git a/libs/@rustymotors/protocol/tsconfig.json b/libs/@rustymotors/protocol/tsconfig.json new file mode 100644 index 000000000..ba131bc27 --- /dev/null +++ b/libs/@rustymotors/protocol/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "incremental": true, + "composite": true, + "resolveJsonModule": true + }, + "include": ["src/**/*.ts"] +} diff --git a/libs/@rustymotors/roomserver/README.md b/libs/@rustymotors/roomserver/README.md new file mode 100644 index 000000000..bda568f31 --- /dev/null +++ b/libs/@rustymotors/roomserver/README.md @@ -0,0 +1,7 @@ +# roomserver + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build binary` to build the library. diff --git a/libs/@rustymotors/roomserver/package.json b/libs/@rustymotors/roomserver/package.json new file mode 100644 index 000000000..132368ad0 --- /dev/null +++ b/libs/@rustymotors/roomserver/package.json @@ -0,0 +1,16 @@ +{ + "name": "@rustymotors/roomserver", + "version": "0.0.1", + "private": true, + "dependencies": { + "@mikro-orm/core": "^6.4.7", + "@rustymotors/binary": "workspace:^", + "rusty-motors-database": "workspace:1.0.0-next.0", + "rusty-motors-shared": "workspace:1.0.0-next.0", + "rusty-motors-shared-packets": "workspace:1.0.0-next.0", + "tslib": "^2.8.1" + }, + "type": "module", + "main": "src/index.js", + "typings": "src/index.d.ts" +} diff --git a/libs/@rustymotors/roomserver/project.json b/libs/@rustymotors/roomserver/project.json new file mode 100644 index 000000000..d5eba0194 --- /dev/null +++ b/libs/@rustymotors/roomserver/project.json @@ -0,0 +1,20 @@ +{ + "name": "@rustymotors/roomserver", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/@rustymotors/binary/src", + "projectType": "library", + "tags": [], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/libs/@rustymotors/roomserver", + "tsConfig": "libs/@rustymotors/roomserver/tsconfig.json", + "packageJson": "libs/@rustymotors/roomserver/package.json", + "main": "libs/@rustymotors/roomserver/src/index.ts", + "assets": ["libs/@rustymotors/roomserver/*.md"] + } + } + } +} diff --git a/libs/@rustymotors/roomserver/src/index.ts b/libs/@rustymotors/roomserver/src/index.ts new file mode 100644 index 000000000..70c90405d --- /dev/null +++ b/libs/@rustymotors/roomserver/src/index.ts @@ -0,0 +1 @@ +export { RoomServer } from "./lib/RoomServer.js"; \ No newline at end of file diff --git a/libs/@rustymotors/roomserver/src/lib/CarDecal.ts b/libs/@rustymotors/roomserver/src/lib/CarDecal.ts new file mode 100644 index 000000000..9a95f65a6 --- /dev/null +++ b/libs/@rustymotors/roomserver/src/lib/CarDecal.ts @@ -0,0 +1,69 @@ +import { BinaryMember, Uint8_tArray, Uint8_t } from "@rustymotors/binary"; + +export class CarDecal extends BinaryMember { + private _data: Uint8_tArray = new Uint8_tArray(4); + + override set(v: Uint8Array): void { + if (v.length !== 4) { + throw new Error(`CarDecal must be 4 bytes long, got ${v.length}`); + } + + this._data.set(v); + } + + override get(): Uint8Array { + return this._data.get(); + } + + override size(): number { + return this._data.size(); + } + + /** + * Get the background color of the decal + * This is the first byte of the decal + * @returns {Uint8_t} + */ + get backgroundImage(): Uint8_t { + const value = this._data.get(); + const returnValue = new Uint8_t(); + returnValue.set(value.slice(0, 1)); + return returnValue; + } + + /** + * Get the foreground color of the decal + * This is the second byte of the decal + * @returns {Uint8_t} + */ + get foregroundImage(): Uint8_t { + const value = this._data.get(); + const returnValue = new Uint8_t(); + returnValue.set(value.slice(1, 2)); + return returnValue; + } + + /** + * Get the first color + * This is the third byte of the decal + * @returns {Uint8_t} + */ + get color9(): Uint8_t { + const value = this._data.get(); + const returnValue = new Uint8_t(); + returnValue.set(value.slice(2, 3)); + return returnValue; + } + + /** + * Get the second color + * This is the fourth byte of the decal + * @returns {Uint8_t} + */ + get color1(): Uint8_t { + const value = this._data.get(); + const returnValue = new Uint8_t(); + returnValue.set(value.slice(3, 4)); + return returnValue; + } +} diff --git a/libs/@rustymotors/roomserver/src/lib/LoginRequest.ts b/libs/@rustymotors/roomserver/src/lib/LoginRequest.ts new file mode 100644 index 000000000..921c89258 --- /dev/null +++ b/libs/@rustymotors/roomserver/src/lib/LoginRequest.ts @@ -0,0 +1,86 @@ +import { + BinaryMember, + CString, + Uint32_t, + Uint8_tArray, +} from "@rustymotors/binary"; +import { UserInfo } from "./UserInfo.js"; + +export class LoginRequest extends BinaryMember { + private _userInfo: UserInfo = new UserInfo(); + private _customerNumber: Uint32_t = new Uint32_t(); + private _flags: Uint32_t = new Uint32_t(); + private _version: CString = new CString(33); + private _hostName: CString = new CString(64); + private _ipAddress: CString = new CString(16); + private _keyHash: Uint8_tArray = new Uint8_tArray(16); + + override deserialize(v: Uint8Array): void { + if (v.length > this.size()) { + throw new Error( + `LoginRequest must be ${this.size()} bytes long, got ${v.length}`, + ); + } + + const fields = [ + this._userInfo, + this._customerNumber, + this._flags, + this._version, + this._hostName, + this._ipAddress, + this._keyHash, + ]; + + try { + let offset = 0; + + for (const field of fields) { + field.set(v.slice(offset, offset + field.size())); + offset += field.size(); + } + } catch (error) { + const e = new Error( + `Error setting LoginRequest: ${(error as Error).message}`, + ); + e.cause = error; + throw e; + } + } + + override serialize(): Buffer { + return Buffer.concat([ + this._userInfo.get(), + this._customerNumber.get(), + this._flags.get(), + this._version.get(), + this._hostName.get(), + this._ipAddress.get(), + this._keyHash.get(), + ]); + } + + override size(): number { + return ( + this._userInfo.size() + + this._customerNumber.size() + + this._flags.size() + + this._version.size() + + this._hostName.size() + + this._ipAddress.size() + + this._keyHash.size() + ); + } + + override toString(): string { + return `LoginRequest(${this._userInfo}, ${this._customerNumber}, ${this._keyHash}, ${this._hostName}, ${this._ipAddress}, ${this._flags}, ${this._version})`; + } + + get customerNumber(): number { + return this._customerNumber.getBE(); + } + + get userInfo(): UserInfo { + return this._userInfo; + } +} diff --git a/libs/@rustymotors/roomserver/src/lib/MessageNumberMap.ts b/libs/@rustymotors/roomserver/src/lib/MessageNumberMap.ts new file mode 100644 index 000000000..e4c958a19 --- /dev/null +++ b/libs/@rustymotors/roomserver/src/lib/MessageNumberMap.ts @@ -0,0 +1,34 @@ + +export const MessageNumberMap: Record = { + 0x0100: "NPS_LOGIN", + 0x0101: "NPS_GET_USER_LIST", + 0x0106: "NPS_OPEN_COMM_CHANNEL", + 0x0120: "NPS_LOGIN_RESPONSE", + 0x1101: "NPS_ENCRYPTED_COMMAND", +}; + +export type MessageNumber = keyof typeof MessageNumberMap; + +export type MessageName = typeof MessageNumberMap[MessageNumber]; + +export function getMessageName(messageNumber: MessageNumber): string { + const messageName = MessageNumberMap[messageNumber]; + if (!messageName) { + throw new Error(`Unknown message number: ${messageNumber}`); + } + return messageName; +} + +/** + * Rturns the message number for the given message name + * @param messageName + * @returns {number | undefined} + */ +export function getMessageNumber(messageName: MessageName): number { + for (const [key, value] of Object.entries(MessageNumberMap)) { + if (value === messageName) { + return parseInt(key); + } + } + throw new Error(`Unknown message name: ${messageName}`); +} diff --git a/libs/@rustymotors/roomserver/src/lib/MiniUserInfo.ts b/libs/@rustymotors/roomserver/src/lib/MiniUserInfo.ts new file mode 100644 index 000000000..99dbb67cb --- /dev/null +++ b/libs/@rustymotors/roomserver/src/lib/MiniUserInfo.ts @@ -0,0 +1,12 @@ +import { BytableStructure } from "@rustymotors/binary"; + + +export class MiniUserInfo extends BytableStructure { + constructor() { + super(); + this.setSerializeOrder([ + { name: "userId", field: "Dword" }, + { name: "userName", field: "String" }, + ]); + } +} diff --git a/libs/@rustymotors/roomserver/src/lib/OpenCommChannelRequest.ts b/libs/@rustymotors/roomserver/src/lib/OpenCommChannelRequest.ts new file mode 100644 index 000000000..173930d63 --- /dev/null +++ b/libs/@rustymotors/roomserver/src/lib/OpenCommChannelRequest.ts @@ -0,0 +1,250 @@ +import { + BinaryMember, + CString, + Uint16_t, + Uint32_t, + Uint8_t, +} from "@rustymotors/binary"; + +export class ChannelData extends BinaryMember { + constructor() { + super(); + this.maxSize = 256; + this.value = Buffer.alloc(this.maxSize); + } +} + +export class CommData extends BinaryMember { + private commId = new Uint32_t(); + private riff = new CString(32); + private slotNumber = new Uint32_t(); + private slotFlags = new Uint32_t(); + private port = new Uint32_t(); + private protocol = new Uint32_t(); + private userId = new Uint32_t(); + private connectedUserCount = new Uint16_t(); + private openChnnelsCount = new Uint16_t(); + private userCanBeMadeReady = new Uint16_t(); + private userIsReady = new Uint16_t(); + private IsChannelOperator = new Uint16_t(); + private channeltype = new Uint16_t(); + private disableBacklog = new Uint8_t(); + private gamneServerIsRunning = new Uint8_t(); + private shouldLaunchGameServer = new Uint32_t(); + private maxReadyUsers = new Uint16_t(); + private sku = new Uint32_t(); + private sendRate = new Uint32_t(); + private channelData = new ChannelData(); + private flags = new Uint32_t(); + private messageOnListen = new Uint16_t(); + private isBeingRemoved = new Uint16_t(); + private numberOMessagesToSend = new Uint16_t(); + + constructor() { + super(); + } + + override serialize(): Buffer { + const buffer = Buffer.alloc(this.size()); + let offset = 0; + this.commId.serialize().copy(buffer, offset); + offset += this.commId.size(); + this.riff.serialize().copy(buffer, offset); + offset += this.riff.size(); + this.slotNumber.serialize().copy(buffer, offset); + offset += this.slotNumber.size(); + this.slotFlags.serialize().copy(buffer, offset); + offset += this.slotFlags.size(); + this.port.serialize().copy(buffer, offset); + offset += this.port.size(); + this.protocol.serialize().copy(buffer, offset); + offset += this.protocol.size(); + this.userId.serialize().copy(buffer, offset); + offset += this.userId.size(); + this.connectedUserCount.serialize().copy(buffer, offset); + offset += this.connectedUserCount.size(); + this.openChnnelsCount.serialize().copy(buffer, offset); + offset += this.openChnnelsCount.size(); + this.userCanBeMadeReady.serialize().copy(buffer, offset); + offset += this.userCanBeMadeReady.size(); + this.userIsReady.serialize().copy(buffer, offset); + offset += this.userIsReady.size(); + this.IsChannelOperator.serialize().copy(buffer, offset); + offset += this.IsChannelOperator.size(); + this.channeltype.serialize().copy(buffer, offset); + offset += this.channeltype.size(); + this.disableBacklog.serialize().copy(buffer, offset); + offset += this.disableBacklog.size(); + this.gamneServerIsRunning.serialize().copy(buffer, offset); + offset += this.gamneServerIsRunning.size(); + this.shouldLaunchGameServer.serialize().copy(buffer, offset); + offset += this.shouldLaunchGameServer.size(); + this.maxReadyUsers.serialize().copy(buffer, offset); + offset += this.maxReadyUsers.size(); + this.sku.serialize().copy(buffer, offset); + offset += this.sku.size(); + this.sendRate.serialize().copy(buffer, offset); + offset += this.sendRate.size(); + this.channelData.serialize().copy(buffer, offset); + offset += this.channelData.size(); + this.flags.serialize().copy(buffer, offset); + offset += this.flags.size(); + this.messageOnListen.serialize().copy(buffer, offset); + offset += this.messageOnListen.size(); + this.isBeingRemoved.serialize().copy(buffer, offset); + offset += this.isBeingRemoved.size(); + this.numberOMessagesToSend.serialize().copy(buffer, offset); + offset += this.numberOMessagesToSend.size(); + return buffer; + } + + override deserialize(buffer: Buffer): void { + let offset = 0; + this.commId.deserialize( + buffer.subarray(offset, offset + this.commId.size()), + ); + offset += this.commId.size(); + this.riff.deserialize(buffer.subarray(offset, offset + this.riff.size())); + offset += this.riff.size(); + this.slotNumber.deserialize( + buffer.subarray(offset, offset + this.slotNumber.size()), + ); + offset += this.slotNumber.size(); + this.slotFlags.deserialize( + buffer.subarray(offset, offset + this.slotFlags.size()), + ); + offset += this.slotFlags.size(); + this.port.deserialize(buffer.subarray(offset, offset + this.port.size())); + offset += this.port.size(); + this.protocol.deserialize( + buffer.subarray(offset, offset + this.protocol.size()), + ); + offset += this.protocol.size(); + this.userId.deserialize( + buffer.subarray(offset, offset + this.userId.size()), + ); + offset += this.userId.size(); + this.connectedUserCount.deserialize( + buffer.subarray(offset, offset + this.connectedUserCount.size()), + ); + offset += this.connectedUserCount.size(); + this.openChnnelsCount.deserialize( + buffer.subarray(offset, offset + this.openChnnelsCount.size()), + ); + offset += this.openChnnelsCount.size(); + this.userCanBeMadeReady.deserialize( + buffer.subarray(offset, offset + this.userCanBeMadeReady.size()), + ); + offset += this.userCanBeMadeReady.size(); + this.userIsReady.deserialize( + buffer.subarray(offset, offset + this.userIsReady.size()), + ); + offset += this.userIsReady.size(); + this.IsChannelOperator.deserialize( + buffer.subarray(offset, offset + this.IsChannelOperator.size()), + ); + offset += this.IsChannelOperator.size(); + this.channeltype.deserialize( + buffer.subarray(offset, offset + this.channeltype.size()), + ); + offset += this.channeltype.size(); + this.disableBacklog.deserialize( + buffer.subarray(offset, offset + this.disableBacklog.size()), + ); + offset += this.disableBacklog.size(); + this.gamneServerIsRunning.deserialize( + buffer.subarray(offset, offset + this.gamneServerIsRunning.size()), + ); + offset += this.gamneServerIsRunning.size(); + this.shouldLaunchGameServer.deserialize( + buffer.subarray(offset, offset + this.shouldLaunchGameServer.size()), + ); + offset += this.shouldLaunchGameServer.size(); + this.maxReadyUsers.deserialize( + buffer.subarray(offset, offset + this.maxReadyUsers.size()), + ); + offset += this.maxReadyUsers.size(); + this.sku.deserialize(buffer.subarray(offset, offset + this.sku.size())); + offset += this.sku.size(); + this.sendRate.deserialize( + buffer.subarray(offset, offset + this.sendRate.size()), + ); + offset += this.sendRate.size(); + this.channelData.deserialize( + buffer.subarray(offset, offset + this.channelData.size()), + ); + offset += this.channelData.size(); + this.flags.deserialize(buffer.subarray(offset, offset + this.flags.size())); + offset += this.flags.size(); + this.messageOnListen.deserialize( + buffer.subarray(offset, offset + this.messageOnListen.size()), + ); + offset += this.messageOnListen.size(); + this.isBeingRemoved.deserialize( + buffer.subarray(offset, offset + this.isBeingRemoved.size()), + ); + offset += this.isBeingRemoved.size(); + this.numberOMessagesToSend.deserialize( + buffer.subarray(offset, offset + this.numberOMessagesToSend.size()), + ); + offset += this.numberOMessagesToSend.size(); + } + + override size(): number { + return ( + this.commId.size() + + this.riff.size() + + this.slotNumber.size() + + this.slotFlags.size() + + this.port.size() + + this.protocol.size() + + this.userId.size() + + this.connectedUserCount.size() + + this.openChnnelsCount.size() + + this.userCanBeMadeReady.size() + + this.userIsReady.size() + + this.IsChannelOperator.size() + + this.channeltype.size() + + this.disableBacklog.size() + + this.gamneServerIsRunning.size() + + this.shouldLaunchGameServer.size() + + this.maxReadyUsers.size() + + this.sku.size() + + this.sendRate.size() + + this.channelData.size() + + this.flags.size() + + this.messageOnListen.size() + + this.isBeingRemoved.size() + + this.numberOMessagesToSend.size() + ); + } + + override toString(): string { + return `CommData { + commId: ${this.commId.toString()}, + riff: ${this.riff.toString()}, + slotNumber: ${this.slotNumber.toString()}, + slotFlags: ${this.slotFlags.toString()}, + port: ${this.port.toString()}, + protocol: ${this.protocol.toString()}, + userId: ${this.userId.toString()}, + connectedUserCount: ${this.connectedUserCount.toString()}, + openChnnelsCount: ${this.openChnnelsCount.toString()}, + userCanBeMadeReady: ${this.userCanBeMadeReady.toString()}, + userIsReady: ${this.userIsReady.toString()}, + IsChannelOperator: ${this.IsChannelOperator.toString()}, + channeltype: ${this.channeltype.toString()}, + disableBacklog: ${this.disableBacklog.toString()}, + gamneServerIsRunning: ${this.gamneServerIsRunning.toString()}, + shouldLaunchGameServer: ${this.shouldLaunchGameServer.toString()}, + maxReadyUsers: ${this.maxReadyUsers.toString()}, + sku: ${this.sku.toString()}, + sendRate: ${this.sendRate.toString()}, + channelData: ${this.channelData.toString()}, + flags: ${this.flags.toString()}, + messageOnListen: ${this.messageOnListen.toString()}, + isBeingRemoved: ${this.isBeingRemoved.toString()}, + numberOMessagesToSend: ${this.numberOMessagesToSend.toString()} + }`; + } +} diff --git a/libs/@rustymotors/roomserver/src/lib/Room.ts b/libs/@rustymotors/roomserver/src/lib/Room.ts new file mode 100644 index 000000000..2e1334041 --- /dev/null +++ b/libs/@rustymotors/roomserver/src/lib/Room.ts @@ -0,0 +1,2 @@ + +export class Room { } diff --git a/libs/@rustymotors/roomserver/src/lib/RoomServer.ts b/libs/@rustymotors/roomserver/src/lib/RoomServer.ts new file mode 100644 index 000000000..5b770eebf --- /dev/null +++ b/libs/@rustymotors/roomserver/src/lib/RoomServer.ts @@ -0,0 +1,484 @@ +import { BytableMessage, createRawMessage } from "@rustymotors/binary"; +import { databaseManager } from "rusty-motors-database"; +import { + createCommandEncryptionPair, + createDataEncryptionPair, +} from "rusty-motors-gateway"; +import { + addEncryption, + fetchStateFromDatabase, + getEncryption, + getServerLogger, + McosEncryption, + ServiceResponse, + updateEncryption, + type ServerLogger, +} from "rusty-motors-shared"; +import { getMessageNumber, MessageNumberMap } from "./MessageNumberMap.js"; +import { LoginRequest } from "./LoginRequest.js"; +import { UserData } from "./UserData.js"; +import { handleGetMiniUserList } from "./handleGetMiniUserList.js"; +import { handleSendMiniRiffList } from "./handleSendMiniRiffList.js"; +import { _setMyUserData } from "./_setMyUserData.js"; +import { _handleCommChannelOpen } from "./_openCommChannel.js"; +import { _handleGetUserList} from "./_getUserList.js" + +export type NpsCommandHandler = { + opCode: number; + name: string; + handler: (args: { + connectionId: string; + message: BytableMessage; + log?: ServerLogger; + }) => Promise<{ + connectionId: string; + message: BytableMessage | null; + }>; +}; + +const npsCommandHandlers: NpsCommandHandler[] = [ + { + opCode: 0x128, // 296 + name: "NPS_GET_MINI_USER_LIST", + handler: handleGetMiniUserList, + }, + { + opCode: 0x30c, // 780 + name: "NPS_SEND_MINI_RIFF_LIST", + handler: handleSendMiniRiffList, + }, + { + opCode: 0x101, // 257 + name: "NPS_GET_USER_LIST", + handler: _handleGetUserList, + }, + { + opCode: 0x103, // 259 + name: "NPS_SET_MY_USER_DATA", + handler: _setMyUserData, + }, + { + opCode: 0x106, + name: "NPS_OPEN_COMM_CHANNEL", + handler: _handleCommChannelOpen, + }, +]; + +async function handleCommand({ + connectionId, + message, + log, +}: { + connectionId: string; + message: BytableMessage; + log: ServerLogger; +}): Promise<{ + connectionId: string; + message: BytableMessage | null; +}> { + log.debug( + { + connectionId, + command: message.serialize().toString("hex").slice(0, 4), + }, + "Handling command", + ); + + const command = message.header.messageId; + + // What is the command? + log.debug( + { + connectionId, + command: MessageNumberMap[command], + }, + `Received command: ${MessageNumberMap[command]}(${command})`, + ); + + const handler = npsCommandHandlers.find((h) => h.opCode === command); + + if (typeof handler === "undefined") { + throw Error(`Unknown command: ${command}`); + } + + const { message: response } = await handler.handler({ + connectionId, + message, + log: log.child({ connectionId, loggerName: handler.name }), + }); + + if (response !== null) { + log.debug( + { + loggerName: handler.name, + connectionId, + }, + `[${connectionId}] Sending response: ${response.serialize().toString("hex")}`, + ); + } + + return { + connectionId, + message: response, + }; +} + +/** + * Takes an plaintext command packet and return the encrypted bytes + * + * @param {object} args + * @param {string} args.connectionId + * @param {LegacyMessage | MessageBuffer} args.message + * @param {ServerLogger} [args.log] Logger + * @returns {Promise<{ + * connectionId: string, + * message: LegacyMessage | MessageBuffer, + * }>} + */ +async function encryptCmd({ + connectionId, + message, + log = getServerLogger("lobby.encryptCmd"), +}: { + connectionId: string; + message: BytableMessage; + log?: ServerLogger; +}): Promise<{ + connectionId: string; + message: BytableMessage; +}> { + log.debug(`[ciphering Cmd: ${message.serialize().toString("hex")}`); + const state = fetchStateFromDatabase(); + + const encryption = getEncryption(state, connectionId); + + if (typeof encryption === "undefined") { + throw Error( + `Unable to locate encryption session for connection id ${connectionId}`, + ); + } + + let precriptedMessage = message.serialize(); + + log.debug(`[precripted Cmd: ${precriptedMessage.toString("hex")}`); + if (precriptedMessage.length % 8 !== 0) { + const padding = Buffer.alloc(8 - (precriptedMessage.length % 8)); + precriptedMessage = Buffer.concat([precriptedMessage, padding]); + log.debug(`[padded Cmd: ${precriptedMessage.toString("hex")}`); + } + + const result = encryption.commandEncryption.encrypt(precriptedMessage); + updateEncryption(state, encryption).save(); + + log.debug(`[ciphered Cmd: ${result.toString("hex")}`); + + const encryptedMessage = createRawMessage(); + encryptedMessage.header.setMessageId(0x1101); + encryptedMessage.setBody(result); + + log.debug( + `[ciphered message: ${encryptedMessage.serialize().toString("hex")}`, + ); + + return { + connectionId, + message: encryptedMessage, + }; +} + +/** + * Takes an encrypted command packet and returns the decrypted bytes + * + * @param {object} args + * @param {string} args.connectionId + * @param {LegacyMessage} args.message + * @param {ServerLogger} [args.log=getServerLogger({ name: "Lobby" })] + * @returns {Promise<{ + * connectionId: string, + * message: LegacyMessage, + * }>} + */ +async function decryptCmd({ + connectionId, + message, + log = getServerLogger("lobby.decryptCmd"), +}: { + connectionId: string; + message: BytableMessage; + log?: ServerLogger; +}): Promise<{ + connectionId: string; + message: BytableMessage; +}> { + const state = fetchStateFromDatabase(); + + const encryption = getEncryption(state, connectionId); + + if (typeof encryption === "undefined") { + throw Error( + `Unable to locate encryption session for connection id ${connectionId}`, + ); + } + + const result = encryption.commandEncryption.decrypt(message.getBody()); + + updateEncryption(state, encryption).save(); + + log.debug(`[Deciphered Cmd: ${result.toString("hex")}`); + + const decipheredMessage = createRawMessage(result); + + return { + connectionId, + message: decipheredMessage, + }; +} + +export async function handleEncryptedNPSCommand({ + connectionId, + message, + log = getServerLogger("lobby.handleEncryptedNPSCommand"), +}: { + connectionId: string; + message: BytableMessage; + log?: ServerLogger; +}): Promise<{ + connectionId: string; + messages: BytableMessage[]; +}> { + log.debug(`[${connectionId}] Handling encrypted NPS command`); + log.debug( + `[${connectionId}] Received command: ${message.serialize().toString("hex")}`, + ); + + // Decipher + const decipheredMessage = await decryptCmd({ + connectionId, + message, + log: log.child({ connectionId, loggerName: "decryptCmd" }), + }); + + log.debug( + `[${connectionId}] Deciphered message: ${decipheredMessage.message.serialize().toString("hex")}`, + ); + + const response = await handleCommand({ + connectionId, + message: decipheredMessage.message, + log: log.child({ connectionId, loggerName: "handleCommand" }), + }); + + if (response.message === null) { + log.debug(`[${connectionId}] No response to send`); + return { + connectionId, + messages: [], + }; + } + + log.debug( + `[${connectionId}] Sending response: ${response.message.serialize().toString("hex")}`, + ); + + // Encipher + const result = await encryptCmd({ + connectionId, + message: response.message, + log: log.child({ connectionId, loggerName: "encryptCmd" }), + }); + + const encryptedResponse = result.message; + + log.debug( + `[${connectionId}] Enciphered response: ${encryptedResponse.serialize().toString("hex")}`, + ); + + return { + connectionId, + messages: [encryptedResponse], + }; +} + +export class RoomServer { + private _id: number; + private _name: string; + private _ip: string; + private _port: number; + private log: ServerLogger; + + private usersData = new Map(); + + constructor({ + id, + name, + ip, + port, + }: { id: number; name: string; ip: string; port: number }) { + this._name = name; + this._id = id; + this.log = getServerLogger(name, "roomserver"); + this._ip = ip; + this._port = port; + } + + async receivePacket({ + connectionId, + packet, + }: { + connectionId: string; + packet: BytableMessage; + }): Promise { + this.log.debug( + { connectionId, packet: packet.toHexString() }, + "Received packet", + ); + + const messageNumber = packet.header.messageId; + const messageName = MessageNumberMap[messageNumber]; + + if (!messageName) { + this.log.warn({ connectionId, messageNumber }, "Unknown message number"); + return { + connectionId, + messages: [], + }; + } + + this.log.debug({ connectionId, messageName }, "Handling message"); + + switch (messageName) { + case "NPS_LOGIN": + return this.handleLogin({ connectionId, packet }); + case "NPS_ENCRYPTED_COMMAND": + return handleEncryptedNPSCommand({ + connectionId, + message: packet, + log: this.log.child({ + connectionId, + loggerName: "handleEncryptedNPSCommand", + }), + }); + default: { + this.log.warn({ connectionId, messageName }, "Unknown message name"); + return { + connectionId, + messages: [], + }; + } + } + } + private async handleLogin({ + connectionId, + packet, + }: { + connectionId: string; + packet: BytableMessage; + }): Promise { + const log = this.log.child({ + connectionId, + loggerName: "handlers/_npsRequestGameConnectServer", + }); + + try { + log.debug( + { connectionId, packet: packet.toHexString() }, + "Handling NPS_LOGIN", + ); + + const getServerInfoRequest = new LoginRequest(); + getServerInfoRequest.deserialize(packet.getBody()); + + const customerId = getServerInfoRequest.customerNumber; + const userId = getServerInfoRequest.userInfo.userId; + + log.debug( + { connectionId, customerId: customerId, userId: userId }, + "Connecting to game server", + ); + + const state = fetchStateFromDatabase(); + + const existingEncryption = getEncryption(state, connectionId); + + if (!existingEncryption) { + // Set the encryption keys on the lobby connection + const keys = + await databaseManager.fetchSessionKeyByCustomerId(customerId); + + if (keys === undefined) { + throw Error("Error fetching session keys!"); + } + + // We have the session keys, set them on the connection + const newCommandEncryptionPair = createCommandEncryptionPair( + keys.sessionKey, + ); + + const newDataEncryptionPair = createDataEncryptionPair(keys.sessionKey); + + const newEncryption = new McosEncryption({ + connectionId, + commandEncryptionPair: newCommandEncryptionPair, + dataEncryptionPair: newDataEncryptionPair, + }); + + addEncryption(state, newEncryption).save(); + } + + const userInfo = getServerInfoRequest.userInfo; + + const userData = userInfo.userData; + + this.usersData.set(userId, userData); + + const response = new BytableMessage(); + response.header.setMessageId(getMessageNumber("NPS_LOGIN_RESPONSE")); + response.header.setMessageVersion(0); + response.setSerializeOrder([ + { name: "userId", field: "Dword" }, + { name: "userName", field: "Container" }, + { name: "userData", field: "Buffer" }, + ]); + + response.setFieldValueByName("userId", userInfo.userId); + response.setFieldValueByName("userName", userInfo.userName); + response.setFieldValueByName("userData", Buffer.from(userData.get())); + + log.debug( + { connectionId, response: response.toHexString() }, + "Sending NPS_LOGIN_RESPONSE", + ); + + return { + connectionId, + messages: [response], + }; + } catch (error: any) { + log.error( + { connectionId, error }, + `Error handling NPS_LOGIN: ${(error as Error).message}`, + ); + return { + connectionId, + messages: [], + }; + } + } + + get id() { + return this._id; + } + + get name() { + return this._name; + } + + get ip() { + return this._ip; + } + + get port() { + return this._port; + } +} + diff --git a/libs/@rustymotors/roomserver/src/lib/UserCar.ts b/libs/@rustymotors/roomserver/src/lib/UserCar.ts new file mode 100644 index 000000000..982039bd8 --- /dev/null +++ b/libs/@rustymotors/roomserver/src/lib/UserCar.ts @@ -0,0 +1,108 @@ +import { BinaryMember, Uint32_t } from "@rustymotors/binary"; +import { CarDecal } from "./CarDecal.js"; + +export class UserCar extends BinaryMember { + private _carId: Uint32_t = new Uint32_t(); + private _brandedPartId: Uint32_t = new Uint32_t(); + private _skinId: Uint32_t = new Uint32_t(); + private _driverModelType: Uint32_t = new Uint32_t(); + private _driverSkinColor: Uint32_t = new Uint32_t(); + private _driverHairColor: Uint32_t = new Uint32_t(); + private _driverShirtColor: Uint32_t = new Uint32_t(); + private _driverPantsColor: Uint32_t = new Uint32_t(); + private _flags: Uint32_t = new Uint32_t(); + private _skinFlags: Uint32_t = new Uint32_t(); + private _decal: CarDecal = new CarDecal(); + + override set(v: Uint8Array): void { + if (v.length > this.size()) { + throw new Error( + `UserCar must be ${this.size()} bytes long, got ${v.length}`, + ); + } + + if (v.length !== this.size()) { + console.warn( + "UserCar message smaller than expected size", + "expectedSize", + this.size(), + "actualSize", + v.length, + ); + } + + let value = v; + + try { + let offset = 0; + value = v.slice(offset, offset + this._carId.size()); + this._carId.set(value); + offset += this._carId.size(); + value = v.slice(offset, offset + this._brandedPartId.size()); + this._brandedPartId.set(value); + offset += this._brandedPartId.size(); + value = v.slice(offset, offset + this._skinId.size()); + this._skinId.set(value); + offset += this._skinId.size(); + value = v.slice(offset, offset + this._driverModelType.size()); + this._driverModelType.set(value); + offset += this._driverModelType.size(); + value = v.slice(offset, offset + this._driverSkinColor.size()); + this._driverSkinColor.set(value); + offset += this._driverSkinColor.size(); + value = v.slice(offset, offset + this._driverHairColor.size()); + this._driverHairColor.set(value); + offset += this._driverHairColor.size(); + value = v.slice(offset, offset + this._driverShirtColor.size()); + this._driverShirtColor.set(value); + offset += this._driverShirtColor.size(); + value = v.slice(offset, offset + this._driverPantsColor.size()); + this._driverPantsColor.set(value); + offset += this._driverPantsColor.size(); + value = v.slice(offset, offset + this._flags.size()); + this._flags.set(value); + offset += this._flags.size(); + value = v.slice(offset, offset + this._skinFlags.size()); + this._skinFlags.set(value); + offset += this._skinFlags.size(); + value = v.slice(offset, offset + this._decal.size()); + this._decal.set(value); + } catch (error) { + const e = new Error(`Error setting UserCar: ${(error as Error).message}`); + e.cause = error; + throw e; + } + } + + override get(): Uint8Array { + return new Uint8Array([ + ...this._carId.get(), + ...this._brandedPartId.get(), + ...this._skinId.get(), + ...this._driverModelType.get(), + ...this._driverSkinColor.get(), + ...this._driverHairColor.get(), + ...this._driverShirtColor.get(), + ...this._driverPantsColor.get(), + ...this._flags.get(), + ...this._skinFlags.get(), + ...this._decal.get(), + ]); + } + + override size(): number { + return ( + this._carId.size() + + this._brandedPartId.size() + + this._skinId.size() + + this._driverModelType.size() + + this._driverSkinColor.size() + + this._driverHairColor.size() + + this._driverShirtColor.size() + + this._driverPantsColor.size() + + this._flags.size() + + this._skinFlags.size() + + this._decal.size() + ); + } +} diff --git a/libs/@rustymotors/roomserver/src/lib/UserData.ts b/libs/@rustymotors/roomserver/src/lib/UserData.ts new file mode 100644 index 000000000..9bb66ee30 --- /dev/null +++ b/libs/@rustymotors/roomserver/src/lib/UserData.ts @@ -0,0 +1,106 @@ +import { BinaryMember, Uint32_t, Uint8_t, Uint16_t } from "@rustymotors/binary"; +import { UserCar } from "./UserCar.js"; + +export class UserData extends BinaryMember { + private _carId: UserCar = new UserCar(); + private _lobbyId: Uint32_t = new Uint32_t(); + private _clubId: Uint32_t = new Uint32_t(); + private _isInLobby: Uint8_t = new Uint8_t(false); + private _isInTransit: Uint8_t = new Uint8_t(false); + private _isInRace: Uint8_t = new Uint8_t(false); + private _isValid: Uint8_t = new Uint8_t(false); + private _performance: Uint32_t = new Uint32_t(); + private _points: Uint16_t = new Uint16_t(); + private _level: Uint16_t = new Uint16_t(); + + override set(v: Uint8Array): void { + if (v.length > this.size()) { + throw new Error( + `UserData must be ${this.size()} bytes long, got ${v.length}`, + ); + } + + if (v.length !== this.size()) { + console.warn( + "UserData message smaller than expected size", + "expectedSize", + this.size(), + "actualSize", + v.length, + ); + } + + let value = v; + + try { + let offset = 0; + value = v.slice(offset, offset + this._carId.size()); + this._carId.set(value); + offset += this._carId.size(); + value = v.slice(offset, offset + this._lobbyId.size()); + this._lobbyId.set(value); + offset += this._lobbyId.size(); + value = v.slice(offset, offset + this._clubId.size()); + this._clubId.set(value); + offset += this._clubId.size(); + value = v.slice(offset, offset + this._isInLobby.size()); + this._isInLobby.set(value); + offset += this._isInLobby.size(); + value = v.slice(offset, offset + this._isInTransit.size()); + this._isInTransit.set(value); + offset += this._isInTransit.size(); + value = v.slice(offset, offset + this._isInRace.size()); + this._isInRace.set(value); + offset += this._isInRace.size(); + value = v.slice(offset, offset + this._isValid.size()); + this._isValid.set(value); + offset += this._isValid.size(); + value = v.slice(offset, offset + this._performance.size()); + this._performance.set(value); + offset += this._performance.size(); + value = v.slice(offset, offset + this._points.size()); + this._points.set(value); + offset += this._points.size(); + value = v.slice(offset, offset + this._level.size()); + this._level.set(value); + } catch (error) { + const e = new Error(`Error setting UserData: ${(error as Error).message}`); + e.cause = error; + throw e; + } + } + + override get(): Uint8Array { + return new Uint8Array([ + ...this._carId.get(), + ...this._lobbyId.get(), + ...this._clubId.get(), + ...this._isInLobby.get(), + ...this._isInTransit.get(), + ...this._isInRace.get(), + ...this._isValid.get(), + ...this._performance.get(), + ...this._points.get(), + ...this._level.get(), + ]); + } + + override size(): number { + return ( + this._carId.size() + + this._lobbyId.size() + + this._clubId.size() + + this._isInLobby.size() + + this._isInTransit.size() + + this._isInRace.size() + + this._isValid.size() + + this._performance.size() + + this._points.size() + + this._level.size() + ); + } + + get lobbyId(): number { + return this._lobbyId.getBE(); + } +} diff --git a/libs/@rustymotors/roomserver/src/lib/UserInfo.ts b/libs/@rustymotors/roomserver/src/lib/UserInfo.ts new file mode 100644 index 000000000..c4aceef93 --- /dev/null +++ b/libs/@rustymotors/roomserver/src/lib/UserInfo.ts @@ -0,0 +1,97 @@ +import { + BinaryMember, + Uint32_t, + CString, +} from "@rustymotors/binary"; +import { UserData } from "./UserData.js"; + +export class UserInfo extends BinaryMember { + private _userId: Uint32_t = new Uint32_t(); + private _userName: CString = new CString(32); + private _userData: UserData = new UserData(); + + override set(v: Uint8Array): void { + if (v.length > this.size()) { + throw new Error( + `UserInfo must be ${this.size()} bytes long, got ${v.length}`, + ); + } + + if (v.length !== this.size()) { + console.warn( + "UserInfo message smaller than expected size", + "expectedSize", + this.size(), + "actualSize", + v.length, + ); + } + + let value = v; + + try { + let offset = 0; + value = v.slice(offset, offset + this._userId.size()); + this._userId.set(value); + offset += this._userId.size(); + value = v.slice(offset, offset + this._userName.size()); + this._userName.set(value); + offset += this._userName.size(); + value = v.slice(offset, offset + this._userData.size()); + this._userData.set(value); + } catch (error) { + const e = new Error(`Error setting UserInfo: ${(error as Error).message}`); + e.cause = error; + throw e; + } + } + + swapBytes(v: Uint8Array): Uint8Array { + const byte0 = v[0] || 0; + const byte1 = v[1] || 0; + const byte2 = v[2] || 0; + const byte3 = v[3] || 0; + v[0] = byte3; + v[1] = byte2; + v[2] = byte1; + v[3] = byte0; + return v; + } + + override get(): Uint8Array { + + return new Uint8Array([ + ...this._userId.get(), + ...this._userName.get(), + ...this._userData.get(), + ]); + } + + override size(): number { + return this._userId.size() + this._userName.size() + this._userData.size(); + } + + get userId(): number { + return this._userId.getInt("BE"); + } + + get userName(): string { + return this._userName.toString(); + } + + get userData(): UserData { + return this._userData; + } + + set userId(value: number) { + this._userId.setInt(value, "BE"); + } + + set userName(value: CString) { + this._userName = value; + } + + set userData(value: UserData) { + this._userData = value; + } +} diff --git a/libs/@rustymotors/roomserver/src/lib/_getUserList.ts b/libs/@rustymotors/roomserver/src/lib/_getUserList.ts new file mode 100644 index 000000000..7fad7aef0 --- /dev/null +++ b/libs/@rustymotors/roomserver/src/lib/_getUserList.ts @@ -0,0 +1,55 @@ +import { BytableMessage, Uint16_t } from "@rustymotors/binary"; +import { getServerLogger, LegacyMessage, type ServerLogger } from "rusty-motors-shared"; + +export async function _handleGetUserList({ + connectionId, + message, + log = getServerLogger("lobby._getUserList"), +}: { + connectionId: string; + message: BytableMessage; + log?: ServerLogger; +}) { + log.debug( + { connectionId, message: message.toHexString() }, + "Handling NPS_GET_USER_LIST", + ); + + // 0101000800000002 + + const requestPayload = new Uint16_t(); + requestPayload.deserialize(message.getBody()); + + const requestedCommId = requestPayload.getBE(); + + log.debug( + { connectionId, requestedCommId }, + "Requested Comm ID", + ); + + const serverPort = 9001; + + const channelGrantedData = Buffer.alloc(8); + channelGrantedData.writeUInt32BE(requestedCommId, 0); // commId + channelGrantedData.writeUInt32BE(serverPort, 4); // userCount + + const response = new LegacyMessage(); + response.setMessageId(0x214); // COMM_GRANTED + response.setBuffer(channelGrantedData); + const packetResult = new BytableMessage(); + packetResult.setSerializeOrder([ + { name: "data", field: "Buffer" }, + ]); + packetResult.deserialize(response.serialize()); + log.debug({ + connectionId, + packetResult: packetResult.toHexString(), + }, + "COMM_GRANTED", + ); + + return { + connectionId, + message: packetResult, + }; +} diff --git a/libs/@rustymotors/roomserver/src/lib/_openCommChannel.ts b/libs/@rustymotors/roomserver/src/lib/_openCommChannel.ts new file mode 100644 index 000000000..1f47621b1 --- /dev/null +++ b/libs/@rustymotors/roomserver/src/lib/_openCommChannel.ts @@ -0,0 +1,48 @@ +import { BytableMessage } from "@rustymotors/binary"; +import { getServerLogger, LegacyMessage, type ServerLogger } from "rusty-motors-shared"; +import { CommData } from "./OpenCommChannelRequest.js"; + +export async function _handleCommChannelOpen({ + connectionId, + message, + log = getServerLogger("lobby._openCommChannel"), +}: { + connectionId: string; + message: BytableMessage; + log?: ServerLogger; +}) { + log.debug( + { connectionId, message: message.toHexString() }, + "Handling NPS_OPEN_COMM_CHANNEL", + ); + + const request = new CommData(); + request.deserialize(message.getBody()); + + const requestedCommId = 2; + const serverPort = 9001; + + const channelGrantedData = Buffer.alloc(8); + channelGrantedData.writeUInt32BE(requestedCommId, 0); // commId + channelGrantedData.writeUInt32BE(serverPort, 4); // userCount + + const response = new LegacyMessage(); + response.setMessageId(0x214); // COMM_GRANTED + response.setBuffer(channelGrantedData); + const packetResult = new BytableMessage(); + packetResult.setSerializeOrder([ + { name: "data", field: "Buffer" }, + ]); + packetResult.deserialize(response.serialize()); + log.debug({ + connectionId, + packetResult: packetResult.toHexString(), + }, + "COMM_GRANTED", + ); + + return { + connectionId, + message: packetResult, + }; +} diff --git a/libs/@rustymotors/roomserver/src/lib/_setMyUserData.ts b/libs/@rustymotors/roomserver/src/lib/_setMyUserData.ts new file mode 100644 index 000000000..0dce051f9 --- /dev/null +++ b/libs/@rustymotors/roomserver/src/lib/_setMyUserData.ts @@ -0,0 +1,69 @@ +import { LegacyMessage } from "rusty-motors-shared"; +import { databaseManager } from "rusty-motors-database"; +import { ServerLogger, getServerLogger } from "rusty-motors-shared"; +import { BytableMessage } from "@rustymotors/binary"; +import { UserInfo } from "./UserInfo.js"; +import { UserData } from "./UserData.js"; + +const NPS_USER_INFO = 0x204; // 516 +const NPS_CHANNEL_GRANTED = 0x214; // 532 + +export async function _setMyUserData({ + connectionId, + message, + log = getServerLogger("lobby._setMyUserData"), +}: { + connectionId: string; + message: BytableMessage; + log?: ServerLogger; +}) { + try { + log.debug({ + connectionId, + payload: message.serialize().toString("hex"), + }, `Handling NPS_SET_MY_USER_DATA`); + + const incomingMessage = new UserInfo(); + incomingMessage.deserialize(message.serialize()); + + log.debug(`User ID: ${incomingMessage.userId}`); + + // Update the user's data + databaseManager.updateUser({ + userId: incomingMessage.userId, + userData: incomingMessage.userData.serialize(), + }); + + const userData = new UserData(); + userData.deserialize(incomingMessage.userData.serialize()); + + log.debug(`User data: ${userData.toString()}`); + + const currentChannel = userData.lobbyId; + + // Build the packet + const packetResult = new LegacyMessage(); + // packetResult._header.id = NPS_USER_INFO; + packetResult._header.id = NPS_CHANNEL_GRANTED; + + const channelBuffer = Buffer.alloc(4); + channelBuffer.writeInt32BE(currentChannel); + + const response = Buffer.concat([channelBuffer, Buffer.from([0, 0, 0, 0])]); + + packetResult.setBuffer(response); + + // packetResult.deserialize(incomingMessage.serialize()); + + message.header.setMessageId(NPS_USER_INFO) + + return { + connectionId, + message: null, + }; + } catch (error) { + const err = Error(`[${connectionId}] Error handling NPS_SET_MY_USER_DATA: ${String(error)}`); + err.cause = error; + throw err; + } +} diff --git a/libs/@rustymotors/roomserver/src/lib/channels.ts b/libs/@rustymotors/roomserver/src/lib/channels.ts new file mode 100644 index 000000000..c315cdf70 --- /dev/null +++ b/libs/@rustymotors/roomserver/src/lib/channels.ts @@ -0,0 +1,120 @@ +export const channelRecordSize = 40; + +export const channels = [ + { + id: 30, + name: "CTRL", + population: 5, + }, + { + id: 0, + name: "LOBBY", + population: 4, + }, + { + id: 191, + name: "MCCHAT", + population: 6, + }, + { + id: 2, + name: "LOBBY", + population: 15, + }, + { + id: 224, + name: "MCC01", + population: 1, + }, + { + id: 225, + name: "MCC02", + population: 1, + }, + { + id: 226, + name: "MCC03", + population: 1, + }, + { + id: 227, + name: "MCC04", + population: 1, + }, + { + id: 228, + name: "MCC05", + population: 1, + }, + { + id: 229, + name: "MCC06", + population: 1, + }, + { + id: 230, + name: "MCC07", + population: 1, + }, + { + id: 231, + name: "MCC08", + population: 1, + }, + { + id: 232, + name: "MCC09", + population: 1, + }, + { + id: 233, + name: "MCC10", + population: 1, + }, + { + id: 234, + name: "MCC11", + population: 1, + }, + { + id: 235, + name: "MCC12", + population: 1, + }, + { + id: 236, + name: "MCC13", + population: 1, + }, + { + id: 237, + name: "MCC14", + population: 1, + }, + { + id: 238, + name: "MCC15", + population: 1, + }, + { + id: 239, + name: "MCC16", + population: 1, + }, + { + id: 240, + name: "MCC17", + population: 1, + }, + { + id: 241, + name: "MCC18", + population: 1, + }, + { + id: 242, + name: "MCC19", + population: 1, + } + +]; diff --git a/libs/@rustymotors/roomserver/src/lib/handleGetMiniUserList.ts b/libs/@rustymotors/roomserver/src/lib/handleGetMiniUserList.ts new file mode 100644 index 000000000..239c76b1b --- /dev/null +++ b/libs/@rustymotors/roomserver/src/lib/handleGetMiniUserList.ts @@ -0,0 +1,76 @@ +import { ServerLogger } from "rusty-motors-shared"; +import { LegacyMessage } from "rusty-motors-shared"; +import { MiniUserInfo } from "./MiniUserInfo.js"; +import { getServerLogger } from "rusty-motors-shared"; +import { BytableMessage } from "@rustymotors/binary"; + + +export async function handleGetMiniUserList({ + connectionId, + message, + log = getServerLogger("lobby.handleGetMiniUserList"), +}: { + connectionId: string; + message: BytableMessage; + log?: ServerLogger; +}): Promise<{ + connectionId: string; + message: BytableMessage; +}> { + try { + log.debug({ + connectionId, + payload: message.serialize().toString("hex"), + }, "Handling NPS_GET_MINI_USER_LIST"); + + const requestedCommId = message.getBody().readUInt32BE(0); + + log.debug(`[${connectionId}] Requested commId: ${requestedCommId}`); + + const userCount = 1; + + const channelCountRecord = Buffer.alloc(8); + channelCountRecord.writeUInt32BE(requestedCommId, 0); // commId + channelCountRecord.writeUInt32BE(userCount, 4); // userCount + + const user1 = new MiniUserInfo(); + user1.setFieldValueByName("userId", 21); + user1.setFieldValueByName("userName", "Dr Brown"); + + log.debug(`[${connectionId}] User1: ${user1.toString()}`); + + const realData = Buffer.concat([ + channelCountRecord, + user1.serialize(), + ]); + + const align8Padding = 8 - (realData.length % 8); + + const padding = Buffer.alloc(align8Padding); + + const packetContent = Buffer.concat + ([realData, padding]); + + const outgoingMessage = new LegacyMessage(); + outgoingMessage.setMessageId(553); // NPS_MINI_USER_LIST - 0x229 + outgoingMessage.setBuffer(packetContent); + + const packetResult = new BytableMessage(); + packetResult.setSerializeOrder([ + { name: "data", field: "Buffer" }, + ]); + packetResult.deserialize(outgoingMessage.serialize()); + + log.debug(`[${connectionId}] Sending NPS_MINI_USER_LIST`); + log.debug(`[${connectionId}] Sending response: ${packetResult.serialize().toString("hex")}`); + + return { + connectionId, + message: packetResult, + }; + } catch (error) { + const err = Error(`Error handling NPS_MINI_USER_LIST: ${String(error)}`); + err.cause = error; + throw err; + } +} diff --git a/libs/@rustymotors/roomserver/src/lib/handleSendMiniRiffList.ts b/libs/@rustymotors/roomserver/src/lib/handleSendMiniRiffList.ts new file mode 100644 index 000000000..ff6984ed5 --- /dev/null +++ b/libs/@rustymotors/roomserver/src/lib/handleSendMiniRiffList.ts @@ -0,0 +1,66 @@ +import { LegacyMessage, ServerLogger, } from "rusty-motors-shared"; +import { serializeString } from "rusty-motors-shared"; +import { channelRecordSize, channels } from "./channels.js"; +import { getServerLogger } from "rusty-motors-shared"; +import { BytableMessage } from "@rustymotors/binary"; + +export async function handleSendMiniRiffList({ + connectionId, + message, + log = getServerLogger("lobby.handleSendMiniRiffList"), +}: { + connectionId: string; + message: BytableMessage; + log?: ServerLogger; +}): Promise<{ + connectionId: string; + message: BytableMessage; +}> { + log.debug("[${connectionId}] Handling NPS_SEND_MINI_RIFF_LIST"); + log.debug(`[${connectionId}] Received command: ${message.serialize().toString("hex")}`); + + const outgoingGameMessage = new LegacyMessage(); + outgoingGameMessage.setMessageId(1028); // NPS_SEND_MINI_RIFF_LIST + + const resultSize = channelRecordSize * channels.length - 12; + + const packetContent = Buffer.alloc(resultSize); + + let offset = 0; + try { + packetContent.writeUInt32BE(channels.length, offset); + offset += 4; // offset is 8 + + // loop through the channels + for (const channel of channels) { + offset = serializeString(channel.name, packetContent, offset); + + packetContent.writeUInt32BE(channel.id, offset); + offset += 4; + packetContent.writeUInt16BE(channel.population, offset); + offset += 2; + } + + outgoingGameMessage.setBuffer(packetContent); + + // Build the packet + const packetResult = new BytableMessage(); + packetResult.setSerializeOrder([ + { name: "data", field: "Buffer" }, + ]); + packetResult.deserialize(outgoingGameMessage.serialize()); + + log.debug(`[${connectionId}] Sending response: ${packetResult.serialize().toString("hex")}`); + + return { + connectionId, + message: packetResult, + }; + } catch (error) { + const err = Error( + `Error handling NPS_SEND_MINI_RIFF_LIST: ${String(error)}`, + ); + err.cause = error; + throw err; + } +} diff --git a/libs/@rustymotors/roomserver/src/lib/types.ts b/libs/@rustymotors/roomserver/src/lib/types.ts new file mode 100644 index 000000000..8d4b1a6f0 --- /dev/null +++ b/libs/@rustymotors/roomserver/src/lib/types.ts @@ -0,0 +1,8 @@ + +export interface PacketBody { + serialize(): Uint8Array; + deserialize(data: Uint8Array): void; + size(): number; + toString(): string; + toHexString(): string; +} diff --git a/libs/@rustymotors/roomserver/tsconfig.json b/libs/@rustymotors/roomserver/tsconfig.json new file mode 100644 index 000000000..ba131bc27 --- /dev/null +++ b/libs/@rustymotors/roomserver/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "incremental": true, + "composite": true, + "resolveJsonModule": true + }, + "include": ["src/**/*.ts"] +} diff --git a/migrations/0001_createAttachmentPoint.sql b/migrations/0001-createAttachmentPoint.sql similarity index 100% rename from migrations/0001_createAttachmentPoint.sql rename to migrations/0001-createAttachmentPoint.sql diff --git a/migrations/0002_createBrand.sql b/migrations/0002-createBrand.sql similarity index 100% rename from migrations/0002_createBrand.sql rename to migrations/0002-createBrand.sql diff --git a/migrations/0003_createModel.sql b/migrations/0003-createModel.sql similarity index 100% rename from migrations/0003_createModel.sql rename to migrations/0003-createModel.sql diff --git a/migrations/0004_createAbstractPartType.sql b/migrations/0004-createAbstractPartType.sql similarity index 100% rename from migrations/0004_createAbstractPartType.sql rename to migrations/0004-createAbstractPartType.sql diff --git a/migrations/0005_createPartGrade.sql b/migrations/0005-createPartGrade.sql similarity index 100% rename from migrations/0005_createPartGrade.sql rename to migrations/0005-createPartGrade.sql diff --git a/migrations/0006_createPartType.sql b/migrations/0006-createPartType.sql similarity index 100% rename from migrations/0006_createPartType.sql rename to migrations/0006-createPartType.sql diff --git a/migrations/0007_createBrandedPart.sql b/migrations/0007-createBrandedPart.sql similarity index 100% rename from migrations/0007_createBrandedPart.sql rename to migrations/0007-createBrandedPart.sql diff --git a/migrations/0008_createPart.sql b/migrations/0008-createPart.sql similarity index 100% rename from migrations/0008_createPart.sql rename to migrations/0008-createPart.sql diff --git a/migrations/0009_createDriverClass.sql b/migrations/0009-createDriverClass.sql similarity index 100% rename from migrations/0009_createDriverClass.sql rename to migrations/0009-createDriverClass.sql diff --git a/migrations/0010_createPlayerType.sql b/migrations/0010-createPlayerType.sql similarity index 100% rename from migrations/0010_createPlayerType.sql rename to migrations/0010-createPlayerType.sql diff --git a/migrations/0011_createPlayer.sql b/migrations/0011-createPlayer.sql similarity index 100% rename from migrations/0011_createPlayer.sql rename to migrations/0011-createPlayer.sql diff --git a/migrations/0012_createSkinType.sql b/migrations/0012-createSkinType.sql similarity index 100% rename from migrations/0012_createSkinType.sql rename to migrations/0012-createSkinType.sql diff --git a/migrations/0013_createPtSkin.sql b/migrations/0013-createPtSkin.sql similarity index 100% rename from migrations/0013_createPtSkin.sql rename to migrations/0013-createPtSkin.sql diff --git a/migrations/0014_createVehicle.sql b/migrations/0014-createVehicle.sql similarity index 100% rename from migrations/0014_createVehicle.sql rename to migrations/0014-createVehicle.sql diff --git a/migrations/0015_seedAttachmentPoint.sql b/migrations/0015-seedAttachmentPoint.sql similarity index 100% rename from migrations/0015_seedAttachmentPoint.sql rename to migrations/0015-seedAttachmentPoint.sql diff --git a/migrations/0016_seedBrand.sql b/migrations/0016-seedBrand.sql similarity index 100% rename from migrations/0016_seedBrand.sql rename to migrations/0016-seedBrand.sql diff --git a/migrations/0017_seedModel.sql b/migrations/0017-seedModel.sql similarity index 100% rename from migrations/0017_seedModel.sql rename to migrations/0017-seedModel.sql diff --git a/migrations/0018_seedAbstractPartType.sql b/migrations/0018-seedAbstractPartType.sql similarity index 100% rename from migrations/0018_seedAbstractPartType.sql rename to migrations/0018-seedAbstractPartType.sql diff --git a/migrations/0019_seedAbstractPartGrade.sql b/migrations/0019-seedAbstractPartGrade.sql similarity index 100% rename from migrations/0019_seedAbstractPartGrade.sql rename to migrations/0019-seedAbstractPartGrade.sql diff --git a/migrations/0020_seedAbstractPartType.sql b/migrations/0020-seedAbstractPartType.sql similarity index 100% rename from migrations/0020_seedAbstractPartType.sql rename to migrations/0020-seedAbstractPartType.sql diff --git a/migrations/0021_seedBrandedPart.sql b/migrations/0021-seedBrandedPart.sql similarity index 100% rename from migrations/0021_seedBrandedPart.sql rename to migrations/0021-seedBrandedPart.sql diff --git a/migrations/0022_seedPart.sql b/migrations/0022-seedPart.sql similarity index 100% rename from migrations/0022_seedPart.sql rename to migrations/0022-seedPart.sql diff --git a/migrations/0023_seedDriverClass.sql b/migrations/0023-seedDriverClass.sql similarity index 100% rename from migrations/0023_seedDriverClass.sql rename to migrations/0023-seedDriverClass.sql diff --git a/migrations/0024_seedPlayerType.sql b/migrations/0024-seedPlayerType.sql similarity index 100% rename from migrations/0024_seedPlayerType.sql rename to migrations/0024-seedPlayerType.sql diff --git a/migrations/0025_seedPlayer.sql b/migrations/0025-seedPlayer.sql similarity index 100% rename from migrations/0025_seedPlayer.sql rename to migrations/0025-seedPlayer.sql diff --git a/migrations/0026_createSVACarClass.sql b/migrations/0026-createSVACarClass.sql similarity index 100% rename from migrations/0026_createSVACarClass.sql rename to migrations/0026-createSVACarClass.sql diff --git a/migrations/0027_createSVAModeRestriction.sql b/migrations/0027-createSVAModeRestriction.sql similarity index 100% rename from migrations/0027_createSVAModeRestriction.sql rename to migrations/0027-createSVAModeRestriction.sql diff --git a/migrations/0028_createStockVehicleAtrributes.sql b/migrations/0028-createStockVehicleAtrributes.sql similarity index 100% rename from migrations/0028_createStockVehicleAtrributes.sql rename to migrations/0028-createStockVehicleAtrributes.sql diff --git a/migrations/0029_seedSVACarClass.sql b/migrations/0029-seedSVACarClass.sql similarity index 100% rename from migrations/0029_seedSVACarClass.sql rename to migrations/0029-seedSVACarClass.sql diff --git a/migrations/0030_seedSVAModeRestriction.sql b/migrations/0030-seedSVAModeRestriction.sql similarity index 100% rename from migrations/0030_seedSVAModeRestriction.sql rename to migrations/0030-seedSVAModeRestriction.sql diff --git a/migrations/0031_seedStockVehicleAttributes.sql b/migrations/0031-seedStockVehicleAttributes.sql similarity index 100% rename from migrations/0031_seedStockVehicleAttributes.sql rename to migrations/0031-seedStockVehicleAttributes.sql diff --git a/migrations/0032_createStockAssembly.sql b/migrations/0032-createStockAssembly.sql similarity index 100% rename from migrations/0032_createStockAssembly.sql rename to migrations/0032-createStockAssembly.sql diff --git a/migrations/0033_seedStockAssembly.sql b/migrations/0033-seedStockAssembly.sql similarity index 100% rename from migrations/0033_seedStockAssembly.sql rename to migrations/0033-seedStockAssembly.sql diff --git a/migrations/0034_seedSkinType.sql b/migrations/0034-seedSkinType.sql similarity index 100% rename from migrations/0034_seedSkinType.sql rename to migrations/0034-seedSkinType.sql diff --git a/migrations/0035_seedPTSkin.sql b/migrations/0035-seedPTSkin.sql similarity index 100% rename from migrations/0035_seedPTSkin.sql rename to migrations/0035-seedPTSkin.sql diff --git a/migrations/0037_createNextPartNumberSeq.sql b/migrations/0037-createNextPartNumberSeq.sql similarity index 100% rename from migrations/0037_createNextPartNumberSeq.sql rename to migrations/0037-createNextPartNumberSeq.sql diff --git a/migrations/0038_createWarehouse.sql b/migrations/0038-createWarehouse.sql similarity index 100% rename from migrations/0038_createWarehouse.sql rename to migrations/0038-createWarehouse.sql diff --git a/migrations/0039_createLogin.sql b/migrations/0039-createLogin.sql similarity index 100% rename from migrations/0039_createLogin.sql rename to migrations/0039-createLogin.sql diff --git a/migrations/0040_createProfile.sql b/migrations/0040-createProfile.sql similarity index 100% rename from migrations/0040_createProfile.sql rename to migrations/0040-createProfile.sql diff --git a/migrations/0041_createKey.sql b/migrations/0041-createKey.sql similarity index 100% rename from migrations/0041_createKey.sql rename to migrations/0041-createKey.sql diff --git a/package.json b/package.json index d20496679..14f7dbec4 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,7 @@ "test": "make test", "types:db": "pnpm --filter=rusty-motors-database run types:db", "types": "dotenvx run -- pnpm run types:db", - "migrate": "pg-migrations apply --directory migrations ", - "prepare": "husky", + "migrate": "npx dotenvx run -- pnpm pg-migrations apply --directory migrations ", "build": "tsc" }, "files": [ @@ -93,7 +92,6 @@ "eslint-config-prettier": "^10.1.1", "eslint-plugin-prettier": "5.2.6", "globals": "^16.0.0", - "husky": "^9.1.7", "lint-staged": "^15.5.0", "nx": "20.7.2", "prettier": "3.5.3", @@ -134,6 +132,14 @@ "pnpm": { "overrides": { "tinymce": "^7.7.2" - } + }, + "onlyBuiltDependencies": [ + "@sentry-internal/node-cpu-profiler", + "@sentry/cli", + "@sentry/profiling-node", + "@swc/core", + "bcrypt", + "nx" + ] } } diff --git a/packages/database/index.ts b/packages/database/index.ts index da76d2fc6..083580a8d 100644 --- a/packages/database/index.ts +++ b/packages/database/index.ts @@ -1,8 +1,15 @@ -export { - type DatabaseManager, - databaseManager, - getDatabase, -} from "./src/DatabaseManager.js"; +// export { +// type DatabaseManager, +// databaseManager, +// getDatabase, +// } from "./src/DatabaseManager.js"; +export { getDatabase } from "./src/services/database.js"; export { databaseService, findCustomerByContext } from "./src/databaseService.js"; export { getTunables as getTuneables } from "./src/services/tunables.js"; - +export { databaseManager } from "./src/DatabaseManager.js"; +export { purchaseCar, } from "./src/functions/purchaseCar.js"; +export { getOwnedVehiclesForPerson, getVehicleAndParts } from "./src/functions/createNewCar.js"; +export { getVehiclePartTree, setVehiclePartTree, buildVehiclePartTreeFromDB } from "./src/cache.js"; +export { DamageInfo } from "./src/models/DamageInfo.js"; +export { addRoomServer, getRoomServerByPort, getRoomServerByName, getRoomServerById } from "./src/services/roomServers.js"; +export type { TPart } from "./src/models/Part.js"; diff --git a/packages/database/package.json b/packages/database/package.json index 017b3dcdc..c821412f7 100644 --- a/packages/database/package.json +++ b/packages/database/package.json @@ -14,7 +14,7 @@ "format": "npx @biomejs/biome format --write .", "test": "vitest run --coverage", "types:db": "npx @databases/pg-schema-cli --directory src/__generated__", - "build": "tsc" + "build": "tsc" }, "keywords": [], "author": "", @@ -23,7 +23,9 @@ "@types/pg": "^8.11.11", "pg": "^8.14.1", "pg-hstore": "^2.3.4", - "sequelize": "^6.37.7" + "sequelize": "^6.37.7", + "slonik": "^46.4.0", + "zod": "^3.24.2" }, "directories": { "test": "test" diff --git a/packages/database/src/cache.ts b/packages/database/src/cache.ts new file mode 100644 index 000000000..5ce1469b0 --- /dev/null +++ b/packages/database/src/cache.ts @@ -0,0 +1,241 @@ +import { getServerLogger } from "rusty-motors-shared"; +import type { TBrand } from "./models/Brand.js"; +import { TVehicle, vehiclePartTreeToJSON, type VehiclePartTreeType } from "./models/VehiclePartTree.js"; +import { getSlonik, getDatabase } from "./services/database.js"; +import * as Sentry from "@sentry/node"; +import { TPart } from "./models/Part.js"; +const { slonik, sql } = await getDatabase(); + +const brandCache = new Map(); + +export async function getBrand(brandName: string): Promise { + if (brandCache.has(brandName)) { + return brandCache.get(brandName); + } + + return await Sentry.startSpan( + { + name: "Get next part id", + op: "db.query", + attributes: { + sql: "SELECT nextval('part_partid_seq')", + db: "postgres", + }, + }, + async () => { + const { slonik, sql } = await getSlonik(); + + const brand = await slonik.one(sql.typeAlias("brand")` + SELECT brandid, brand, isstock FROM brand WHERE brandname = ${brandName} + `); + brandCache.set(brandName, brand); + return brand; + }, + ); +} + +const vehiclePartTreeCache = new Map(); + +export async function getVehiclePartTree( + vehicleId: number, +): Promise { + if (vehiclePartTreeCache.has(vehicleId)) { + return vehiclePartTreeCache.get(vehicleId); + } + + return undefined; +} + +export async function setVehiclePartTree( + vehicleId: number, + vehiclePartTree: VehiclePartTreeType, +): Promise { + vehiclePartTreeCache.set(vehicleId, vehiclePartTree); +} + +export async function buildVehiclePartTreeFromDB( + vehicleId: number, +): Promise { + const log = getServerLogger("database/cache"); + const vehicle = await Sentry.startSpan( + { + name: "Get vehicle", + op: "db.query", + attributes: { + sql: "SELECT vehicle_id, skin_id, flags, class, info_setting, damage_info FROM vehicle WHERE vehicle_id = $1", + db: "postgres", + }, + }, + async () => { + return slonik.one(sql.typeAlias("vehicle")` + SELECT vehicle_id, skin_id, flags, class, info_setting, damage_info + FROM vehicle + WHERE vehicle_id = ${vehicleId} + `); + } + ) as TVehicle; + + if (!vehicle) { + log.error(`Vehicle with id ${vehicleId} does not exist`); + throw new Error(`Vehicle with id ${vehicleId} does not exist`); + } + + const vehiclePartTree: VehiclePartTreeType = { + vehicleId: vehicle.vehicle_id, + skinId: vehicle.skin_id, + flags: vehicle.flags, + class: vehicle.class, + infoSetting: vehicle.info_setting, + damageInfo: vehicle.damage_info, + isStock: false, + ownedLotId: null, + ownerID: null, + partId: vehicle.vehicle_id, + parentPartId: null, + brandedPartId: 0, + partTree: { + level1: { + partId: 0, + parts: [], + }, + level2: { + partId: 0, + parts: [], + }, + }, + }; + + // Get first part + const part = await Sentry.startSpan( + { + name: "Get part", + op: "db.query", + attributes: { + sql: "SELECT partid, parentpartid, brandedpartid, percentdamage, itemwear, attachmentpointid, ownerid, partname, repaircost, scrapvalue FROM part WHERE partid = $1", + db: "postgres", + }, + }, + async () => { + return slonik.one(sql.typeAlias("part")` + SELECT part_id, parent_part_id, branded_part_id, percent_damage, item_wear, attachment_point_id, owner_id, part_name, repair_cost, scrap_value + FROM part + WHERE part_id = ${vehicleId} + `); + }, + ); + + if (!part) { + log.error(`Part with id ${vehicleId} does not exist`); + throw new Error(`Part with id ${vehicleId} does not exist`); + } + + vehiclePartTree.brandedPartId = part.brandedpartid; + vehiclePartTree.ownerID = part.ownerid; + + const level1Parts = await Sentry.startSpan( + { + name: "Get level 1 parts", + op: "db.query", + attributes: { + sql: "SELECT partid, parentpartid, brandedpartid, percentdamage, itemwear, attachmentpointid, ownerid, partname, repaircost, scrapvalue FROM part WHERE parentpartid = $1", + db: "postgres", + }, + }, + async () => { + return slonik.many(sql.typeAlias("part")` + SELECT part_id, parent_part_id, branded_part_id, percent_damage, item_wear, attachment_point_id, owner_id, part_name, repair_cost, scrap_value + FROM part + WHERE parent_part_id = ${vehicleId} + `); + }, + ) as TPart[]; + + if (level1Parts.length === 0) { + log.error(`Vehicle with id ${vehicleId} has no parts`); + throw new Error(`Vehicle with id ${vehicleId} has no parts`); + } + + log.debug(`We got parts!`); + log.debug( + `There are ${level1Parts.length} level 1 parts in the vehicle assembly`, + ); + + log.debug(`level1Parts: ${JSON.stringify(level1Parts)}`); + + const level1PartsIds = level1Parts.map((part) => part.part_id); + + log.debug(`level1PartsIds: ${level1PartsIds}`); + + for (const part of level1Parts) { + log.debug( + `Adding part: ${JSON.stringify(part)} to vehicle part tree level 1`, + ); + + const newPart: TPart = { + part_id: part.part_id, + parent_part_id: part.parent_part_id, + branded_part_id: part.branded_part_id, + percent_damage: part.percent_damage, + item_wear: part.item_wear, + attachment_point_id: part.attachment_point_id, + owner_id: part.owner_id, + part_name: part.part_name, + repair_cost: part.repair_cost, + scrap_value: part.scrap_value, + }; + + vehiclePartTree.partTree.level1.parts.push(newPart); + } + + const level2Parts = await Sentry.startSpan( + { + name: "Get level 2 parts", + op: "db.query", + attributes: { + sql: "SELECT partid, parentpartid, brandedpartid, percentdamage, itemwear, attachmentpointid, ownerid, partname, repaircost, scrapvalue FROM part WHERE parentpartid IN ($1)", + db: "postgres", + }, + }, + async () => { + return slonik.many(sql.typeAlias("part")` + SELECT part_id, parent_part_id, branded_part_id, percent_damage, item_wear, attachment_point_id, owner_id, part_name, repair_cost, scrap_value + FROM part + WHERE parent_part_id IN (${sql.join(level1PartsIds, sql.fragment`, `)}) + `); + }, + ); + + if (level2Parts.length === 0) { + log.error(`Vehicle with id ${vehicleId} has no level 2 parts`); + throw new Error(`Vehicle with id ${vehicleId} has no level 2 parts`); + } + + log.debug(`We got parts!`); + log.debug( + `There are ${level2Parts.length} level 2 parts in the vehicle assembly`, + ); + + for (const part of level2Parts) { + const newPart: TPart = { + part_id: part.part_id, + parent_part_id: part.parent_part_id, + branded_part_id: part.branded_part_id, + percent_damage: part.percent_damage, + item_wear: part.item_wear, + attachment_point_id: part.attachment_point_id, + owner_id: part.owner_id, + part_name: part.part_name, + repair_cost: part.repair_cost, + scrap_value: part.scrap_value, + }; + + vehiclePartTree.partTree.level2.parts.push(newPart); + } + + log.debug(`Vehicle part tree populated`); + log.debug(`Vehicle part tree: ${vehiclePartTreeToJSON(vehiclePartTree)}`); + + setVehiclePartTree(vehiclePartTree.vehicleId, vehiclePartTree); + + return vehiclePartTree; +} diff --git a/packages/database/src/databaseService.ts b/packages/database/src/databaseService.ts index fbb506902..acfa95841 100644 --- a/packages/database/src/databaseService.ts +++ b/packages/database/src/databaseService.ts @@ -2,7 +2,7 @@ import { hashSync } from "bcrypt"; import { DatabaseSync } from "node:sqlite"; import { getServerLogger } from "rusty-motors-shared"; import type { UserRecordMini } from "rusty-motors-shared"; -import { SQL, DATABASE_PATH } from "./databaseConstrants"; +import { SQL, DATABASE_PATH } from "./databaseConstrants.js"; // Database Service Interface export interface DatabaseService { diff --git a/packages/database/src/functions/createNewCar.ts b/packages/database/src/functions/createNewCar.ts new file mode 100644 index 000000000..978b737c6 --- /dev/null +++ b/packages/database/src/functions/createNewCar.ts @@ -0,0 +1,595 @@ +import type { DatabaseTransactionConnection } from 'slonik'; +import { getDatabase } from '../services/database.js'; +import * as Sentry from '@sentry/node'; +import { getServerLogger } from 'rusty-motors-shared'; +import { buildVehiclePartTree, saveVehicle, saveVehiclePartTree } from '../models/VehiclePartTree.js'; + +const { slonik, sql } = await getDatabase(); +const log = getServerLogger('createNewCar'); + +// async function playerExists(playerId: number): Promise { +// return Sentry.startSpan( +// { +// name: 'Get player', +// op: 'db.query', +// attributes: { +// sql: 'SELECT 1 FROM player WHERE player_id = ${playerId}', +// db: 'postgres', +// }, +// }, +// async () => { +// return slonik.exists(sql.typeAlias('id')` +// SELECT 1 FROM player WHERE player_id = ${playerId} +// `); +// }, +// ); +// } + +async function skinExists(skinId: number): Promise { + return Sentry.startSpan( + { + name: 'skinExists', + op: 'db.query', + attributes: { + sql: 'SELECT 1 FROM pt_skin WHERE skin_id = ${skinId}', + db: 'postgres', + }, + }, + async () => { + return slonik.exists(sql.typeAlias('id')` + SELECT 1 FROM pt_skin WHERE skin_id = ${skinId} + `); + }, + ); +} + +async function getAbstractPartTypeIDForBrandedPartID( + connection: DatabaseTransactionConnection, + brandedPartId: number, +): Promise { + const abstractPartTypeId = await Sentry.startSpan( + { + name: 'GetAbstractPartTypeIDForBrandedPartID', + op: 'db.query', + attributes: { + sql: 'SELECT pt.abstract_part_type_id FROM branded_part bp inner join part_type pt on bp.part_type_id = pt.part_type_id WHERE bp.branded_part_id = ${brandedPartId}', + db: 'postgres', + }, + }, + async () => { + return connection.one(sql.typeAlias('abstractPartType')` + SELECT pt.abstract_part_type_id + FROM branded_part bp + inner join part_type pt on bp.part_type_id = pt.part_type_id + WHERE bp.branded_part_id = ${brandedPartId} + `); + }, + ); + + if (!abstractPartTypeId) { + log.error(`branded part with id ${brandedPartId} does not exist`); + throw new Error(`branded part with id ${brandedPartId} does not exist`); + } + return abstractPartTypeId.abstract_part_type_id; +} + +// async function isAbstractPartTypeAVehicle( +// abstractPartTypeId: number, +// ): Promise { +// return abstractPartTypeId === 101; +// } + +// type partTableEntry = { +// partId: number | null; +// parentPartId: number | null; +// brandedPartId: number | null; +// AttachmentPointId: number | null; +// }; + + + + + +export async function createNewCar( + brandedPartId: number, + skinId: number, + newCarOwnerId: number, +): Promise { + if ((await skinExists(skinId)) === false) { + log.error('skin does not exist'); + throw new Error('skin does not exist'); + } + + const abstractPartTypeId = await getAbstractPartTypeIDForBrandedPartID( + slonik, + brandedPartId, + ).catch((error) => { + log.error('Error getting abstract part type id: ' + error); + throw new Error('Error getting abstract part type id: ' + error); + }); + + if (abstractPartTypeId !== 101) { + log.error( + { + brandedPartId, + abstractPartTypeId, + }, + 'branded part is not a vehicle', + ); + throw new Error( + `branded part with id ${brandedPartId} and abstract part type id ${abstractPartTypeId} is not a vehicle`, + ); + } + + const vehicle = await buildVehiclePartTree({ + brandedPartId, + skinId, + ownedLotId: 6, + ownerID: newCarOwnerId, + isStock: true, + }); + + + log.debug({ vehicle }, 'vehicle'); + + await saveVehicle(vehicle); + await saveVehiclePartTree(vehicle); + + return vehicle.vehicleId; +} + + + + + + +// const tmpParts: partTableEntry[] = []; + +// tmpParts.push({ +// partId: null, +// parentPartId: null, +// brandedPartId: brandedPartId, +// AttachmentPointId: 0, +// }); + +// log.debug({ tmpParts }, 'tmpParts after pushing the first part'); + +// // Get the rest of the parts for the vehicle +// const part = tmpParts[0]; + +// if (typeof part === 'undefined') { +// log.error('tmpParts[0] is undefined'); +// throw new Error('tmpParts[0] is undefined'); +// } + +// const restOfTheParts = await slonik.many( +// sql.typeAlias('brandedPart')` +// SELECT b.branded_part_id, a.attachment_point_id +// From stock_assembly a +// inner join branded_part b on a.child_branded_part_id = b.branded_part_id +// where a.parent_branded_part_id = ${part.brandedPartId} +// `, +// ); + +// if (restOfTheParts.length === 0) { +// log.error('No parts found for the vehicle'); +// throw new Error('No parts found for the vehicle'); +// } + +// log.debug(`Found ${restOfTheParts.length} parts for the vehicle`); + +// log.debug({ restOfTheParts }, 'restOfTheParts'); + +// for (const part of restOfTheParts) { +// tmpParts.push({ +// partId: null, +// parentPartId: null, +// brandedPartId: part.branded_part_id, +// AttachmentPointId: part.attachment_point_id, +// }); +// } + +// log.debug({ tmpParts }, 'tmpParts after getting the rest of the parts'); + +// let vehicleId: number | null = null; + +// await slonik.transaction(async (connection) => { +// // First insert the new car into the vehicle table + +// if (tmpParts.length === 0) { +// log.error(`No parts found for the vehicle ${brandedPartId}`); +// throw new Error('No parts found for the vehicle'); +// } + +// log.debug({ tmpParts }, 'tmpParts'); + +// let parentPartId = null; +// let currentPartId = await getNextSq(connection, 'part_partid_seq'); + +// if (typeof tmpParts[0] === 'undefined') { +// log.error('tmpParts[0] is undefined'); +// throw new Error('tmpParts[0] is undefined'); +// } + +// // Make sure the first part's branded part id is not null +// if (tmpParts[0].brandedPartId === null) { +// log.error("The first part's branded part id is null"); +// throw new Error("The first part's branded part id is null"); +// } + +// // Get the first part's abstract part type id +// const firstPartAbstractPartTypeId = +// await getAbstractPartTypeIDForBrandedPartID( +// connection, +// tmpParts[0].brandedPartId, +// ); + +// if (firstPartAbstractPartTypeId !== 101) { +// throw new Error('The first part is not a vehicle'); +// } + +// // Get the skin record for the new car +// const skinDefaultFlag = ( +// await connection.one(sql.typeAlias('ptSkin')` +// SELECT default_flag FROM pt_skin WHERE skin_id = ${skinId} +// `) +// ).default_flag; + +// if (typeof skinDefaultFlag === 'undefined') { +// log.error('skinDefaultFlag is undefined'); +// throw new Error('skinDefaultFlag is undefined'); +// } + +// // The first part will have a parentpartid of 0, and a partid of nextval(next_part_id) +// ({ currentPartId, parentPartId, vehicleId } = await saveVehicleAndParts( +// tmpParts, +// connection, +// currentPartId, +// parentPartId, +// newCarOwnerId, +// skinId, +// skinDefaultFlag, +// vehicleId, +// )); +// }); + +// if (vehicleId === null) { +// log.error('vehicleId is null'); +// throw new Error('vehicleId is null'); +// } + +// return vehicleId; +// } + +export type DBPart = { + partId: number; + parentPartId: number; + brandedPartId: number; + percentDamage: number; + itemWear: number; + attachmentPointId: number; + ownerId: number; + partName: string; + repairCost: number; + scrapValue: number; +}; + +// async function saveVehicleAndParts( +// tmpParts: partTableEntry[], +// connection: DatabaseTransactionConnection, +// currentPartId: number, +// parentPartId: any, +// newCarOwnerId: number, +// skinId: number, +// skinDefaultFlag: any, +// vehicleId: any, +// ) { +// const part = tmpParts[0]; +// if (typeof part === 'undefined') { +// log.error('tmpParts[0] is undefined'); +// throw new Error('tmpParts[0] is undefined'); +// } + +// await connection.query(sql.typeAlias('part')` +// INSERT INTO part (part_id, parent_part_id, branded_part_id, percent_damage, item_wear, attachment_point_id, owner_id, part_name, repair_cost, scrap_value) +// VALUES (${currentPartId}, ${parentPartId}, ${part.brandedPartId}, 0, 0, ${part.AttachmentPointId}, ${newCarOwnerId}, null, 0, 0) +// `); + +// // Insert the vehicle record +// await connection.query( +// sql.typeAlias('brandedPart')` +// INSERT INTO vehicle (vehicle_id, skin_id, flags, class, info_setting, damage_info) +// VALUES (${currentPartId}, ${skinId}, ${skinDefaultFlag}, 0, 0, null) +// `, +// ); + +// vehicleId = currentPartId; + +// log.debug({ vehicleId }, 'vehicleId'); + +// // Update the partid of the part in the tmpParts +// if (typeof tmpParts[0] === 'undefined') { +// log.error('tmpParts[0] is undefined'); +// throw new Error('tmpParts[0] is undefined'); +// } + +// tmpParts[0].partId = currentPartId; +// tmpParts[0].parentPartId = parentPartId; + +// log.debug({ tmpParts }, 'tmpParts after inserting the first part'); + +// // Now insert the rest of the parts +// for (let i = 1; i < tmpParts.length; i++) { +// parentPartId = currentPartId; +// currentPartId = await getNextSq(connection, 'part_partid_seq'); + +// const part = tmpParts[i]; + +// if (typeof part === 'undefined') { +// log.error('tmpParts[i] is undefined'); +// throw new Error('tmpParts[i] is undefined'); +// } + +// await addPartToDatabase( +// part, +// connection, +// currentPartId, +// parentPartId, +// newCarOwnerId, +// ); + +// // Update the partid of the part in the tmpParts array +// part.partId = currentPartId; +// part.parentPartId = parentPartId; +// } + +// log.debug({ tmpParts }, 'tmpParts after inserting the rest of the parts'); +// return { currentPartId, parentPartId, vehicleId }; +// } + +// async function addPartToDatabase( +// part: partTableEntry | undefined, +// connection: DatabaseTransactionConnection, +// currentPartId: number, +// parentPartId: any, +// newCarOwnerId: number, +// ) { +// if (typeof part === 'undefined') { +// log.error('tmpParts[i] is undefined'); + +// throw new Error('tmpParts[i] is undefined'); +// } + +// if (typeof part.brandedPartId === 'undefined') { +// log.error({ part }, 'brandedPartId is undefined'); +// throw new Error('brandedPartId is undefined'); +// } + +// if (typeof part.AttachmentPointId === 'undefined') { +// log.error({ part }, 'AttachmentPointId is undefined'); +// throw new Error('AttachmentPointId is undefined'); +// } + +// await connection.query( +// sql.typeAlias('part')` +// INSERT INTO part (part_id, parent_part_id, branded_part_id, percent_damage, item_wear, attachment_point_id, owner_id, part_name, repair_cost, scrap_value) +// VALUES (${currentPartId}, ${parentPartId}, ${part.brandedPartId}, 0, 0, ${part.AttachmentPointId}, ${newCarOwnerId}, null, 0, 0) +// `, +// ); +// } + +// async function getNextSq( +// connection: DatabaseTransactionConnection, +// seqName: string, +// ) { +// return await Sentry.startSpan( +// { +// name: 'Get next part id', +// op: 'db.query', +// attributes: { +// sql: "SELECT nextval('part_partid_seq')", +// db: 'postgres', +// }, +// }, +// async () => { +// return Number( +// ( +// await connection.one(sql.typeAlias('nextPartId')` +// SELECT nextval(${seqName}) +// `) +// ).nextval, +// ); +// }, +// ); +// } + +export type PartEntry = { + partId: number; + parentPartId: number | null; + brandedPartId: number; + percentDamage: number; + itemWear: number; + attachmentPointId: number; + ownerId: number; + partName: string; + repairCost: number; + scrapValue: number; +}; + +export type VehicleRecord = { + vehicleId: number; + skinId: number; + flags: number; + class: number; + infoSetting: number; + damageInfo: number; + ownerId: number; + parts: PartEntry[]; +}; + +export async function getVehicleAndParts( + vehicleId: number, +): Promise { + const vehicle: VehicleRecord = { + vehicleId: vehicleId, + skinId: 0, + flags: 0, + class: 0, + infoSetting: 0, + damageInfo: 0, + ownerId: 0, + parts: [], + } + + // Get the vehicle record + const rawVehicleRecord = await Sentry.startSpan( + { + name: 'Get vehicle and parts', + op: 'db.query', + attributes: { + sql: 'SELECT * FROM vehicle WHERE vehicle_id = ${vehicleId}', + db: 'postgres', + }, + }, + async (): Promise => { + const vehicle = await slonik.one(sql.typeAlias('vehicleWithOwner')` + SELECT v.*, p.owner_id +from public.vehicle v +inner join public.part p on p.part_id = v.vehicle_id +where v.vehicle_id = ${vehicleId} + `); + + if (!vehicle) { + log.error(`Vehicle with id ${vehicleId} not found`); + throw new Error(`Vehicle with id ${vehicleId} not found`); + } + + return { + vehicleId: vehicle.vehicle_id, + skinId: vehicle.skin_id, + flags: vehicle.flags, + class: vehicle.class, + infoSetting: vehicle.info_setting, + damageInfo: vehicle.damage_info, + ownerId: vehicle.owner_id, + parts: [], + }; + }, + ); + + if (!rawVehicleRecord) { + log.error(`Vehicle with id ${vehicleId} not found`); + throw new Error(`Vehicle with id ${vehicleId} not found`); + } + + vehicle.vehicleId = rawVehicleRecord.vehicleId; + vehicle.skinId = rawVehicleRecord.skinId; + vehicle.flags = rawVehicleRecord.flags; + vehicle.class = rawVehicleRecord.class; + vehicle.infoSetting = rawVehicleRecord.infoSetting; + vehicle.damageInfo = rawVehicleRecord.damageInfo; + vehicle.ownerId = rawVehicleRecord.ownerId; + + // Get the parts for the vehicle + const parts = await Sentry.startSpan( + { + name: 'Get vehicle and parts', + op: 'db.query', + attributes: { + sql: 'SELECT * FROM part WHERE part_id = ${vehicleId} OR parent_part_id = ${vehicleId}', + db: 'postgres', + }, + }, + async (): Promise => { + const parts = []; + const rawParts = await slonik.many(sql.typeAlias('part')` + SELECT * + FROM part p1 + inner join part p2 on p1.part_id = p2.parent_part_id + WHERE p1.part_id = ${vehicleId} OR p1.parent_part_id = ${vehicleId} + `); + + log.debug({ rawParts }, 'rawParts'); + + for (const rawPart of rawParts) { + parts.push({ + partId: rawPart.part_id, + parentPartId: rawPart.parent_part_id, + brandedPartId: rawPart.branded_part_id, + percentDamage: rawPart.percent_damage, + itemWear: rawPart.item_wear, + attachmentPointId: rawPart.attachment_point_id, + ownerId: rawPart.owner_id, + partName: rawPart.part_name, + repairCost: rawPart.repair_cost, + scrapValue: rawPart.scrap_value, + }); + } + return parts; + }, + ); + + if (parts.length === 0) { + log.error(`No parts found for vehicle with id ${vehicleId}`); + throw new Error(`No parts found for vehicle with id ${vehicleId}`); + } + + log.debug({ parts }, 'parts'); + + vehicle.parts = parts; + + return vehicle; +} + +export async function getOwnedVehiclesForPerson( + personId: number, +): Promise< + { + partId: number; + parentPartId: number | null; + brandedPartId: number; + percentDamage: number; + itemWear: number; + attachmentPointId: number; + ownerId: number; + partName: string; + repairCost: number; + scrapValue: number; + }[] +> { + return Sentry.startSpan( + { + name: 'Get owned vehicles for person', + op: 'db.query', + attributes: { + sql: 'SELECT p.part_id, p.parent_part_id, p.branded_part_id, p.attachment_point_id, p.owner_id, p.part_name, p.repair_cost, p.scrap_value from public.part p inner join public.vehicle v on v.vehicle_id = p.part_id where p.owner_id = ${personId}', + db: 'postgres', + }, + }, + async () => { + const cars = []; + const parts = await slonik.any(sql.typeAlias('part')` + SELECT p.part_id, p.branded_part_id, p.attachment_point_id, p.owner_id, p.part_name, p.repair_cost, p.scrap_value +from public.part p +inner join public.vehicle v on v.vehicle_id = p.part_id +where p.owner_id = ${personId}; + `); + for (const part of parts) { + cars.push({ + partId: part.part_id, + parentPartId: null, + brandedPartId: part.branded_part_id, + percentDamage: 0, + itemWear: 0, + attachmentPointId: part.attachment_point_id, + ownerId: part.owner_id, + partName: part.part_name, + repairCost: part.repair_cost, + scrapValue: part.scrap_value, + }); + } + return cars; + }, + ); +} + diff --git a/packages/database/src/functions/getAbstractPartTypeId.ts b/packages/database/src/functions/getAbstractPartTypeId.ts new file mode 100644 index 000000000..79ae7affa --- /dev/null +++ b/packages/database/src/functions/getAbstractPartTypeId.ts @@ -0,0 +1,53 @@ +import { eq } from "drizzle-orm"; +import { getDatabase } from "rusty-motors-database"; +import { + brandedPart as brandedPartSchema, + part as partSchema, + partType as partTypeSchema, +} from "rusty-motors-schema"; +import { getServerLogger } from "rusty-motors-shared"; + +/** + * Get the abstract part type id from the partId + * + * @param partId The part id + * @returns The abstract part type id + * @throws {Error} If the part ID is not found + * @throws {Error} If the abstract part type ID is not found + */ +export async function getAbstractPartTypeId(partId: number): Promise { + const log = getServerLogger(); + log.setName("getAbstractPartTypeId"); + + log.debug(`Getting abstract part type ID for part ${partId}`); + + const db = getDatabase(); + + const abstractPartTypeId = await db + .select() + .from(partSchema) + .leftJoin( + brandedPartSchema, + eq(partSchema.brandedPartId, brandedPartSchema.brandedPartId), + ) + .leftJoin( + partTypeSchema, + eq(brandedPartSchema.partTypeId, partTypeSchema.partTypeId), + ) + .where(eq(partSchema.partId, partId)) + .limit(1) + .then((rows) => { + if (rows.length === 0) { + throw new Error(`Part ${partId} not found`); + } + + return rows[0]?.part_type?.abstractPartTypeId; + }); + + if (typeof abstractPartTypeId === "undefined") { + log.error(`Abstract part type ID not found for part ${partId}`); + throw new Error(`Abstract part type ID not found for part ${partId}`); + } + + return abstractPartTypeId; +} \ No newline at end of file diff --git a/packages/database/src/functions/getWarehouseInventory.ts b/packages/database/src/functions/getWarehouseInventory.ts new file mode 100644 index 000000000..260409075 --- /dev/null +++ b/packages/database/src/functions/getWarehouseInventory.ts @@ -0,0 +1,70 @@ +import { db, getTuneables, sql } from "../../index.js"; +import { getServerLogger } from "rusty-motors-shared"; + +export type WarehouseInventory = { + inventory: { + brandedPartId: number; + retailPrice: number | null; + isDealOfTheDay: number; + }[]; + dealOfTheDayDiscount: number; +}; + +export async function getWarehouseInventory( + warehouseId: number, + brandId: number, +): Promise { + const log = getServerLogger({ name: "getWarehouseInventory" }); + + log.debug( + `Getting warehouse inventory for part ${brandId} in warehouse ${warehouseId}`, + ); + + let inventoryCars: { + brandedPartId: number; + retailPrice: number | null; + isDealOfTheDay: number; + }[] = []; + + const tunables = getTuneables(); + + const dealOfTheDayDiscount = tunables.getDealOfTheDayDiscount(); + const dealOfTheDayBrandedPartId = tunables.getDealOfTheDayBrandedPartId(); + + if (dealOfTheDayDiscount < 1) { + log.warn("Deal of the day not found"); + } + + if (brandId > 0) { + inventoryCars = await db.query(sql` + SELECT + brandedPartId: warehouseSchema.brandedPartId, + retailPrice: stockVehicleAttributesSchema.retailPrice, + isDealOfTheDay: warehouseSchema.isDealOfTheDay, + FROM warehouse w + LEFT JOIN branded_part bp ON w.brandedPartId = bp.brandedPartId + LEFT JOIN model m ON bp.modelId = m.modelId + LEFT JOIN stock_vehicle_attributes sva ON w.brandedPartId = sva.brandedPartId + WHERE w.playerId = ${warehouseId} AND m.brandId = ${brandId} + `); + } else { + inventoryCars = await db.query(sql` + SELECT + brandedPartId: warehouseSchema.brandedPartId, + retailPrice: stockVehicleAttributesSchema.retailPrice, + isDealOfTheDay: warehouseSchema.isDealOfTheDay, + FROM warehouse w + LEFT JOIN branded_part bp ON w.brandedPartId = bp.brandedPartId + LEFT JOIN model m ON bp.modelId = m.modelId + LEFT JOIN stock_vehicle_attributes sva ON w.brandedPartId = sva.brandedPartId + WHERE w.playerId = ${warehouseId} + `); + } + + const inventory = { + inventory: inventoryCars, + dealOfTheDayDiscount: dealOfTheDayDiscount ?? 0, + }; + + return inventory; +} \ No newline at end of file diff --git a/packages/database/src/functions/purchaseCar.ts b/packages/database/src/functions/purchaseCar.ts new file mode 100644 index 000000000..f14ed7dbd --- /dev/null +++ b/packages/database/src/functions/purchaseCar.ts @@ -0,0 +1,62 @@ +import { getServerLogger } from 'rusty-motors-shared'; +import { buildVehiclePartTree } from '../models/VehiclePartTree.js'; +import { createNewCar } from './createNewCar.js'; + +export async function purchaseCar( + playerId: number, + dealerId: number, + brandedPardId: number, + skinId: number, + tradeInCarId: number, +): Promise { +try { + getServerLogger('purchaseCar').debug( + `Player ${playerId} is purchasing car from dealer ${dealerId} with branded part ${brandedPardId} and skin ${skinId} and trading in car ${tradeInCarId}`, + ); + + if (dealerId === 6) { + // This is a new stock car and likeley does not exist in the server yet + // We need to create the car and add it to the player's lot + + // Create the new car + const newCarId = await createNewCar( + brandedPardId, + skinId, + playerId, + ).catch((error) => { + getServerLogger('purchaseCar').error( + { error }, + `Error creating new car for player ${playerId}`, + ); + throw error; + }); + + getServerLogger('purchaseCar').debug( + `Player ${playerId} purchased car with ID ${newCarId}`, + ); + + return newCarId; + } + + const parts = await buildVehiclePartTree({ + brandedPartId: brandedPardId, + skinId, + isStock: true, + ownedLotId: dealerId, + ownerID: playerId, + }); + + getServerLogger('purchaseCar').debug( + { parts }, + `Built vehicle part tree for player ${playerId}`, + ); + + return 1000; +} catch (error) { + getServerLogger('purchaseCar').error( + { error }, + `Error purchasing car for player ${playerId}`, + ); + throw error; +} +} diff --git a/packages/database/src/functions/transferPartAssembly.ts b/packages/database/src/functions/transferPartAssembly.ts new file mode 100644 index 000000000..ac8494697 --- /dev/null +++ b/packages/database/src/functions/transferPartAssembly.ts @@ -0,0 +1,153 @@ +import { eq } from "drizzle-orm"; +import { getDatabase } from "rusty-motors-database"; +import { + part as partSchema, + player as playerSchema, + vehicle as vehicleSchema, +} from "rusty-motors-schema"; +import { getServerLogger } from "rusty-motors-shared"; +import { getAbstractPartTypeId } from "./getAbstractPartTypeId.js"; + +const ABSTRACT_PART_TYPE_ID_CAR = 101; + +/** + * Transfer a part assembly + * + * This function transfers a part assembly from one owner to another, including all child parts. + * + * This function does NOT use a transaction. + * + * @param {number} partId + * @param {number} newOwnerId + * + * @returns {Promise} + * @throws {Error} If the part ID is not found + * @throws {Error} If the new owner ID is not found + * @throws {Error} If the part cannot be transferred + */ +export async function transferPartAssembly( + partId: number, + newOwnerId: number, +): Promise { + const log = getServerLogger(); + const db = getDatabase(); + log.setName("transferPartAssembly"); + + log.debug(`Transferring part assembly ${partId} to new owner ${newOwnerId}`); + + const topPart = await db + .select() + .from(partSchema) + .where(eq(partSchema.partId, partId)) + .limit(1) + .then((rows) => rows[0]); + + if (typeof topPart === "undefined") { + log.error(`Part ${partId} not found`); + throw new Error(`Part ${partId} not found`); + } + + if (topPart.ownerId === null) { + log.error(`Part ${partId} has no owner`); + throw new Error(`Part ${partId} has no owner`); + } + + if (topPart.ownerId === newOwnerId) { + log.error(`Part ${partId} is already owned by ${newOwnerId}`); + throw new Error(`Part ${partId} is already owned by ${newOwnerId}`); + } + + const newOwnerExists = await db + .select() + .from(playerSchema) + .where(eq(playerSchema.playerId, newOwnerId)) + .limit(1) + .then((rows) => rows[0] !== undefined); + + if (!newOwnerExists) { + log.error(`Owner ${newOwnerId} not found`); + throw new Error(`Owner ${newOwnerId} not found`); + } + + const children = await db + .select() + .from(partSchema) + .where(eq(partSchema.parentPartId, partId)) + .then((rows) => rows); + + if (children.length === 0) { + log.error(`Part ${partId} has no children`); + throw new Error(`Part ${partId} has no children`); + } + + try { + // If the part is a car, update the owner ID in the vehicle table + const isPartACar = await getAbstractPartTypeId(topPart.brandedPartId).then( + (abstractPartTypeId) => abstractPartTypeId === ABSTRACT_PART_TYPE_ID_CAR, + ); + + if (isPartACar) { + const car = await db + .select() + .from(vehicleSchema) + .where(eq(vehicleSchema.vehicleId, partId)) + .limit(1) + .then((rows) => rows[0]); + + if (typeof car === "undefined") { + log.error(`Vehicle ${partId} not found`); + throw Error(`Vehicle ${partId} not found`); + } + + // Remove the vehicle from the old owner's lot + const oldOwner = await db + .select() + .from(playerSchema) + .where(eq(playerSchema.playerId, topPart.ownerId)) + .limit(1) + .then((rows) => rows[0]); + + if (typeof oldOwner === "undefined") { + log.error(`Owner ${topPart.ownerId} not found`); + throw Error(`Owner ${topPart.ownerId} not found`); + } + + if (oldOwner.numCarsOwned > 0) { + oldOwner.numCarsOwned--; + try { + await db + .update(playerSchema) + .set(oldOwner) + .where(eq(playerSchema.playerId, topPart.ownerId)); + } catch (error) { + log.error( + `Error updating old owner ${topPart.ownerId}: ${String(error)}`, + ); + throw new Error( + `Error updating old owner ${topPart.ownerId}: ${String(error)}`, + ); + } + } else { + log.error(`Owner ${topPart.ownerId} has no cars`); + throw Error(`Owner ${topPart.ownerId} has no cars`); + } + } + + // Transfer the children + + for (const child of children) { + await transferPartAssembly(child.partId, newOwnerId); + } + + // Update the parent part's owner ID + await db + .update(partSchema) + .set({ ownerId: newOwnerId }) + .where(eq(partSchema.partId, partId)); + } catch (error) { + log.error(`Error transferring part ${partId}: ${String(error)}`); + throw new Error(`Error transferring part ${partId}: ${String(error)}`); + } + + log.debug(`Part assembly ${partId} transferred to new owner ${newOwnerId}`); +} diff --git a/packages/database/src/models/Brand.ts b/packages/database/src/models/Brand.ts new file mode 100644 index 000000000..64356d9f5 --- /dev/null +++ b/packages/database/src/models/Brand.ts @@ -0,0 +1,5 @@ +export type TBrand = { + brandId: number; + brand: string | null; + isStock: boolean; +}; \ No newline at end of file diff --git a/packages/database/src/models/DamageInfo.ts b/packages/database/src/models/DamageInfo.ts new file mode 100644 index 000000000..58a2d9bb5 --- /dev/null +++ b/packages/database/src/models/DamageInfo.ts @@ -0,0 +1,66 @@ +import { getServerLogger } from "rusty-motors-shared"; + +const DAMAGE_VERSION_DEBUG = 3; +const DAMAGE_INFO_SIZE = 24; + +export class DamageInfo { + // The MrC version number used. This is 3 in the debug version of the game. + private _versionNumber: number = DAMAGE_VERSION_DEBUG; // 2 bytes + private _compressed: boolean = false; // 2 bytes + private _datasizeCompressed: number = 0; // 4 bytes + private _datasizeUncompressed: number = 0; // 4 bytes + private _governumber: number = 0; // 4 bytes + private _numberVertices: number = 0; // 4 bytes + private _decalFlags: number = 0; // 4 bytes + + private _compressedData: Buffer = Buffer.alloc(0); // buffer, max DAMAGE_INFO_SIZE + + compress() { + this._compressed = true; + } + + uncompress() { + if (!this._compressed) { + getServerLogger("transactions/CarInfoStruct").warn("DamageInfo is already uncompressed"); + return; + } + + if (this!._versionNumber !== DAMAGE_VERSION_DEBUG) { + getServerLogger("transactions/CarInfoStruct").warn("DamageInfo is not the debug version"); + return; + } + + if (this._compressedData.length !== this._datasizeCompressed) { + getServerLogger("transactions/CarInfoStruct").warn("DamageInfo compressed data size does not match datasizeCompressed"); + return; + } + + this._compressed = false; + } + + serialize() { + try { + const buffer = Buffer.alloc(this.size()); + buffer.writeInt16LE(this._versionNumber, 0); // offset 0 + buffer.writeInt16LE(this._compressed ? 1 : 0, 2); // offset 2 + buffer.writeInt32LE(this._datasizeCompressed, 4); // offset 4 + buffer.writeInt32LE(this._datasizeUncompressed, 8); // offset 8 + buffer.writeInt32LE(this._governumber, 12); // offset 12 + buffer.writeInt32LE(this._numberVertices, 16); // offset 16 + buffer.writeInt32LE(this._decalFlags, 20); // offset 20 + if (this._compressedData.length > 0) { + this._compressedData.copy(buffer, 24); // offset 24 + } + return buffer; + } catch (error) { + getServerLogger("transactions/CarInfoStruct").error( + `Error in DamageInfo.serialize: ${error}`, + ); + throw error; + } + } + + size() { + return 24 + this._compressedData.length; + } + } diff --git a/packages/database/src/models/Part.ts b/packages/database/src/models/Part.ts new file mode 100644 index 000000000..a456010e5 --- /dev/null +++ b/packages/database/src/models/Part.ts @@ -0,0 +1,12 @@ +export type TPart = { + part_id: number; + parent_part_id: number | null; + branded_part_id: number; + percent_damage: number; + item_wear: number; + attachment_point_id: number | null; + owner_id: number | null; + part_name: string | null; + repair_cost: number; + scrap_value: number; +}; \ No newline at end of file diff --git a/packages/database/src/models/VehiclePartTree.ts b/packages/database/src/models/VehiclePartTree.ts new file mode 100644 index 000000000..f3e583a5e --- /dev/null +++ b/packages/database/src/models/VehiclePartTree.ts @@ -0,0 +1,639 @@ +import { getServerLogger } from 'rusty-motors-shared'; +import * as Sentry from '@sentry/node'; +import { setVehiclePartTree } from '../cache.js'; +import { getSlonik } from '../services/database.js'; +import type { TPart } from './Part.js'; + +const level1PartTypes = [1001, 2001, 4001, 5001, 6001, 15001, 36001, 37001]; + +const partNumbersMap = new Map(); + +const log = getServerLogger(); + +export type TVehicle = { + vehicle_id: number; + skin_id: number; + flags: number; + class: number; + info_setting: number; + damage_info: Buffer | null; +}; + +export type VehiclePartTreeType = { + vehicleId: number; + skinId: number; + flags: number; + class: number; + infoSetting: number; + damageInfo: Buffer | null; + isStock: boolean; + // One of the following two properties is required + ownedLotId: number | null; + ownerID: number | null; + partId: number; + parentPartId: null; + brandedPartId: number; + partTree: { + level1: { + partId: number; + parts: TPart[]; + }; + level2: { + partId: number; + parts: TPart[]; + }; + }; +}; + +export function vehiclePartTreeToJSON( + vehiclePartTree: VehiclePartTreeType, +): string { + const level1Parts = vehiclePartTree.partTree.level1.parts.map((part) => ({ + partId: part.part_id, + parentPartId: part.parent_part_id, + brandedPartId: part.branded_part_id, + percentDamage: part.percent_damage, + itemWear: part.item_wear, + attachmentPointId: part.attachment_point_id, + ownerId: part.owner_id, + partName: part.part_name, + repairCost: part.repair_cost, + scrapValue: part.scrap_value, + })); + + const level2Parts = vehiclePartTree.partTree.level2.parts.map((part) => ({ + partId: part.part_id, + parentPartId: part.parent_part_id, + brandedPartId: part.branded_part_id, + percentDamage: part.percent_damage, + itemWear: part.item_wear, + attachmentPointId: part.attachment_point_id, + ownerId: part.owner_id, + partName: part.part_name, + repairCost: part.repair_cost, + scrapValue: part.scrap_value, + })); + + return JSON.stringify({ + vehicleId: vehiclePartTree.vehicleId, + skinId: vehiclePartTree.skinId, + flags: vehiclePartTree.flags, + class: vehiclePartTree.class, + infoSetting: vehiclePartTree.infoSetting, + damageInfo: vehiclePartTree.damageInfo, + isStock: vehiclePartTree.isStock, + ownedLotId: vehiclePartTree.ownedLotId, + ownerID: vehiclePartTree.ownerID, + partId: vehiclePartTree.partId, + parentPartId: vehiclePartTree.parentPartId, + brandedPartId: vehiclePartTree.brandedPartId, + partTree: { + level1: { + partId: vehiclePartTree.partTree.level1.partId, + parts: level1Parts, + }, + level2: { + partId: vehiclePartTree.partTree.level2.partId, + parts: level2Parts, + }, + }, + }); +} + +async function getNextPartId(): Promise { + const result = await Sentry.startSpan( + { + name: 'Get next part id', + op: 'db.query', + attributes: { + sql: "SELECT nextval('part_partid_seq')", + db: 'postgres', + }, + }, + async () => { + const { slonik, sql } = await getSlonik(); + const { nextval } = await slonik.one(sql.typeAlias('nextPartId')` + SELECT nextval('part_partid_seq') + `); + return Number(nextval); + }, + ); + return result; +} + +export async function savePart(part: TPart): Promise { + await Sentry.startSpan( + { + name: 'Save part', + op: 'db.query', + attributes: { + sql: 'INSERT INTO part (partid, parentpartid, brandedpartid, percentdamage, itemwear, attachmentpointid, ownerid, partname, repaircost, scrapvalue) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)', + db: 'postgres', + }, + }, + async () => { + const { slonik, sql } = await getSlonik(); + return slonik.query(sql.typeAlias('dbPart')` + INSERT INTO part ( + part_id, + parent_part_id, + branded_part_id, + percent_damage, + item_wear, + attachment_point_id, + owner_id, + part_name, + repair_cost, + scrap_value + ) VALUES ( + ${part.part_id}, + ${part.parent_part_id}, + ${part.branded_part_id}, + ${part.percent_damage}, + ${part.item_wear}, + ${part.attachment_point_id}, + ${part.owner_id}, + ${part.part_name}, + ${part.repair_cost}, + ${part.scrap_value} + ) + `); + }, + ); +} + +export async function saveVehicle( + vehiclePartTree: VehiclePartTreeType, +): Promise { + try { + const vehiclePart: TPart = { + part_id: vehiclePartTree.vehicleId, + parent_part_id: null, + branded_part_id: vehiclePartTree.brandedPartId, + percent_damage: 0, + item_wear: 0, + attachment_point_id: null, + owner_id: vehiclePartTree.ownerID || null, + part_name: null, + repair_cost: 0, + scrap_value: 0, + }; + + log.debug(`Saving vehicle part: ${JSON.stringify(vehiclePart)}`); + await savePart(vehiclePart).catch((error) => { + log.error(`Error saving vehicle part: ${error}`); + const e = new Error(`Error saving vehicle part: ${error}`); + e.cause = error; + throw e; + }); + + const newVehicle: TVehicle = { + vehicle_id: vehiclePartTree.vehicleId, + skin_id: vehiclePartTree.skinId, + flags: vehiclePartTree.flags, + class: vehiclePartTree.class, + info_setting: vehiclePartTree.infoSetting, + damage_info: vehiclePartTree.damageInfo, + }; + + log.debug(`Saving vehicle: ${JSON.stringify(newVehicle)}`); + + await Sentry.startSpan( + { + name: 'Save vehicle', + op: 'db.query', + attributes: { + sql: 'INSERT INTO vehicle (vehicleid, skinid, flags, class, infosetting, damageinfo) VALUES ($1, $2, $3, $4, $5, $6)', + db: 'postgres', + }, + }, + async () => { + const { slonik, sql } = await getSlonik(); + return slonik.query(sql.typeAlias('vehicle')` + INSERT INTO vehicle ( + vehicle_id, + skin_id, + flags, + class, + info_setting, + damage_info + ) VALUES ( + ${newVehicle.vehicle_id}, + ${newVehicle.skin_id}, + ${newVehicle.flags}, + ${newVehicle.class}, + ${newVehicle.info_setting}, + ${newVehicle.damage_info} + ) + `); + }, + ).catch((error) => { + log.error(`Error saving vehicle(db): ${error}`); + const e = new Error(`Error saving vehicle: ${error}`); + e.cause = error; + throw e; + } + ); + } catch (error) { + log.error(`Error saving vehicle: ${error}`); + throw error; + } +} + +export async function saveVehiclePartTree( + vehiclePartTree: VehiclePartTreeType, +): Promise { + try { + const partIds = new Set(); + + const partTree = vehiclePartTree.partTree; + + for (const part of partTree.level1.parts) { + partIds.add(part.part_id); + } + + for (const part of partTree.level2.parts) { + partIds.add(part.part_id); + } + + for (const partId of partIds) { + const part = + partTree.level1.parts.find((p) => p.part_id === partId) || + partTree.level2.parts.find((p) => p.part_id === partId); + if (!part) { + log.error(`Part with partId ${partId} not found`); + throw new Error(`Part with partId ${partId} not found`); + } + await savePart(part); + } + + // Save the vehicle part tree in the cache + setVehiclePartTree(vehiclePartTree.vehicleId, vehiclePartTree); + } catch (error) { + log.error(`Error saving vehicle part tree: ${error}`); + throw error; + } +} + +export async function buildVehiclePartTreeFromDB( + vehicleId: number, +): Promise { + const vehicle = await Sentry.startSpan( + { + name: 'Get vehicle', + op: 'db.query', + attributes: { + sql: 'SELECT vehicleid, skinid, flags, class, infosetting, damageinfo FROM vehicle WHERE vehicleid = $1', + db: 'postgres', + }, + }, + async () => { + const { slonik, sql } = await getSlonik(); + return slonik.one(sql.typeAlias('vehicle')` + SELECT vehicleid, skinid, flags, class, infosetting, damageinfo + FROM vehicle + WHERE vehicleid = ${vehicleId} + `); + }, + ); + + if (!vehicle) { + log.error(`Vehicle with id ${vehicleId} does not exist`); + throw new Error(`Vehicle with id ${vehicleId} does not exist`); + } + + const vehiclePartTree: VehiclePartTreeType = { + vehicleId: vehicle.vehicleid, + skinId: vehicle.skinid, + flags: vehicle.flags, + class: vehicle.class, + infoSetting: vehicle.infosetting, + damageInfo: vehicle.damageinfo, + isStock: false, + ownedLotId: null, + ownerID: null, + partId: vehicle.vehicleid, + parentPartId: null, + brandedPartId: 0, + partTree: { + level1: { + partId: 0, + parts: [], + }, + level2: { + partId: 0, + parts: [], + }, + }, + }; + + // Get first part + const part = await Sentry.startSpan( + { + name: 'Get part', + op: 'db.query', + attributes: { + sql: 'SELECT partid, parentpartid, brandedpartid, percentdamage, itemwear, attachmentpointid, ownerid, partname, repaircost, scrapvalue FROM part WHERE partid = $1', + db: 'postgres', + }, + }, + async () => { + const { slonik, sql } = await getSlonik(); + return slonik.one(sql.typeAlias('part')` + SELECT partid, parentpartid, brandedpartid, percentdamage, itemwear, attachmentpointid, ownerid, partname, repaircost, scrapvalue + FROM part + WHERE partid = ${vehicleId} + `); + }, + ); + + if (!part) { + log.error(`Part with id ${vehicleId} does not exist`); + throw new Error(`Part with id ${vehicleId} does not exist`); + } + + vehiclePartTree.brandedPartId = part.brandedpartid; + vehiclePartTree.ownerID = part.ownerid; + + const level1Parts = await Sentry.startSpan( + { + name: 'Get level 1 parts', + op: 'db.query', + attributes: { + sql: 'SELECT partid, parentpartid, brandedpartid, percentdamage, itemwear, attachmentpointid, ownerid, partname, repaircost, scrapvalue FROM part WHERE parentpartid = $1', + db: 'postgres', + }, + }, + async () => { + const { slonik, sql } = await getSlonik(); + return slonik.many(sql.typeAlias('dbPart')` + SELECT partid, parentpartid, brandedpartid, percentdamage, itemwear, attachmentpointid, ownerid, partname, repaircost, scrapvalue + FROM part + WHERE parentpartid = ${vehicleId} + `); + }, + ); + + if (level1Parts.length === 0) { + log.error(`Vehicle with id ${vehicleId} has no parts`); + throw new Error(`Vehicle with id ${vehicleId} has no parts`); + } + + log.debug(`We got parts!`); + log.debug( + `There are ${level1Parts.length} level 1 parts in the vehicle assembly`, + ); + + const level1PartsIds = level1Parts.map((part) => part.partid); + + log.debug(`level1PartsIds: ${level1PartsIds}`); + + for (const part of level1Parts) { + log.debug( + `Adding part: ${JSON.stringify(part)} to vehicle part tree level 1`, + ); + + const newPart: TPart = { + part_id: part.partid, + parent_part_id: part.parentpartid, + branded_part_id: part.brandedpartid, + percent_damage: part.percentdamage, + item_wear: part.itemwear, + attachment_point_id: part.attachmentpointid, + owner_id: part.ownerid, + part_name: part.partname, + repair_cost: part.repaircost, + scrap_value: part.scrapvalue, + }; + + vehiclePartTree.partTree.level1.parts.push(newPart); + } + + const level2Parts = await Sentry.startSpan( + { + name: 'Get level 2 parts', + op: 'db.query', + attributes: { + sql: 'SELECT partid, parentpartid, brandedpartid, percentdamage, itemwear, attachmentpointid, ownerid, partname, repaircost, scrapvalue FROM part WHERE parentpartid IN ($1)', + db: 'postgres', + }, + }, + async () => { + const { slonik, sql } = await getSlonik(); + return slonik.many(sql.typeAlias('dbPart')` + SELECT partid, parentpartid, brandedpartid, percentdamage, itemwear, attachmentpointid, ownerid, partname, repaircost, scrapvalue + FROM part + WHERE parentpartid IN (${sql.join(level1PartsIds, sql.fragment`, `)}) + `); + }, + ); + + if (level2Parts.length === 0) { + log.error(`Vehicle with id ${vehicleId} has no level 2 parts`); + throw new Error(`Vehicle with id ${vehicleId} has no level 2 parts`); + } + + log.debug(`We got parts!`); + log.debug( + `There are ${level2Parts.length} level 2 parts in the vehicle assembly`, + ); + + for (const part of level2Parts) { + const newPart: TPart = { + part_id: part.partid, + parent_part_id: part.parentpartid, + branded_part_id: part.brandedpartid, + percent_damage: part.percentdamage, + item_wear: part.itemwear, + attachment_point_id: part.attachmentpointid, + owner_id: part.ownerid, + part_name: part.partname, + repair_cost: part.repaircost, + scrap_value: part.scrapvalue, + }; + + vehiclePartTree.partTree.level2.parts.push(newPart); + } + + log.debug(`Vehicle part tree populated`); + log.debug(`Vehicle part tree: ${JSON.stringify(vehiclePartTree)}`); + + setVehiclePartTree(vehiclePartTree.vehicleId, vehiclePartTree); + + return vehiclePartTree; +} + +export async function buildVehiclePartTree({ + brandedPartId, + skinId, + ownedLotId, + ownerID, + isStock, +}: { + brandedPartId: number; + skinId: number; + ownedLotId?: number; + ownerID?: number; + isStock: boolean; +}): Promise { + if (ownedLotId === undefined && ownerID === undefined) { + log.error(`ownedLotId or ownerID is required`); + throw new Error('ownedLotId or ownerID is required'); + } + + const skinFlags = await Sentry.startSpan( + { + name: 'Get skin flags', + op: 'db.query', + attributes: { + sql: 'SELECT default_flag FROM pt_skin WHERE skin_id = $1', + db: 'postgres', + }, + }, + async () => { + const { slonik, sql } = await getSlonik(); + return slonik.one(sql.typeAlias('ptSkin')` + SELECT default_flag + FROM pt_skin + WHERE skin_id = ${skinId} + `); + }, + ) as { default_flag: number }; + + if (!skinFlags) { + log.error(`Skin with id ${skinId} does not exist`); + throw new Error(`Skin with id ${skinId} does not exist`); + } + + // Get the vehicle assembly from the database + const vehicleAssembly = await Sentry.startSpan( + { + name: 'Get vehicle assembly', + op: 'db.query', + attributes: { + sql: 'SELECT bp.branded_part_id, bp.part_type_id, a.attachment_point_id, pt.abstract_part_type_id, apt.parent_abstract_part_type_id FROM stock_assembly a INNER JOIN branded_part bp ON a.child_branded_part_id = bp.branded_part_id inner join part_type pt on pt.part_type_id = bp.part_type_id inner join abstract_part_type apt on apt.abstract_part_type_id = pt.abstract_part_type_id WHERE a.parent_branded_part_id = $1', + db: 'postgres', + }, + }, + async () => { + log.debug(`Getting vehicle assembly for vehicle with branded part id ${brandedPartId}`); + const { slonik, sql } = await getSlonik(); + return slonik.many(sql.typeAlias('detailedPart')` + SELECT bp.branded_part_id, bp.part_type_id, a.attachment_point_id, pt.abstract_part_type_id, apt.parent_abstract_part_type_id + FROM stock_assembly a + INNER JOIN branded_part bp ON a.child_branded_part_id = bp.branded_part_id + inner join part_type pt on pt.part_type_id = bp.part_type_id + inner join abstract_part_type apt on apt.abstract_part_type_id = pt.abstract_part_type_id + WHERE a.parent_branded_part_id = ${brandedPartId} + `); + }, + ) as { + branded_part_id: number; + part_type_id: number; + abstract_part_type_id: number; + parent_abstract_part_type_id: number; + attachment_point_id: number; + }[]; + + if (vehicleAssembly.length === 0) { + log.error( + `Vehicle assembly with branded part id ${brandedPartId} does not exist`, + ); + throw new Error( + `Vehicle assembly with branded part id ${brandedPartId}`, + ); + } + + // But we did get parts, right? + log.debug(`We got parts!`); + log.debug( + `There are ${vehicleAssembly.length} parts in the vehicle assembly`, + ); + + const topPartId = await getNextPartId(); + + partNumbersMap.set(101, topPartId); + + const vehiclePartTree: VehiclePartTreeType = { + vehicleId: topPartId, + skinId, + isStock, + flags: skinFlags.default_flag, + class: 0, + infoSetting: 0, + damageInfo: null, + ownedLotId: ownedLotId || null, + ownerID: ownerID || null, + partId: topPartId, + parentPartId: null, + brandedPartId, + partTree: { + level1: { + partId: 0, + parts: [], + }, + level2: { + partId: 0, + parts: [], + }, + }, + }; + + log.debug(`Vehicle part tree created`); + log.debug(`Vehicle part tree: ${JSON.stringify(vehiclePartTree)}`); + + // Populate the vehicle part tree + for (const part of vehicleAssembly) { + const parentPartId = partNumbersMap.get(part.parent_abstract_part_type_id); + + log.debug( + `parentAbstractPartTypeId: ${part.parent_abstract_part_type_id}, parentPartId: ${parentPartId}`, + ); + + if (parentPartId === undefined) { + log.error( + `parentPartId is undefined for part with parentabstractparttypeid ${part.parent_abstract_part_type_id}`, + ); + throw new Error( + `parentPartId is undefined for part with parentabstractparttypeid ${part.parent_abstract_part_type_id}`, + ); + } + + const thisPartId = await getNextPartId(); + + if (!partNumbersMap.has(part.abstract_part_type_id)) { + partNumbersMap.set(part.abstract_part_type_id, thisPartId); + } + + const newPart: TPart = { + part_id: thisPartId, + parent_part_id: parentPartId, + branded_part_id: part.branded_part_id, + percent_damage: 0, + item_wear: 0, + attachment_point_id: part.attachment_point_id, + owner_id: ownerID || null, + part_name: null, + repair_cost: 0, + scrap_value: 0, + }; + + const partDepth = level1PartTypes.includes(part.abstract_part_type_id) + ? 1 + : 2; + + if (partDepth === 1) { + vehiclePartTree.partTree.level1.parts.push(newPart); + } else if (partDepth === 2) { + vehiclePartTree.partTree.level2.parts.push(newPart); + } else { + log.error(`Part depth ${partDepth} is not supported`); + throw new Error(`Part depth ${partDepth} is not supported`); + } + } + + log.debug(`Vehicle part tree populated`); + log.debug(`Vehicle part tree: ${JSON.stringify(vehiclePartTree)}`); + + return vehiclePartTree; +} diff --git a/packages/database/src/services/database.ts b/packages/database/src/services/database.ts new file mode 100644 index 000000000..1e6cdac2c --- /dev/null +++ b/packages/database/src/services/database.ts @@ -0,0 +1,101 @@ +import { createPool, type DatabasePool, createSqlTag } from "slonik"; +import { z } from "zod"; + +let slonik: DatabasePool; +let sql: ReturnType; + +function initSQL() { + return createSqlTag({ + typeAliases: { + vehicleWithOwner: z.object({ + vehicle_id: z.number(), + skin_id: z.number(), + flags: z.number(), + class: z.number(), + damage_info: z.instanceof(Buffer) || z.null(), + owner_id: z.number(), + }), + dbPart: z.object({ + part_id: z.number(), + parent_part_id: z.number(), + branded_part_id: z.number(), + percent_damage: z.number(), + item_wear: z.number(), + attachment_point_id: z.number(), + owner_id: z.number(), + part_name: z.string() || z.null(), + repair_cost: z.number(), + scrap_value: z.number(), + }), + id: z.number(), + brandedPart: z.object({ + part_id: z.number() || z.null(), + parent_part_id: z.number() || z.null(), + branded_part_id: z.number() || z.null(), + attachment_point_id: z.number() || z.null(), + }), + part: z.object({ + part_id: z.number(), + parent_part_id: z.number(), + branded_part_id: z.number(), + percent_damage: z.number(), + item_wear: z.number(), + attachment_point_id: z.number(), + part_name: z.string() || z.null(), + owner_id: z.number(), + }), + abstractPartType: z.object({ + abstract_part_type_id: z.number(), + }), + ptSkin: z.object({ + skin_id: z.number(), + default_flag: z.number(), + }), + nextPartId: z.object({ + next_val: z.bigint(), + }), + detailedPart: z.object({ + branded_part_id: z.number(), + part_type_id: z.number(), + abstract_part_type_id: z.number(), + parent_abstract_part_type_id: z.number(), + attachment_point_id: z.number(), + }), + vehicle: z.object({ + vehicle_id: z.number(), + skin_id: z.number(), + flags: z.number(), + class: z.number(), + info_setting: z.number(), + damage_info: z.instanceof(Buffer) || z.null(), + }), + brand: z.object({ + brand_id: z.number(), + brand: z.string(), + is_stock: z.boolean(), + }), + }, + }); +} + +export async function getSlonik(): Promise<{ + slonik: DatabasePool; + sql: ReturnType; +}> { + if (typeof process.env["DATABASE_URL"] === "undefined") { + throw Error("Please set DATABASE_URL in your env.") + } + + if (!slonik) { + slonik = await createPool(process.env["DATABASE_URL"]!); + } + if (!sql) { + sql = initSQL(); + } + return { slonik, sql }; +} + +export async function getDatabase() { + const { slonik, sql } = await getSlonik(); + return { slonik, sql }; +} \ No newline at end of file diff --git a/packages/database/src/services/roomServers.ts b/packages/database/src/services/roomServers.ts new file mode 100644 index 000000000..ac6bfa648 --- /dev/null +++ b/packages/database/src/services/roomServers.ts @@ -0,0 +1,41 @@ +import type { RoomServer } from "@rustymotors/roomserver"; + +const roomServers: Set = new Set(); +/** + * Adds a room server to the set of room servers. + * + * @param roomServer - The room server to add. + */ +export function addRoomServer(roomServer: RoomServer): void { + roomServers.add(roomServer); +} + +/** + * Gets a room server by its port. + * + * @param port - The port of the room server to get. + * @returns The room server with the specified port, or undefined if no room server has that port. + */ +export function getRoomServerByPort(port: number): RoomServer | undefined { + return Array.from(roomServers).find((server) => server.port === port); +} + +/** + * Gets a room server by its name. + * + * @param name - The name of the room server to get. + * @returns The room server with the specified name, or undefined if no room server has that name. + */ +export function getRoomServerByName(name: string): RoomServer | undefined { + return Array.from(roomServers).find((server) => server.name === name); +} + +/** + * Gets a room server by its id. + * + * @param id - The id of the room server to get. + * @returns The room server with the specified id, or undefined if no room server has that id. + */ +export function getRoomServerById(id: number): RoomServer | undefined { + return Array.from(roomServers).find((server) => server.id === id); +} diff --git a/packages/gateway/package.json b/packages/gateway/package.json index 3a8b9eff4..87cc0ae55 100644 --- a/packages/gateway/package.json +++ b/packages/gateway/package.json @@ -21,7 +21,9 @@ "dependencies": { "@fastify/sensible": "^6.0.3", "@rustymotors/binary": "workspace:^", - "fastify": "^5.2.2" + "@rustymotors/protocol": "workspace:^", + "fastify": "^5.2.2", + "@rustymotors/roomserver": "workspace:^" }, "directories": { "test": "test" diff --git a/packages/gateway/src/GatewayServer.ts b/packages/gateway/src/GatewayServer.ts index 8f1256b86..3f86b0ea2 100644 --- a/packages/gateway/src/GatewayServer.ts +++ b/packages/gateway/src/GatewayServer.ts @@ -39,7 +39,7 @@ export class Gateway { */ constructor({ config = getServerConfiguration(), - log = getServerLogger("Gateway"), + log = getServerLogger("Gateway", "gateway"), backlogAllowedCount = 0, listeningPortList = [], socketConnectionHandler = onSocketConnection, @@ -203,6 +203,7 @@ export class Gateway { addPortRouter(8227, npsPortRouter); addPortRouter(8228, npsPortRouter); addPortRouter(7003, npsPortRouter); + addPortRouter(9001, npsPortRouter); addPortRouter(43300, mcotsPortRouter); process.on("SIGINT", this.exit.bind(this)); diff --git a/packages/gateway/src/HotkeyManager.ts b/packages/gateway/src/HotkeyManager.ts index e700b6c98..79c3eca3d 100644 --- a/packages/gateway/src/HotkeyManager.ts +++ b/packages/gateway/src/HotkeyManager.ts @@ -34,7 +34,7 @@ export class HotkeyManager { case "g": this.greetUser(); break; - case "e": + case "x": this.exit(); break; default: @@ -48,7 +48,7 @@ export class HotkeyManager { console.log("Available hotkeys:"); console.log(" h - Show this help message"); console.log(" g - Greet the user"); - console.log(" e - Exit the program"); + console.log(" x - Exit the program"); } private greetUser(): void { diff --git a/packages/gateway/src/index.ts b/packages/gateway/src/index.ts index 4c2bc3b6a..4a3fd3a81 100644 --- a/packages/gateway/src/index.ts +++ b/packages/gateway/src/index.ts @@ -42,7 +42,9 @@ export function onSocketConnection({ // If the local port or remote address is undefined, throw an error if (localPort === undefined || remoteAddress === undefined) { const s = JSON.stringify(incomingSocket); - throw Error("localPort or remoteAddress is undefined: " + s); + log.error(`localPort or remoteAddress is undefined: ${s}`); + incomingSocket.end(); + return; } const socketWithId = tagSocket( diff --git a/packages/gateway/src/mcotsPortRouter.ts b/packages/gateway/src/mcotsPortRouter.ts index ac320ea34..d70ca1672 100644 --- a/packages/gateway/src/mcotsPortRouter.ts +++ b/packages/gateway/src/mcotsPortRouter.ts @@ -6,6 +6,7 @@ import { import { receiveTransactionsData } from "rusty-motors-transactions"; import * as Sentry from "@sentry/node"; import { getServerLogger, ServerLogger } from "rusty-motors-shared"; +import { Socket } from 'net'; /** * Handles the routing of messages for the MCOTS (Motor City Online Transaction Server) ports. @@ -14,55 +15,130 @@ import { getServerLogger, ServerLogger } from "rusty-motors-shared"; */ export async function mcotsPortRouter({ - taggedSocket, - log = getServerLogger("gateway.mcotsPortRouter"), + taggedSocket, + log = getServerLogger('gateway.mcotsPortRouter'), }: { - taggedSocket: TaggedSocket; - log?: ServerLogger; + taggedSocket: TaggedSocket; + log?: ServerLogger; }): Promise { - const { rawSocket: socket, connectionId: id } = taggedSocket; + const { rawSocket: socket, connectionId: id } = taggedSocket; - const port = socket.localPort || 0; + const port = socket.localPort || 0; - if (port === 0) { - log.error(`[${id}] Local port is undefined`); - socket.end(); - return; - } + if (port === 0) { + log.error(`[${id}] Local port is undefined`); + socket.end(); + return; + } - log.debug(`[${id}] MCOTS port router started for port ${port}`); - - // Handle the socket connection here - socket.on("data", async (data) => { - try { - log.debug(`[${id}] Received data: ${data.toString("hex")}`); - const initialPacket = parseInitialMessage(data); - log.debug(`[${id}] Initial packet(str): ${initialPacket}`); - log.debug(`[${id}] initial Packet(hex): ${initialPacket.toHexString()}`); - await routeInitialMessage(id, port, initialPacket) - .then((response) => { - // Send the response back to the client - log.debug(`[${id}] Sending response: ${response.toString("hex")}`); - socket.write(response); - }) - .catch((error) => { - throw new Error(`[${id}] Error routing initial mcots message: ${error}`, { - cause: error, - }); - }); - } catch (error) { - Sentry.captureException(error); - log.error(`[${id}] Error handling data: ${error}`); - } - }); - - socket.on("end", () => { - // log.debug(`[${id}] Socket closed by client for port ${port}`); - }); - - socket.on("error", (error) => { - log.error(`[${id}] Socket error: ${error}`); - }); + log.debug(`[${id}] MCOTS port router started for port ${port}`); + + // Handle the socket connection here + socket.on('data', async (data) => { + await processIncomingPackets(data, log, id, port, socket); + }); + + socket.on('end', () => { + // log.debug(`[${id}] Socket closed by client for port ${port}`); + }); + + socket.on('error', (error) => { + if (error.message.includes('ECONNRESET')) { + log.debug(`[${id}] Connection reset by client`); + return; + } + log.error(`[${id}] Socket error: ${error}`); + }); +} + +function findPackageSignatureIndices(data: Buffer): number[] { + const packageSignature = Buffer.from('544f4d43', 'hex'); + const packageSignatureIndices: number[] = []; + let index = 0; + let currentIndex = 0; + + while (index !== -1) { + index = data.indexOf(packageSignature, currentIndex); + if (index !== -1) { + packageSignatureIndices.push(index); + currentIndex = index + 1; + } + } + + return packageSignatureIndices; +} + +async function processIncomingPackets( + data: Buffer, + log: ServerLogger, + id: string, + port: number, + socket: Socket, +) { + try { + let inPackets: Buffer[] = []; + + log.debug( + `[${id}] Received data in processIncomingPackets: ${data.toString('hex')}`, + ); + + /* Search for the package signature in the hex string + * If found, split the data into packets + * Each packet starts with the 2 bytes (16 bits) length of the packet + * followed by the 4 bytes package signature + */ + let indices = findPackageSignatureIndices(data); + + for (let indexOfPackageSignature of indices) { + let length = data.readUInt16LE(indexOfPackageSignature - 2); + let packet = data.subarray( + indexOfPackageSignature - 2, + indexOfPackageSignature + length, + ); + log.debug({ + connectionId: id, + port, + length: length, + lengthAction: packet.length, + }, `Packet(hex): ${packet.toString('hex')}`); + inPackets.push(packet); + } + + log.debug(`[${id}] Received ${inPackets.length} packets`); + + for (let packet of inPackets) { + log.debug(`[${id}] Received data: ${packet.toString('hex')}`); + const initialPacket = parseInitialMessage(packet); + log.debug( + { + connectionId: id, + port, + seq: initialPacket.getSequence(), + length: initialPacket.getLength(), + }, + `initial Packet(hex): ${initialPacket.toHexString()}`, + ); + await routeInitialMessage(id, port, initialPacket) + .then((response) => { + // Send the response back to the client + log.debug( + `[${id}] Sending response: ${response.toString('hex')}`, + ); + socket.write(response); + }) + .catch(error => { + throw new Error( + `[${id}] Error routing initial mcots message: ${error}`, + { + cause: error, + }, + ); + }); + } + } catch (error) { + Sentry.captureException(error); + log.error(`[${id}] Error handling data: ${error}`); + } } function parseInitialMessage(data: Buffer): ServerPacket { @@ -94,7 +170,11 @@ async function routeInitialMessage( ).messages; break; default: - console.log(`No handler found for port ${port}`); + log.warn({ + connectionId: id, + port, + initialPacket: initialPacket.toHexString(), + }, `WARN: No handler found for port ${port}`); break; } diff --git a/packages/gateway/src/npsPortRouter.ts b/packages/gateway/src/npsPortRouter.ts index 45c94415a..d7d99ff81 100644 --- a/packages/gateway/src/npsPortRouter.ts +++ b/packages/gateway/src/npsPortRouter.ts @@ -4,12 +4,27 @@ import { type SerializableInterface, } from "rusty-motors-shared-packets"; import { receiveLobbyData } from "rusty-motors-lobby"; -import { receiveChatData } from "rusty-motors-chat"; import { receivePersonaData } from "rusty-motors-personas"; import { receiveLoginData } from "rusty-motors-login"; +import { + getServerConfiguration, + getServerLogger, + ServerLogger, +} from "rusty-motors-shared"; +import { BytableMessage, createRawMessage } from "@rustymotors/binary"; +import { RoomServer } from "@rustymotors/roomserver"; +import { addRoomServer, getRoomServerByPort } from "rusty-motors-database"; +import { splitPackets } from "./utility.js"; import * as Sentry from "@sentry/node"; -import { getServerLogger, ServerLogger } from "rusty-motors-shared"; -import { BytableMessage } from "@rustymotors/binary"; + +const server01 = new RoomServer({ + id: 224, + name: "MCC01", + ip: getServerConfiguration().host, + port: 9001, +}); +addRoomServer(server01); + /** * Handles routing for the NPS (Network Play System) ports. @@ -19,7 +34,7 @@ import { BytableMessage } from "@rustymotors/binary"; export async function npsPortRouter({ taggedSocket, - log = getServerLogger("gateway.npsPortRouter"), + log = getServerLogger("gateway.npsPortRouter", "gateway"), }: { taggedSocket: TaggedSocket; log?: ServerLogger; @@ -35,35 +50,67 @@ export async function npsPortRouter({ } log.debug(`[${id}] NPS port router started for port ${port}`); + // getMCOProtocolInstance().acceptIncomingSocket({ + // connectionId: id, + // port, + // socket, + // }); + + // return; + if (port === 7003) { // Sent ok to login packet log.debug(`[${id}] Sending ok to login packet`); - socket.write(Buffer.from([0x02, 0x30, 0x00, 0x00])); + socket.write(Buffer.from([0x02, 0x30, 0x00, 0x04])); } // Handle the socket connection here socket.on("data", async (data) => { try { log.debug(`[${id}] Received data: ${data.toString("hex")}`); - const initialPacket = parseInitialMessage(data); - log.debug(`[${id}] Initial packet(str): ${initialPacket}`); - log.debug(`[${id}] initial Packet(hex): ${initialPacket.toString()}`); - await routeInitialMessage(id, port, initialPacket) - .then((response) => { - // Send the response back to the client - log.debug( - `[${id}] Sending response to socket: ${response.toString("hex")}`, - ); - socket.write(response); - }) - .catch((error) => { - throw new Error( - `[${id}] Error routing initial nps message: ${error}`, - { - cause: error, - }, - ); - }); + log.debug(`[${id}] Data length: ${data.length}`); + + let packets: Buffer[] = []; + + const separator = Buffer.from([0x11, 0x01]); + // Count the number of packets + const packetCount = data.toString("hex").split(separator.toString("hex")) + .length - 1; + log.debug(`[${id}] Number of packets: ${packetCount}`); + if (packetCount > 1) { + log.debug(`[${id}] More than one packet detected`); + // Split the packets + packets = splitPackets(data, separator); + log.debug( + `[${id}] Split packets: ${packets.map((p) => p.toString("hex"))}`, + ); + } else { + log.debug(`[${id}] One packet detected`); + // No need to split the packets + packets = [data]; + } + + for (const packet of packets) { + const initialPacket = parseInitialMessage(packet); + log.debug(`[${id}] Initial packet(str): ${initialPacket}`); + log.debug(`[${id}] initial Packet(hex): ${initialPacket.toString()}`); + await routeInitialMessage(id, port, initialPacket) + .then((response) => { + // Send the response back to the client + log.debug( + `[${id}] Sending response to socket: ${response.toString("hex")}`, + ); + socket.write(response); + }) + .catch((error) => { + throw new Error( + `[${id}] Error routing initial nps message: ${error}`, + { + cause: error, + }, + ); + }); + } } catch (error) { if (error instanceof RangeError) { log.warn(`[${id}] Error parsing initial nps message: ${error}`); @@ -73,33 +120,47 @@ export async function npsPortRouter({ } } }); - + socket.on("end", () => { // log.debug(`[${id}] Socket closed by client for port ${port}`); }); - + socket.on("error", (error) => { + if (error.message.includes("ECONNRESET")) { + log.debug(`[${id}] Connection reset by client`); + return; + } log.error(`[${id}] Socket error: ${error}`); }); } /** * Parses the initial message from a buffer and returns a `GamePacket` object. -* -* @param data - The buffer containing the initial message data. -* @returns A `GamePacket` object deserialized from the buffer. -*/ + * + * @param data - The buffer containing the initial message data. + * @returns A `GamePacket` object deserialized from the buffer. + */ function parseInitialMessage(data: Buffer): BytableMessage { try { - const message = new BytableMessage(0); + const message = createRawMessage(); + message.setVersion(1); // There are a few messages here that need special handling due to length const id = data.readUInt16BE(0); - if ([0x1101, 0x217].includes(id)) { - message.setVersion(0) + if ([0x217, 0x532].includes(id)) { + message.setVersion(0); } - message.setSerializeOrder([{ name: "data", field: "Raw" }]); + getServerLogger( + "gateway.npsPortRouter/parseInitialMessage", + "gateway", + ).debug(`Parsing initial message: ${data.toString("hex")}`); + message.deserialize(data); + + getServerLogger( + "gateway.npsPortRouter/parseInitialMessage", + "gateway", + ).debug(`Parsed initial message: ${message.serialize().toString("hex")}`); return message; } catch (error) { const err = new Error(`Error parsing initial message: ${error}`, { @@ -117,61 +178,143 @@ function parseInitialMessage(data: Buffer): BytableMessage { * Handles different types of packets such as lobby data, login data, chat data, and persona data. * Logs the routing process and the number of responses sent back to the client. * - * @param id - The connection ID of the client. - * @param port - The port number to determine the type of packet. + * @param connectionId - The connection ID of the client. + * @param connectionPort - The port number to determine the type of packet. * @param initialPacket - The initial packet received from the client. * @param log - The logger to use for logging messages. * @returns A promise that resolves to a Buffer containing the serialized responses. */ async function routeInitialMessage( - id: string, - port: number, + connectionId: string, + connectionPort: number, initialPacket: BytableMessage, - log = getServerLogger("gateway.npsPortRouter/routeInitialMessage"), + log = getServerLogger("gateway.npsPortRouter/routeInitialMessage", "gateway"), ): Promise { // Route the initial message to the appropriate handler // Messages may be encrypted, this will be handled by the handler - log.debug(`Routing message for port ${port}: ${initialPacket.toString()}`); + log.debug( + `Routing message for port ${connectionPort}: ${initialPacket.toString()}`, + ); - const packet = new GamePacket(); - packet.deserialize(initialPacket.serialize()); + const gameRequestPacket = new GamePacket(); + gameRequestPacket.deserialize(initialPacket.serialize()); - let responses: SerializableInterface[] = []; + let packetResponses: SerializableInterface[] = []; - switch (port) { + switch (connectionPort) { case 7003: - responses = ( - await receiveLobbyData({ connectionId: id, message: packet }) + // Handle lobby packet + log.debug( + `[${connectionId}] Passing packet to lobby handler: ${gameRequestPacket.serialize().toString("hex")}`, + ); + packetResponses = ( + await receiveLobbyData({ + connectionId: connectionId, + message: initialPacket, + }) ).messages; + log.debug( + `[${connectionId}] Lobby Responses: ${packetResponses.map((r) => r.serialize().toString("hex"))}`, + ); break; case 8226: // Handle login packet - responses = ( - await receiveLoginData({ connectionId: id, message: initialPacket }) + log.debug( + `[${connectionId}] Passing packet to login handler: ${gameRequestPacket.serialize().toString("hex")}`, + ); + packetResponses = ( + await receiveLoginData({ + connectionId: connectionId, + message: initialPacket, + }) ).messages; + log.debug( + `[${connectionId}] Login Responses: ${packetResponses.map((r) => r.serialize().toString("hex"))}`, + ); break; - case 8227: - // Handle chat packet - responses = (await receiveChatData({ connectionId: id, message: packet })) - .messages; - break; + // case 8227: + // // Handle chat packet + // log.debug( + // `[${id}] Passing packet to chat handler: ${packet.serialize().toString("hex")}`, + // ); + // responses = (await receiveChatData({ connectionId: id, message: packet })) + // .messages; + // log.debug(`[${id}] Chat Responses: ${responses.map((r) => r.serialize().toString("hex"))}`); + // break; case 8228: + log.debug( + `[${connectionId}] Passing packet to persona handler: ${gameRequestPacket.serialize().toString("hex")}`, + ); // responses =Handle persona packet - responses = ( - await receivePersonaData({ connectionId: id, message: packet }) + packetResponses = ( + await receivePersonaData({ + connectionId: connectionId, + message: gameRequestPacket, + }) + ).messages; + log.debug( + `[${connectionId}] Persona Responses: ${packetResponses.map((r) => r.serialize().toString("hex"))}`, + ); + break; + case 9001: { + const roomServer = getRoomServerByPort(connectionPort); + if (roomServer === undefined) { + log.warn( + { id: connectionId, port: connectionPort }, + "No room server found for port", + ); + break; + } + log.debug( + { + id: connectionId, + port: connectionPort, + roomServer: roomServer.name, + packet: gameRequestPacket.serialize().toString("hex"), + }, + `Passing packet to room server: ${roomServer.name}`, + ); + packetResponses = ( + await roomServer.receivePacket({ + connectionId: connectionId, + packet: initialPacket, + }) ).messages; + const serializedResponseData = Buffer.concat( + packetResponses.map((r) => r.serialize()), + ); + log.debug( + { + id: connectionId, + port: connectionPort, + roomServer: roomServer.name, + responseBuffer: serializedResponseData.toString("hex"), + }, + `Room Server Responses`, + ); break; + } default: - console.log(`No handler found for port ${port}`); + // No handler + log.warn( + { + id: connectionId, + port: connectionPort, + packet: gameRequestPacket.serialize().toString("hex"), + }, + `No handler found for port ${connectionPort}`, + ); break; } // Send responses back to the client - log.debug(`[${id}] Sending ${responses.length} responses`); + log.debug(`[${connectionId}] Sending ${packetResponses.length} responses`); // Serialize the responses - const serializedResponses = responses.map((response) => response.serialize()); + const serializedResponses = packetResponses.map((response) => + response.serialize(), + ); return Buffer.concat(serializedResponses); } diff --git a/packages/gateway/src/socketErrorHandler.ts b/packages/gateway/src/socketErrorHandler.ts index 12df58624..f8138c7ab 100644 --- a/packages/gateway/src/socketErrorHandler.ts +++ b/packages/gateway/src/socketErrorHandler.ts @@ -13,9 +13,7 @@ import { getServerLogger, type ServerLogger } from "rusty-motors-shared"; export function socketErrorHandler({ connectionId, error, - log = getServerLogger({ - name: "socketErrorHandler", - }), + log = getServerLogger("socketErrorHandler"), }: { connectionId: string; error: NodeJS.ErrnoException; diff --git a/packages/gateway/src/utility.ts b/packages/gateway/src/utility.ts new file mode 100644 index 000000000..3f4bd243f --- /dev/null +++ b/packages/gateway/src/utility.ts @@ -0,0 +1,33 @@ +export function splitPackets(data: Buffer, separator: Buffer): Buffer[] { + const packets: Buffer[] = []; + + let remainingData = data; + + let endIndex = remainingData.indexOf(separator, 2); + + if (endIndex === -1) { + // No separator found, return the entire buffer + return [remainingData]; + } + + // Extract the packets + let startIndex = 0; + while (endIndex !== -1) { + // Check if the separator is at the end + if (endIndex === remainingData.length - separator.length) { + // If the separator is at the end, we should throw an error + throw new Error("Separator found at the end of the buffer"); + } + + const packet = remainingData.subarray(startIndex, endIndex); + packets.push(packet); + + remainingData = remainingData.subarray(endIndex); + endIndex = remainingData.indexOf(separator, separator.length); + } + // Add the last packet if there's any data left + if (startIndex < remainingData.length) { + packets.push(remainingData.subarray(startIndex)); + } + return packets; +} diff --git a/packages/gateway/test/npsPortRouter.test.ts b/packages/gateway/test/npsPortRouter.test.ts index b55c3882c..336e18634 100644 --- a/packages/gateway/test/npsPortRouter.test.ts +++ b/packages/gateway/test/npsPortRouter.test.ts @@ -62,7 +62,7 @@ describe("npsPortRouter", () => { expect(mockSocket.write).toHaveBeenCalledWith( - Buffer.from([0x02, 0x30, 0x00, 0x00]), + Buffer.from([0x02, 0x30, 0x00, 0x04]), ); }); diff --git a/packages/gateway/test/utility.spec.ts b/packages/gateway/test/utility.spec.ts new file mode 100644 index 000000000..1b7fa7fa1 --- /dev/null +++ b/packages/gateway/test/utility.spec.ts @@ -0,0 +1,70 @@ +import { suite, it, expect } from "vitest"; +import { splitPackets } from "../src/utility"; + +suite("Utility Helpers", () => { + suite("splitPackets", () => { + const separator = Buffer.from([0x11, 0x01]); + + it("should return the entire buffer when no seperator is found", () => { + const data = Buffer.from("0102030405060708090a0b0c0d0e0f10", "hex"); + const expectedPackets = [ + Buffer.from("0102030405060708090a0b0c0d0e0f10", "hex"), + ]; + + const result = splitPackets(data, separator); + + expect(result).toEqual(expectedPackets); + }); + + it("should return the entire buffer when the seperator is at the beginning", () => { + const data = Buffer.from("11010203040f010506070809", "hex"); + const expectedPackets = [Buffer.from("11010203040f010506070809", "hex")]; + + const result = splitPackets(data, separator); + + expect(result).toEqual(expectedPackets); + }); + + it("should return packets when a seperator is located at the beginning and in the middle", () => { + const data = Buffer.from("11010203040f0105110106070809", "hex"); + const expectedPackets: Buffer[] = [ + Buffer.from("11010203040f0105", "hex"), + Buffer.from("110106070809", "hex"), + ]; + + const result = splitPackets(data, separator); + + expect(result).toEqual(expectedPackets); + }); + + it("should throw when a seperator is located at the end", () => { + const data = Buffer.from("11010203040f01051101060708091101", "hex"); + + expect(() => splitPackets(data, separator)).toThrowError( + "Separator found at the end of the buffer", + ); + }); + + it("should throw when a seperator is located at beginning and multiple times in the middle", () => { + const data = Buffer.from("11010203040f01051101060708091101", "hex"); + + expect(() => + splitPackets(data, separator), + ).toThrowError("Separator found at the end of the buffer"); + }); + + + it("should return multiple packets when a seperator is located at beginning and multiple times in the middle", () => { + const data = Buffer.from("11010203040f01051101060708091101ab63", "hex"); + const expectedPackets: Buffer[] = [ + Buffer.from("11010203040f0105", "hex"), + Buffer.from("110106070809", "hex"), + Buffer.from("1101ab63", "hex"), + ]; + + const result = splitPackets(data, separator); + + expect(result).toEqual(expectedPackets); + }); + }); +}); diff --git a/packages/lobby/src/CarDecal.ts b/packages/lobby/src/CarDecal.ts new file mode 100644 index 000000000..36f2ecf94 --- /dev/null +++ b/packages/lobby/src/CarDecal.ts @@ -0,0 +1,74 @@ +import { Bytable } from "@rustymotors/binary"; + +export class CarDecal extends Bytable { + // ff000000 + private backgroundImage_ = Buffer.alloc(1); + private forgroundImage_ = Buffer.alloc(1); + private color0_ = Buffer.alloc(1); + private color1_ = Buffer.alloc(1); + + override get serializeSize(): number { + return 4; + } + + get backgroundImage(): number { + return this.backgroundImage_.readUInt8(0); + } + + setBackgroundImage(value: number) { + this.backgroundImage_.writeUInt8(value, 0); + } + + get forgroundImage(): number { + return this.forgroundImage_.readUInt8(0); + } + + setForgroundImage(value: number) { + this.forgroundImage_.writeUInt8(value, 0); + } + + get color0(): number { + return this.color0_.readUInt8(0); + } + + setColor0(value: number) { + this.color0_.writeUInt8(value, 0); + } + + get color1(): number { + return this.color1_.readUInt8(0); + } + + setColor1(value: number) { + this.color1_.writeUInt8(value, 0); + } + + override serialize(): Buffer { + return Buffer.concat([ + this.backgroundImage_, + this.forgroundImage_, + this.color0_, + this.color1_, + ]); + } + + override deserialize(buffer: Buffer): void { + this.backgroundImage_ = buffer.subarray(0, 1); + this.forgroundImage_ = buffer.subarray(1, 2); + this.color0_ = buffer.subarray(2, 3); + this.color1_ = buffer.subarray(3, 4); + } + + override toString(): string { + return `CarDecal { backgroundImage: ${this.backgroundImage}, forgroundImage: ${this.forgroundImage}, color0: ${this.color0}, color1: ${this.color1} }`; + } + + toJSON() { + return { + backgroundImage: this.backgroundImage, + forgroundImage: this.forgroundImage, + color0: this.color0, + color1: this.color1, + }; + } +} diff --git a/packages/lobby/src/LoginInfoMessage.ts b/packages/lobby/src/LoginInfoMessage.ts index 3e01951a2..9ddf92fe0 100644 --- a/packages/lobby/src/LoginInfoMessage.ts +++ b/packages/lobby/src/LoginInfoMessage.ts @@ -3,7 +3,7 @@ import { LegacyMessage } from "rusty-motors-shared"; import { serializeString } from "rusty-motors-shared"; export class LoginInfoMessage extends LegacyMessage { - _userId: number; + _userId: number; // 4 bytes _userName: string; _userData: Buffer; _customerId: number; @@ -31,7 +31,7 @@ export class LoginInfoMessage extends LegacyMessage { */ override deserialize(buffer: Buffer): this { try { - this._header._doDeserialize(buffer); + this._header.deserialize(buffer); let offset = this._header._size; this._userId = buffer.readInt32BE(offset); offset += 4; @@ -67,7 +67,7 @@ export class LoginInfoMessage extends LegacyMessage { override serialize(): Buffer { try { const buffer = Buffer.alloc(this._header.length); - this._header._doSerialize().copy(buffer); + this._header.serialize().copy(buffer); let offset = this._header._size; buffer.writeInt32BE(this._userId, offset); offset += 4; diff --git a/packages/lobby/src/MiniRiffMessage.ts b/packages/lobby/src/MiniRiffMessage.ts index 3aabe688f..7b99413cb 100644 --- a/packages/lobby/src/MiniRiffMessage.ts +++ b/packages/lobby/src/MiniRiffMessage.ts @@ -34,7 +34,7 @@ export class MiniRiffMessage extends LegacyMessage { const neededSize = this.size(); this._header.length = neededSize + 4; const buffer = Buffer.alloc(this._header.length); - this._header._doSerialize().copy(buffer); + this._header.serialize().copy(buffer); let offset = this._header._size; // offset is 4 buffer.writeUInt16BE(this._riffList.length, offset); offset += 4; diff --git a/packages/lobby/src/MiniUserInfo.ts b/packages/lobby/src/MiniUserInfo.ts new file mode 100644 index 000000000..99dbb67cb --- /dev/null +++ b/packages/lobby/src/MiniUserInfo.ts @@ -0,0 +1,12 @@ +import { BytableStructure } from "@rustymotors/binary"; + + +export class MiniUserInfo extends BytableStructure { + constructor() { + super(); + this.setSerializeOrder([ + { name: "userId", field: "Dword" }, + { name: "userName", field: "String" }, + ]); + } +} diff --git a/packages/lobby/src/UserInfoMessage.ts b/packages/lobby/src/UserInfoMessage.ts index b1e5ae30e..f5c8e3887 100644 --- a/packages/lobby/src/UserInfoMessage.ts +++ b/packages/lobby/src/UserInfoMessage.ts @@ -3,6 +3,120 @@ import { LegacyMessage } from "rusty-motors-shared"; import { serializeString } from "rusty-motors-shared"; // eslint-disable-next-line no-unused-vars import { LoginInfoMessage } from "./LoginInfoMessage.js"; +import { Bytable, BytableContainer, BytableStructure } from "@rustymotors/binary"; +import { CarDecal } from "./CarDecal.js"; + +export class CarId extends Bytable { + // 00000000000000000000000005000000a5ceffff0d45acffffffffff00d8ffff0000000000000000 + private carId_: Buffer = Buffer.alloc(4); + private brandedPartId_: Buffer = Buffer.alloc(4); + private skinId_: Buffer = Buffer.alloc(4); + private driverModelType_: Buffer = Buffer.alloc(1); + private driverSkinColor_: Buffer = Buffer.alloc(4); + private driverHairColor_: Buffer = Buffer.alloc(4); + private driverShirtColor_: Buffer = Buffer.alloc(4); + private driverPantsColor_: Buffer = Buffer.alloc(4); + private flags_: Buffer = Buffer.alloc(1); + private skinFlags_: Buffer = Buffer.alloc(1); + private decal_ = new CarDecal(); + + override get serializeSize(): number { + return 40; + } + + override serialize(): Buffer { + const buffer = Buffer.alloc(this.serializeSize); + let offset = 0; + this.carId_.copy(buffer, offset); + offset += 4; + this.brandedPartId_.copy(buffer, offset); + offset += 4; + this.skinId_.copy(buffer, offset); + offset += 4; + this.driverModelType_.copy(buffer, offset); + offset += 1; + this.driverSkinColor_.copy(buffer, offset); + offset += 4; + this.driverHairColor_.copy(buffer, offset); + offset += 4; + this.driverShirtColor_.copy(buffer, offset); + offset += 4; + this.driverPantsColor_.copy(buffer, offset); + offset += 4; + this.flags_.copy(buffer, offset); + offset += 1; + this.skinFlags_.copy(buffer, offset); + offset += 1; + this.decal_.serialize().copy(buffer, offset); + return buffer; + } + + override deserialize(buffer: Buffer): void { + let offset = 0; + this.carId_ = buffer.subarray(offset, offset + 4); + offset += 4; + this.brandedPartId_ = buffer.subarray(offset, offset + 4); + offset += 4; + this.skinId_ = buffer.subarray(offset, offset + 4); + offset += 4; + this.driverModelType_ = buffer.subarray(offset, offset + 1); + offset += 1; + this.driverSkinColor_ = buffer.subarray(offset, offset + 4); + offset += 4; + this.driverHairColor_ = buffer.subarray(offset, offset + 4); + offset += 4; + this.driverShirtColor_ = buffer.subarray(offset, offset + 4); + offset += 4; + this.driverPantsColor_ = buffer.subarray(offset, offset + 4); + offset += 4; + this.flags_ = buffer.subarray(offset, offset + 1); + offset += 1; + this.skinFlags_ = buffer.subarray(offset, offset + 1); + offset += 1; + this.decal_.deserialize(buffer.subarray(offset)); + } + + override toString(): string { + return `Car ID: ${this.carId_.readUInt32LE(0)}, Branded Part ID: ${this.brandedPartId_.readUInt32LE(0)}, Skin ID: ${this.skinId_.readUInt32LE(0)}, Driver Model Type: ${this.driverModelType_.readUInt8(0)}, Driver Skin Color: ${this.driverSkinColor_.readUInt32LE(0)}, Driver Hair Color: ${this.driverHairColor_.readUInt32LE(0)}, Driver Shirt Color: ${this.driverShirtColor_.readUInt32LE(0)}, Driver Pants Color: ${this.driverPantsColor_.readUInt32LE(0)}, Flags: ${this.flags_.readUInt8(0)}, Skin Flags: ${this.skinFlags_.readUInt8(0)}, Decal: ${this.decal_.toString()}`; + } + + toJSON(): any { + return { + carId: this.carId_.readUInt32LE(0), + brandedPartId: this.brandedPartId_.readUInt32LE(0), + skinId: this.skinId_.readUInt32LE(0), + driverModelType: this.driverModelType_.readUInt8(0), + driverSkinColor: this.driverSkinColor_.readUInt32LE(0), + driverHairColor: this.driverHairColor_.readUInt32LE(0), + driverShirtColor: this.driverShirtColor_.readUInt32LE(0), + driverPantsColor: this.driverPantsColor_.readUInt32LE(0), + flags: this.flags_.readUInt8(0), + skinFlags: this.skinFlags_.readUInt8(0), + decal: this.decal_.toJSON(), + }; + } +} + +export class UserData extends BytableStructure { + // 00000000000000000000000005000000a5ceffff0d45acffffffffff00d8ffff0000000000000000010000000000000008000000000000000001000000000000 + + constructor() { + super(); + this.setSerializeOrder([ + { name: "carIds", field: "Structure" }, + { name: "lobbyId", field: "Dword" }, + { name: "clubId", field: "Dword" }, + { name: "inLobby", field: "Boolean" }, + { name: "inMovement", field: "Boolean" }, + { name: "inRace", field: "Boolean" }, + { name: "isDataValid", field: "Boolean" }, + { name: "unused", field: "Boolean" }, + { name: "performance", field: "Dword" }, + { name: "points", field: "Dword" }, + { name: "level", field: "Short" }, + ]); + } +} export class UserInfo { _userId: number; @@ -10,7 +124,7 @@ export class UserInfo { _userData: Buffer; constructor() { this._userId = 0; // 4 bytes - this._userName = ""; // 4 bytes + string + 1 byte + this._userName = ""; // 2 bytes + string this._userData = Buffer.alloc(64); // 64 bytes } @@ -43,6 +157,10 @@ export class UserInfo { } } +function align8(value: number) { + return value + (8 - (value % 8)); +} + export class UserInfoMessage extends LegacyMessage { _userId: number; _userName: string; @@ -50,7 +168,7 @@ export class UserInfoMessage extends LegacyMessage { constructor() { super(); this._userId = 0; // 4 bytes - this._userName = ""; // 4 bytes + string + 1 byte + this._userName = ""; // 2 bytes + string this._userData = Buffer.alloc(64); // 64 bytes } @@ -60,7 +178,7 @@ export class UserInfoMessage extends LegacyMessage { */ override deserialize(buffer: Buffer): this { try { - this._header._doDeserialize(buffer); + this._header.deserialize(buffer); let offset = this._header._size; this._userId = buffer.readInt32BE(offset); offset += 4; @@ -83,13 +201,19 @@ export class UserInfoMessage extends LegacyMessage { */ override serialize(): Buffer { try { - const buffer = Buffer.alloc(this._header.length); - this._header._doSerialize().copy(buffer); + const leangth8 = align8(this._header.length); + this._header.length = leangth8; + const buffer = Buffer.alloc(leangth8); + this._header.serialize().copy(buffer); let offset = this._header._size; buffer.writeInt32BE(this._userId, offset); offset += 4; - offset = serializeString(this._userName, buffer, offset); + const username = new BytableContainer(); + username.setValue(this._userName); + + username.serialize().copy(buffer, offset); + offset += username.serializeSize; this._userData.copy(buffer, offset); return buffer; @@ -111,10 +235,8 @@ export class UserInfoMessage extends LegacyMessage { return this; } - calculateLength() { - this._header._size = 4 + 4 + 64; - this._header.length += 4 + this._userName.length + 1; - this._header.length += this._userData.length; + calculateLength() { + this._header.length = this._header._size + 4 + 2 + this._userName.length + 64; return this._header.length; } diff --git a/packages/lobby/src/handlers/_setMyUserData.ts b/packages/lobby/src/handlers/_setMyUserData.ts index 14c55c498..694680b61 100644 --- a/packages/lobby/src/handlers/_setMyUserData.ts +++ b/packages/lobby/src/handlers/_setMyUserData.ts @@ -2,20 +2,26 @@ import { LegacyMessage } from "rusty-motors-shared"; import { UserInfo } from "../UserInfoMessage.js"; import { databaseManager } from "rusty-motors-database"; import { ServerLogger, getServerLogger } from "rusty-motors-shared"; +import { UserData } from "../UserInfoMessage.js"; +import { BytableMessage } from "@rustymotors/binary"; +const NPS_USER_INFO = 0x204; // 516 +const NPS_CHANNEL_GRANTED = 0x214; // 532 export async function _setMyUserData({ connectionId, message, - log = getServerLogger("handlers/_setMyUserData"), + log = getServerLogger("lobby._setMyUserData"), }: { connectionId: string; - message: LegacyMessage; + message: BytableMessage; log?: ServerLogger; }) { try { - log.debug("Handling NPS_SET_MY_USER_DATA"); - log.debug(`Received command: ${message.serialize().toString("hex")}`); + log.debug({ + connectionId, + payload: message.serialize().toString("hex") + }, "Handling NPS_SET_MY_USER_DATA"); const incomingMessage = new UserInfo(); incomingMessage.deserialize(message.serialize()); @@ -28,19 +34,35 @@ export async function _setMyUserData({ userData: incomingMessage._userData, }); + const userData = new UserData(); + userData.deserialize(incomingMessage._userData); + + log.debug(`User data: ${userData.toString()}`); + + const currentChannel = userData.getFieldValueByName("lobbyId") as number; + // Build the packet const packetResult = new LegacyMessage(); - packetResult._header.id = 516; - packetResult.deserialize(incomingMessage.serialize()); + // packetResult._header.id = NPS_USER_INFO; + packetResult._header.id = NPS_CHANNEL_GRANTED; + + const channelBuffer = Buffer.alloc(4); + channelBuffer.writeInt32BE(currentChannel); + + const response = Buffer.concat([channelBuffer, Buffer.from([0, 0, 0, 0])]); + + packetResult.setBuffer(response); + + // packetResult.deserialize(incomingMessage.serialize()); - log.debug(`Sending response: ${packetResult.serialize().toString("hex")}`); + message.header.setMessageId(NPS_USER_INFO) return { connectionId, message: null, }; } catch (error) { - const err = Error(`Error handling NPS_SET_MY_USER_DATA: ${String(error)}`); + const err = Error(`[${connectionId}] Error handling NPS_SET_MY_USER_DATA: ${String(error)}`); err.cause = error; throw err; } diff --git a/packages/lobby/src/handlers/channels.ts b/packages/lobby/src/handlers/channels.ts index af158df0a..4a421eb14 100644 --- a/packages/lobby/src/handlers/channels.ts +++ b/packages/lobby/src/handlers/channels.ts @@ -2,18 +2,119 @@ export const channelRecordSize = 40; export const channels = [ { - id: 0, + id: 30, name: "CTRL", - population: 1, + population: 5, }, { - id: 2, + id: 0, name: "LOBBY", - population: 1, + population: 4, }, { id: 191, name: "MCCHAT", - population: 0, + population: 6, + }, + { + id: 17, + name: "MARK", + population: 15, + }, + { + id: 224, + name: "MCC01", + population: 1, + }, + { + id: 225, + name: "MCC02", + population: 1, + }, + { + id: 226, + name: "MCC03", + population: 1, + }, + { + id: 227, + name: "MCC04", + population: 1, + }, + { + id: 228, + name: "MCC05", + population: 1, + }, + { + id: 229, + name: "MCC06", + population: 1, }, + { + id: 230, + name: "MCC07", + population: 1, + }, + { + id: 231, + name: "MCC08", + population: 1, + }, + { + id: 232, + name: "MCC09", + population: 1, + }, + { + id: 233, + name: "MCC10", + population: 1, + }, + { + id: 234, + name: "MCC11", + population: 1, + }, + { + id: 235, + name: "MCC12", + population: 1, + }, + { + id: 236, + name: "MCC13", + population: 1, + }, + { + id: 237, + name: "MCC14", + population: 1, + }, + { + id: 238, + name: "MCC15", + population: 1, + }, + { + id: 239, + name: "MCC16", + population: 1, + }, + { + id: 240, + name: "MCC17", + population: 1, + }, + { + id: 241, + name: "MCC18", + population: 1, + }, + { + id: 242, + name: "MCC19", + population: 1, + } + ]; diff --git a/packages/lobby/src/handlers/encryptedCommand.ts b/packages/lobby/src/handlers/encryptedCommand.ts index 9c2d560da..a50a1a5eb 100644 --- a/packages/lobby/src/handlers/encryptedCommand.ts +++ b/packages/lobby/src/handlers/encryptedCommand.ts @@ -4,13 +4,13 @@ import { ServerLogger, updateEncryption, } from "rusty-motors-shared"; -import { MessageBufferOld } from "rusty-motors-shared"; import { SerializedBufferOld } from "rusty-motors-shared"; -import { LegacyMessage } from "rusty-motors-shared"; import { _setMyUserData } from "./_setMyUserData.js"; import { handleGetMiniUserList } from "./handleGetMiniUserList.js"; import { handleSendMiniRiffList } from "./handleSendMiniRiffList.js"; import { getServerLogger } from "rusty-motors-shared"; +import { BytableMessage, createRawMessage } from "@rustymotors/binary"; +import { handleGetServerInfo } from "./handleGetServerInfo.js"; /** * Array of supported command handlers @@ -32,7 +32,7 @@ export const messageHandlers: { name: string; handler: (args: { connectionId: string; - message: SerializedBufferOld; + message: BytableMessage; log: ServerLogger; }) => Promise<{ connectionId: string; @@ -58,12 +58,13 @@ async function encryptCmd({ log = getServerLogger( "lobby.encryptCmd"), }: { connectionId: string; - message: LegacyMessage | MessageBufferOld; + message: BytableMessage; log?: ServerLogger; }): Promise<{ connectionId: string; - message: LegacyMessage | MessageBufferOld; + message: BytableMessage; }> { + log.debug(`[ciphering Cmd: ${message.serialize().toString("hex")}`); const state = fetchStateFromDatabase(); const encryption = getEncryption(state, connectionId); @@ -74,17 +75,29 @@ async function encryptCmd({ ); } - const result = encryption.commandEncryption.encrypt(message.data); + let precriptedMessage = message.serialize(); + log.debug(`[precripted Cmd: ${precriptedMessage.toString("hex")}`); + if (precriptedMessage.length % 8 !== 0) { + const padding = Buffer.alloc(8 - (precriptedMessage.length % 8)); + precriptedMessage = Buffer.concat([precriptedMessage, padding]); + log.debug(`[padded Cmd: ${precriptedMessage.toString("hex")}`); + } + + const result = encryption.commandEncryption.encrypt(precriptedMessage); updateEncryption(state, encryption).save(); log.debug(`[ciphered Cmd: ${result.toString("hex")}`); - message.setBuffer(result); + const encryptedMessage = createRawMessage(); + encryptedMessage.header.setMessageId(0x1101); + encryptedMessage.setBody(result); + + log.debug(`[ciphered message: ${encryptedMessage.serialize().toString("hex")}`); return { connectionId, - message, + message: encryptedMessage, }; } @@ -106,11 +119,11 @@ async function decryptCmd({ log = getServerLogger( "lobby.decryptCmd"), }: { connectionId: string; - message: LegacyMessage; + message: BytableMessage; log?: ServerLogger; }): Promise<{ connectionId: string; - message: LegacyMessage; + message: BytableMessage; }> { const state = fetchStateFromDatabase(); @@ -122,17 +135,17 @@ async function decryptCmd({ ); } - const result = encryption.commandEncryption.decrypt(message.data); + const result = encryption.commandEncryption.decrypt(message.getBody()); updateEncryption(state, encryption).save(); - log.debug(`[Deciphered Cmd: ${result.toString("hex")}`); - - message.setBuffer(result); + log.debug(`[Deciphered Cmd: ${result.toString("hex")}`); + + const decipheredMessage = createRawMessage(result) return { connectionId, - message, + message: decipheredMessage, }; } @@ -141,66 +154,58 @@ export type NpsCommandHandler = { name: string; handler: (args: { connectionId: string; - message: LegacyMessage; - log: ServerLogger; + message: BytableMessage; + log?: ServerLogger; }) => Promise<{ connectionId: string; - message: LegacyMessage | null; + message: BytableMessage | null; }>; }; const npsCommandHandlers: NpsCommandHandler[] = [ { - opCode: 0x128, + opCode: 0x10c, // 268 + name: "NPS_GET_SERVER_INFO", + handler: handleGetServerInfo, + }, + { + opCode: 0x128, // 296 name: "NPS_GET_MINI_USER_LIST", handler: handleGetMiniUserList, }, { - opCode: 0x30c, + opCode: 0x30c, // 780 name: "NPS_SEND_MINI_RIFF_LIST", handler: handleSendMiniRiffList, }, { - opCode: 0x103, + opCode: 0x103, // 259 name: "NPS_SET_MY_USER_DATA", handler: _setMyUserData, }, ]; -/** - * - * - * @param {object} args - * @param {string} args.connectionId - * @param {LegacyMessage} args.message - * @param {ServerLogger} [args.log=getServerLogger({ name: "Lobby" })] - * @return {Promise<{ - * connectionId: string, - * message: MessageBuffer | LegacyMessage, - * }>}} - */ + async function handleCommand({ connectionId, message, log = getServerLogger( "lobby.handleCommand"), }: { connectionId: string; - message: LegacyMessage; + message: BytableMessage; log?: ServerLogger; }): Promise<{ connectionId: string; - message: MessageBufferOld | LegacyMessage | null; + message: BytableMessage | null; }> { - const incommingRequest = message; - log.debug( - `[${connectionId}] Received command: ${incommingRequest._doSerialize().toString("hex")}`, + `[${connectionId}] Received command: ${message.serialize().toString("hex")}`, ); - // What is the command? - const command = incommingRequest.data.readUInt16BE(0); + const command = message.header.messageId; - log.debug(`Command: ${command}`); + // What is the command? + log.debug(`[${connectionId}] Command: ${command}`); const handler = npsCommandHandlers.find((h) => h.opCode === command); @@ -208,11 +213,20 @@ async function handleCommand({ throw Error(`Unknown command: ${command}`); } - return handler.handler({ + const {message: response} = await handler.handler({ + connectionId, message, - log, }); + + if (response !== null) { + log.debug(`[${connectionId}] Sending response: ${response.serialize().toString("hex")}`); + } + + return { + connectionId, + message: response, + }; } /** @@ -234,26 +248,26 @@ export async function handleEncryptedNPSCommand({ log = getServerLogger( "lobby.handleEncryptedNPSCommand"), }: { connectionId: string; - message: SerializedBufferOld; + message: BytableMessage; log?: ServerLogger; }): Promise<{ connectionId: string; messages: SerializedBufferOld[]; }> { - const inboundMessage = new LegacyMessage(); - inboundMessage._doDeserialize(message.data); + log.debug(`[${connectionId}] Handling encrypted NPS command`); + log.debug(`[${connectionId}] Received command: ${message.serialize().toString("hex")}`); // Decipher const decipheredMessage = await decryptCmd({ connectionId, - message: inboundMessage, - log, + message, }); + log.debug(`[${connectionId}] Deciphered message: ${decipheredMessage.message.serialize().toString("hex")}`); + const response = await handleCommand({ connectionId, message: decipheredMessage.message, - log, }); if (response.message === null) { @@ -264,18 +278,20 @@ export async function handleEncryptedNPSCommand({ }; } + log.debug(`[${connectionId}] Sending response: ${response.message.serialize().toString("hex")}`); + // Encipher - const encryptedResponse = encryptCmd({ + const result = await encryptCmd({ connectionId, message: response.message, - log, }); - const outboundMessage = new SerializedBufferOld(); - outboundMessage.setBuffer((await encryptedResponse).message.serialize()); + const encryptedResponse = result.message; + + log.debug(`[${connectionId}] Enciphered response: ${encryptedResponse.serialize().toString("hex")}`); return { connectionId, - messages: [outboundMessage], + messages: [encryptedResponse], }; } diff --git a/packages/lobby/src/handlers/handleGetMiniUserList.ts b/packages/lobby/src/handlers/handleGetMiniUserList.ts index 1d7d1febf..88dba9d48 100644 --- a/packages/lobby/src/handlers/handleGetMiniUserList.ts +++ b/packages/lobby/src/handlers/handleGetMiniUserList.ts @@ -1,68 +1,72 @@ -import { GameMessage, ServerLogger } from "rusty-motors-shared"; +import { ServerLogger } from "rusty-motors-shared"; import { LegacyMessage } from "rusty-motors-shared"; -import { serializeString } from "rusty-motors-shared"; -import { UserInfo } from "../UserInfoMessage.js"; -import { channelRecordSize, channels } from "./channels.js"; +import { MiniUserInfo } from "../MiniUserInfo.js"; import { getServerLogger } from "rusty-motors-shared"; +import { BytableMessage } from "@rustymotors/binary"; -const defaultLogger = getServerLogger("Lobby"); - - -const user1 = new UserInfo(); -user1._userId = 1; -user1._userName = "User 1"; -/** - * @param {object} args - * @param {string} args.connectionId - * @param {LegacyMessage} args.message - * @param {ServerLogger} [args.log=getServerLogger({ name: "Lobby" })] - */ export async function handleGetMiniUserList({ connectionId, message, - log = defaultLogger, + log = getServerLogger("lobby.handleGetMiniUserList"), }: { connectionId: string; - message: LegacyMessage; + message: BytableMessage; log?: ServerLogger; -}) { - log.debug("Handling NPS_GET_MINI_USER_LIST"); - log.debug(`Received command: ${message._doSerialize().toString("hex")}`); +}): Promise<{ + connectionId: string; + message: BytableMessage; +}> { + try { + log.debug(`[${connectionId}] Handling NPS_GET_MINI_USER_LIST`); + log.debug( + `[${connectionId}]Received command: ${message.serialize().toString("hex")}`, + ); + log.debug( + `[${connectionId}] Received NPS_GET_MINI_USER_LIST: ${message.toString()}`, + ); - const outgoingGameMessage = new GameMessage(553); + const requestedCommId = message.getBody().readUInt32BE(0); - const resultSize = channelRecordSize * channels.length - 12; + log.debug(`[${connectionId}] Requested commId: ${requestedCommId}`); - const packetContent = Buffer.alloc(resultSize); + const commId = 1; + const userCount = 1; - let offset = 0; - try { - // Add the response code - packetContent.writeUInt32BE(17, offset); - offset += 4; // offset is 8 + const channelCountRecord = Buffer.alloc(8); + channelCountRecord.writeUInt32BE(commId, 0); // commId + channelCountRecord.writeUInt32BE(userCount, 4); // userCount + + const user1 = new MiniUserInfo(); + user1.setFieldValueByName("userId", 21); + user1.setFieldValueByName("userName", "Dr Brown"); + + log.debug(`[${connectionId}] User1: ${user1.toString()}`); - packetContent.writeUInt32BE(1, offset); - offset += 4; // offset is 12 + const realData = Buffer.concat([ + channelCountRecord, + user1.serialize(), + ]); - // Write the count of users - packetContent.writeUInt32BE(1, offset); - offset += 4; // offset is 16 + const align8Padding = 8 - (realData.length % 8); - // write the persona id - packetContent.writeUInt32BE(user1._userId, offset); - offset += 4; // offset is 20 + const padding = Buffer.alloc(align8Padding); - // write the persona name - serializeString(user1._userName, packetContent, offset); + const packetContent = Buffer.concat + ([realData, padding]); - outgoingGameMessage.setRecordData(packetContent); + const outgoingMessage = new LegacyMessage(); + outgoingMessage.setMessageId(553); // NPS_MINI_USER_LIST - 0x229 + outgoingMessage.setBuffer(packetContent); - // Build the packet - const packetResult = new LegacyMessage(); - packetResult._doDeserialize(outgoingGameMessage.serialize()); + const packetResult = new BytableMessage(); + packetResult.setSerializeOrder([ + { name: "data", field: "Buffer" }, + ]); + packetResult.deserialize(outgoingMessage.serialize()); - log.debug(`Sending response: ${packetResult.serialize().toString("hex")}`); + log.debug(`[${connectionId}] Sending NPS_MINI_USER_LIST`); + log.debug(`[${connectionId}] Sending response: ${packetResult.serialize().toString("hex")}`); return { connectionId, diff --git a/packages/lobby/src/handlers/handleGetServerInfo.ts b/packages/lobby/src/handlers/handleGetServerInfo.ts new file mode 100644 index 000000000..ed0c121da --- /dev/null +++ b/packages/lobby/src/handlers/handleGetServerInfo.ts @@ -0,0 +1,121 @@ +import { BytableMessage } from "@rustymotors/binary"; +import { getRoomServerById } from "rusty-motors-database"; +import { + getServerLogger, + ServerLogger, +} from "rusty-motors-shared"; + +const DEFAULT_RIFF = { + name: "MCC01", + ip: "71.186.155.248\n", + port: 7003, +} + +export async function handleGetServerInfo({ + connectionId, + message, + log = getServerLogger("lobby.handleGetServerInfo"), +}: { + connectionId: string; + message: BytableMessage; + log?: ServerLogger; +}): Promise<{ + connectionId: string; + message: BytableMessage; +}> { + try { + log.debug(`[${connectionId}] Handling NPS_GET_SERVER_INFO`); + log.debug( + `[${connectionId}] Received command: ${message.serialize().toString("hex")}`, + ); + + // l + const incommingRequest = new BytableMessage(); + incommingRequest.setSerializeOrder([{ name: "riffId", field: "Dword" }]); + incommingRequest.deserialize(message.serialize()); + + const requestRiffId = incommingRequest.getFieldValueByName("riffId"); + + if (typeof requestRiffId !== "number") { + log.error({ + connectionId, + requestRiffId, + }, `Invalid riffId: ${requestRiffId}`); + return { + connectionId, + message: new BytableMessage(), + }; + } + + log.debug( + `[${connectionId}] Received riffId: ${requestRiffId}`, + ); + + let riff = getRoomServerById(requestRiffId) || DEFAULT_RIFF; + + if (riff === DEFAULT_RIFF) { + log.warn( + `[${connectionId}] Riff not found, using default riff: ${riff.name}`, + ); + } + + + // TODO: Actually have servers + + // plplll + const outgoingGameMessage = new BytableMessage(); + outgoingGameMessage.setSerializeOrder([ + { name: "riffName", field: "String" }, + { name: "commId", field: "Dword" }, + { name: "ipAddress", field: "String" }, + { name: "port", field: "Dword" }, + { name: "userId", field: "Dword" }, + { name: "playerCount", field: "Dword" }, + ]); + + outgoingGameMessage.header.setMessageId(525); + outgoingGameMessage.setVersion(0); + outgoingGameMessage.setFieldValueByName("riffName", riff.name); + outgoingGameMessage.setFieldValueByName("commId", 224); + outgoingGameMessage.setFieldValueByName( + "ipAddress", + "71.186.155.248\n", + ); + outgoingGameMessage.setFieldValueByName( + "port", + riff.port + ); + outgoingGameMessage.setFieldValueByName("userId", 21); + outgoingGameMessage.setFieldValueByName("playerCount", 1); + + log.debug( + `[${connectionId}] Sending response[string]: ${outgoingGameMessage.toString()}`, + ); + log.debug( + `[${connectionId}] Sending response[serialize1]: ${outgoingGameMessage.serialize().toString("hex")}`, + ); + + // Build the packet + const packetResult = new BytableMessage(); + packetResult.setSerializeOrder([ + { name: "data", field: "Buffer" }, + ]); + packetResult.setVersion(0); + packetResult.deserialize(outgoingGameMessage.serialize()); + + log.debug( + `[${connectionId}] Sending response[serialize2]: ${packetResult.serialize().toString("hex")}`, + ); + + return { + connectionId, + message: packetResult, + }; + } catch (error) { + const err = Error( + `[${connectionId}] Error handling NPS_GET_SERVER_INFO: ${String(error)}`, + ); + err.cause = error; + throw err; + } +} diff --git a/packages/lobby/src/handlers/handleSendMiniRiffList.ts b/packages/lobby/src/handlers/handleSendMiniRiffList.ts index bd07ca264..c31490cbc 100644 --- a/packages/lobby/src/handlers/handleSendMiniRiffList.ts +++ b/packages/lobby/src/handlers/handleSendMiniRiffList.ts @@ -1,32 +1,27 @@ -import { ServerLogger, } from "rusty-motors-shared"; +import { LegacyMessage, ServerLogger, } from "rusty-motors-shared"; import { GameMessage } from "rusty-motors-shared"; -import { LegacyMessage } from "rusty-motors-shared"; import { serializeString } from "rusty-motors-shared"; import { channelRecordSize, channels } from "./channels.js"; import { getServerLogger } from "rusty-motors-shared"; +import { BytableMessage } from "@rustymotors/binary"; -const defaultLogger = getServerLogger("Lobby"); - -// const users = [user1]; -/** - * @param {object} args - * @param {string} args.connectionId - * @param {LegacyMessage} args.message - * @param {ServerLogger} [args.log=getServerLogger({ name: "Lobby" })] - */ export async function handleSendMiniRiffList({ connectionId, message, - log = defaultLogger, + log = getServerLogger("lobby.handleSendMiniRiffList"), }: { connectionId: string; - message: LegacyMessage; + message: BytableMessage; log?: ServerLogger; -}) { - log.debug("Handling NPS_SEND_MINI_RIFF_LIST"); - log.debug(`Received command: ${message._doSerialize().toString("hex")}`); +}): Promise<{ + connectionId: string; + message: BytableMessage; +}> { + log.debug("[${connectionId}] Handling NPS_SEND_MINI_RIFF_LIST"); + log.debug(`[${connectionId}] Received command: ${message.serialize().toString("hex")}`); - const outgoingGameMessage = new GameMessage(1028); + const outgoingGameMessage = new LegacyMessage(); + outgoingGameMessage.setMessageId(1028); // NPS_SEND_MINI_RIFF_LIST const resultSize = channelRecordSize * channels.length - 12; @@ -47,13 +42,16 @@ export async function handleSendMiniRiffList({ offset += 2; } - outgoingGameMessage.setRecordData(packetContent); + outgoingGameMessage.setBuffer(packetContent); // Build the packet - const packetResult = new LegacyMessage(); - packetResult._doDeserialize(outgoingGameMessage.serialize()); + const packetResult = new BytableMessage(); + packetResult.setSerializeOrder([ + { name: "data", field: "Buffer" }, + ]); + packetResult.deserialize(outgoingGameMessage.serialize()); - log.debug(`Sending response: ${packetResult.serialize().toString("hex")}`); + log.debug(`[${connectionId}] Sending response: ${packetResult.serialize().toString("hex")}`); return { connectionId, diff --git a/packages/lobby/src/handlers/handleTrackingPing.ts b/packages/lobby/src/handlers/handleTrackingPing.ts index 586309390..404636e1e 100644 --- a/packages/lobby/src/handlers/handleTrackingPing.ts +++ b/packages/lobby/src/handlers/handleTrackingPing.ts @@ -1,3 +1,4 @@ +import { BytableMessage } from "@rustymotors/binary"; import { SerializedBufferOld, ServerLogger } from "rusty-motors-shared"; import { getServerLogger } from "rusty-motors-shared"; @@ -9,7 +10,7 @@ export async function handleTrackingPing({ log = defaultLogger, }: { connectionId: string; - message: SerializedBufferOld; + message: BytableMessage; log?: ServerLogger; }): Promise<{ connectionId: string; diff --git a/packages/lobby/src/handlers/index.js b/packages/lobby/src/handlers/index.js deleted file mode 100644 index 50e1547f5..000000000 --- a/packages/lobby/src/handlers/index.js +++ /dev/null @@ -1,19 +0,0 @@ -// eslint-disable-next-line no-unused-vars -import { SerializedBufferOld } from "rusty-motors-shared"; -import { handleEncryptedNPSCommand } from "./encryptedCommand.js"; -import { _npsHeartbeat } from "./heartbeat.js"; -import { _npsRequestGameConnectServer } from "./requestConnectGameServer.js"; - -export const handlerMap = [ - { - opCode: 100, - name: "Connect game server", - handler: _npsRequestGameConnectServer, - }, - { opCode: 217, name: "Heartbeat", handler: _npsHeartbeat }, - { - opCode: 1101, - name: "Encrypted command", - handler: handleEncryptedNPSCommand, - }, -]; diff --git a/packages/lobby/src/handlers/requestConnectGameServer.ts b/packages/lobby/src/handlers/requestConnectGameServer.ts index e978ca59e..1b265176a 100644 --- a/packages/lobby/src/handlers/requestConnectGameServer.ts +++ b/packages/lobby/src/handlers/requestConnectGameServer.ts @@ -1,5 +1,4 @@ import { getPersonasByPersonaId } from "rusty-motors-personas"; -import { type ServiceArgs } from "rusty-motors-shared"; import { LoginInfoMessage } from "../LoginInfoMessage.js"; import { @@ -13,9 +12,9 @@ import { getEncryption, } from "rusty-motors-shared"; import { SerializedBufferOld } from "rusty-motors-shared"; -import { UserInfoMessage } from "../UserInfoMessage.js"; import { databaseManager } from "rusty-motors-database"; import { getServerLogger } from "rusty-motors-shared"; +import { BytableMessage } from "@rustymotors/binary"; /** @@ -48,7 +47,11 @@ export async function _npsRequestGameConnectServer({ connectionId, message, log = getServerLogger("handlers/_npsRequestGameConnectServer"), -}: ServiceArgs): Promise<{ +}: { + connectionId: string; + message: BytableMessage; + log?: ReturnType; +}): Promise<{ connectionId: string; messages: SerializedBufferOld[]; }> { @@ -58,7 +61,7 @@ export async function _npsRequestGameConnectServer({ // by the data payload. const inboundMessage = new LoginInfoMessage(); - inboundMessage.deserialize(message.data); + inboundMessage.deserialize(message.serialize()); log.debug(`LoginInfoMessage: ${inboundMessage.toString()}`); @@ -109,10 +112,19 @@ export async function _npsRequestGameConnectServer({ // We have a session, we are good to go! // Send the response packet - const responsePacket = new UserInfoMessage(); - responsePacket.fromLoginInfoMessage(inboundMessage); + const responsePacket = new BytableMessage(); + responsePacket.header.setMessageVersion(0); + responsePacket.header.setMessageId(0x120); + + responsePacket.setSerializeOrder([ + { name: "userId", field: "Dword" }, + { name: "userName", field: "Container" }, + { name: "userData", field: "Buffer" }, + ]) - responsePacket._header.id = 0x120; + responsePacket.setFieldValueByName("userId", inboundMessage._userId); + responsePacket.setFieldValueByName("userName", inboundMessage._userName); + responsePacket.setFieldValueByName("userData", inboundMessage._userData); // log the packet log.debug( @@ -120,7 +132,9 @@ export async function _npsRequestGameConnectServer({ ); const outboundMessage = new SerializedBufferOld(); - outboundMessage._doDeserialize(responsePacket.serialize()); + outboundMessage.deserialize(responsePacket.serialize()); + + log.debug(`[${connectionId}] Returning with ${outboundMessage.toHexString()}`); return { connectionId, diff --git a/packages/lobby/src/internal.ts b/packages/lobby/src/internal.ts index 7d5dae7c7..42b25d001 100644 --- a/packages/lobby/src/internal.ts +++ b/packages/lobby/src/internal.ts @@ -15,13 +15,11 @@ // along with this program. If not, see . import { SerializedBufferOld, ServerLogger } from "rusty-motors-shared"; -import { NPSMessage } from "rusty-motors-shared"; -import { LegacyMessage } from "rusty-motors-shared"; import { handleEncryptedNPSCommand } from "./handlers/encryptedCommand.js"; import { handleTrackingPing } from "./handlers/handleTrackingPing.js"; import { _npsRequestGameConnectServer } from "./handlers/requestConnectGameServer.js"; -import type { BufferSerializer } from "rusty-motors-shared-packets"; import { getServerLogger } from "rusty-motors-shared"; +import { BytableMessage } from "@rustymotors/binary"; /** * Array of supported message handlers @@ -31,7 +29,7 @@ import { getServerLogger } from "rusty-motors-shared"; * name: string, * handler: (args: { * connectionId: string, - * message: SerializedBufferOld, + * message: BytableMessage, * log: ServerLogger, * }) => Promise<{ * connectionId: string, @@ -43,7 +41,7 @@ export const messageHandlers: { name: string; handler: (args: { connectionId: string; - message: SerializedBufferOld; + message: BytableMessage; log?: ServerLogger; }) => Promise<{ connectionId: string; @@ -84,30 +82,12 @@ export async function receiveLobbyData({ log = getServerLogger( "lobby.receiveLobbyData" ), }: { connectionId: string; - message: BufferSerializer; + message: BytableMessage; log?: ServerLogger; }): Promise<{ connectionId: string; messages: SerializedBufferOld[]; }> { - /** @type {LegacyMessage | NPSMessage} */ - let inboundMessage: LegacyMessage | NPSMessage; - - // Check data length - const dataLength = message.getByteSize(); - - if (dataLength < 4) { - throw Error(`Data length ${dataLength} is too short to deserialize`); - } - - if (dataLength > 12) { - inboundMessage = new NPSMessage(); - } else { - inboundMessage = new LegacyMessage(); - } - - inboundMessage._doDeserialize(message.serialize()); - const data = message.serialize(); log.debug( `Received Lobby packet', @@ -117,21 +97,18 @@ export async function receiveLobbyData({ ); const supportedHandler = messageHandlers.find((h) => { - return h.opCode === inboundMessage._header.id; + return h.opCode === message.header.messageId; }); if (typeof supportedHandler === "undefined") { // We do not yet support this message code - throw Error(`UNSUPPORTED_MESSAGECODE: ${inboundMessage._header.id}`); + throw Error(`UNSUPPORTED_MESSAGECODE: ${message.header.messageId}`); } - const buff = new SerializedBufferOld(); - buff._doDeserialize(data); - try { const result = await supportedHandler.handler({ connectionId, - message: buff, + message }); log.debug(`Returning with ${result.messages.length} messages`); log.debug("Leaving receiveLobbyData"); diff --git a/packages/login/package.json b/packages/login/package.json index 35122ca65..f6cbee003 100644 --- a/packages/login/package.json +++ b/packages/login/package.json @@ -21,7 +21,8 @@ "dependencies": { "@fastify/sensible": "^6.0.3", "@rustymotors/binary": "workspace:^", - "fastify": "^5.2.2" + "fastify": "^5.2.2", + "rusty-motors-personas": "workspace:^" }, "directories": { "test": "test" diff --git a/packages/login/src/NPSUserStatus.ts b/packages/login/src/NPSUserStatus.ts index d1145eb6f..8d4d0ce09 100644 --- a/packages/login/src/NPSUserStatus.ts +++ b/packages/login/src/NPSUserStatus.ts @@ -52,7 +52,7 @@ export class NPSUserStatus extends LegacyMessage { this._config = config; this.log = getServerLogger("NPSUserStatus"); log.debug("Constructing NPSUserStatus"); - this._header._doDeserialize(packet); + this._header.deserialize(packet); this.sessionKey = ""; // Save the NPS opCode diff --git a/packages/login/src/handleLoginData.ts b/packages/login/src/handleLoginData.ts index 8aee24b4b..f8cadbf9d 100644 --- a/packages/login/src/handleLoginData.ts +++ b/packages/login/src/handleLoginData.ts @@ -6,6 +6,7 @@ import { messageHandlers } from "./internal.js"; import { getServerLogger } from "rusty-motors-shared"; import { GamePacket } from "rusty-motors-shared-packets"; import { BytableMessage } from "@rustymotors/binary"; +import {getHandlers} from "rusty-motors-personas"; const defaultLogger = getServerLogger("LoginServer"); @@ -38,17 +39,25 @@ export async function handleLoginData({ // The packet needs to be an NPSMessage const inboundMessage = new NPSMessage(); - inboundMessage._doDeserialize(message.serialize()); + inboundMessage.deserialize(message.serialize()); + let supportedHandler; - const supportedHandler = messageHandlers.find((h) => { + supportedHandler = messageHandlers.find((h) => { return h.opCode === inboundMessage._header.id; }); if (typeof supportedHandler === "undefined") { - // We do not yet support this message code - throw Error( - `[${connectionId}] UNSUPPORTED_MESSAGECODE: ${inboundMessage._header.id}`, - ); + // There is a change this is a message for the persona service + const handlers = getHandlers(); + supportedHandler = handlers.find((h) => { + return h.opCode === inboundMessage._header.id; + }); + + if (typeof supportedHandler === "undefined") { + throw Error( + `[${connectionId}] UNSUPPORTED_MESSAGECODE: ${inboundMessage._header.id}`, + ); + } } try { @@ -60,7 +69,10 @@ export async function handleLoginData({ log.debug( `[${connectionId}] Leaving handleLoginData with ${result.messages.length} messages`, ); - return result; + return { + connectionId, + messages: result.messages, + } } catch (error) { const err = Error(`[${connectionId}] Error in login service`, { cause: error, diff --git a/packages/login/src/login.ts b/packages/login/src/login.ts index f1e586d81..6def58c95 100644 --- a/packages/login/src/login.ts +++ b/packages/login/src/login.ts @@ -108,7 +108,10 @@ export async function login({ // Update the data buffer const response = { connectionId, - messages: [outboundMessage2, outboundMessage2], + messages: [ + outboundMessage2, + outboundMessage2 + ], }; log.debug( `[${connectionId}] Leaving login with ${response.messages.length} messages`, diff --git a/packages/nps/CHANGELOG.md b/packages/nps/CHANGELOG.md deleted file mode 100644 index 296e1ddfe..000000000 --- a/packages/nps/CHANGELOG.md +++ /dev/null @@ -1,7 +0,0 @@ -# rusty-motors-nps - -## 1.0.0-next.0 - -### Patch Changes - -- ae13a4c: Initial changeset diff --git a/packages/nps/gameMessageProcessors/getLobMiniRiffList.ts b/packages/nps/gameMessageProcessors/getLobMiniRiffList.ts deleted file mode 100644 index 1be0c6350..000000000 --- a/packages/nps/gameMessageProcessors/getLobMiniRiffList.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { - GameMessage, - MiniRiffInfo, - MiniRiffList, - getAsHex, -} from "rusty-motors-nps"; -import { getServerLogger } from "rusty-motors-shared"; - -const defaultLogger = getServerLogger("nps.getLobMiniRiffList"); - -// Command id: 0x30c -export async function getLobMiniRiffList( - _commandId: number, - data: Buffer, -): Promise { - defaultLogger.debug("getLobMiniRiffList called"); - defaultLogger.info(`Processing getLobMiniRiffList command: ${getAsHex(data)}`); - - const riffList = new MiniRiffList(); - - riffList.addRiff(new MiniRiffInfo("CTRL", 0, 1)); - riffList.addRiff(new MiniRiffInfo("MC141", 141, 0)); - riffList.addRiff(new MiniRiffInfo("MCCHAT", 191, 0)); - - defaultLogger.info(`getLobMiniRiffList: ${riffList.toString()}`); - - const responseMessage = new GameMessage(0); - responseMessage.header.setId(0x404); - responseMessage.setData(riffList); - - defaultLogger.info("Dumping responseMessage: "); - - defaultLogger.info( - `responseMessage: ${ - responseMessage.serialize().length - } bytes - ${getAsHex(responseMessage.serialize())}`, - ); - - return responseMessage.serialize(); -} diff --git a/packages/nps/gameMessageProcessors/getLobMiniUserList.test.ts b/packages/nps/gameMessageProcessors/getLobMiniUserList.test.ts deleted file mode 100644 index 224840adb..000000000 --- a/packages/nps/gameMessageProcessors/getLobMiniUserList.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { describe, it, expect, vi, Mock } from "vitest"; -import { getLobMiniUserList } from "./getLobMiniUserList"; -import { - GameMessage, - MiniUserInfo, - MiniUserList, -} from "rusty-motors-nps"; - -vi.mock("rusty-motors-nps", () => ({ - GameMessage: vi.fn(), - MiniUserInfo: vi.fn(), - MiniUserList: vi.fn(), - getAsHex: vi.fn(), -})); - -describe("getLobMiniUserList", () => { - it("should process the command and return a serialized response", async () => { - const commandId = 0x128; - const data = Buffer.from([0x01, 0x02, 0x03, 0x04]); - const mockMiniUserList = { - addChannelUser: vi.fn(), - }; - const mockResponseMessage = { - header: { - setId: vi.fn(), - }, - setData: vi.fn(), - serialize: vi.fn().mockReturnValue(Buffer.from([0x05, 0x06, 0x07, 0x08])), - }; - - (MiniUserList as unknown as Mock).mockImplementation( - () => mockMiniUserList, - ); - (GameMessage as unknown as Mock).mockImplementation( - () => mockResponseMessage, - ); - - const result = await getLobMiniUserList(commandId, data); - - expect(mockMiniUserList.addChannelUser).toHaveBeenCalledWith( - new MiniUserInfo(1000, "Molly"), - ); - expect(mockResponseMessage.header.setId).toHaveBeenCalledWith(0x229); - expect(mockResponseMessage.setData).toHaveBeenCalledWith(mockMiniUserList); - expect(result).toEqual(Buffer.from([0x05, 0x06, 0x07, 0x08])); - }); -}); \ No newline at end of file diff --git a/packages/nps/gameMessageProcessors/getLobMiniUserList.ts b/packages/nps/gameMessageProcessors/getLobMiniUserList.ts deleted file mode 100644 index 2c37c9c23..000000000 --- a/packages/nps/gameMessageProcessors/getLobMiniUserList.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { - GameMessage, - MiniUserInfo, - MiniUserList, - getAsHex, -} from "rusty-motors-nps"; -import { getServerLogger } from "rusty-motors-shared"; - -const defaultLogger = getServerLogger("nps.getLobMiniUserList"); - -// Command id: 0x128 -export async function getLobMiniUserList( - _commandId: number, - data: Buffer, -): Promise { - defaultLogger.debug("getLobMiniUserList called"); - defaultLogger.info(`Processing getLobMiniUserList command: ${getAsHex(data)}`); - - const miniUserList = new MiniUserList(0); - - miniUserList.addChannelUser(new MiniUserInfo(1000, "Molly")); - - const responseMessage = new GameMessage(0); - responseMessage.header.setId(0x229); - responseMessage.setData(miniUserList); - - return Promise.resolve(responseMessage.serialize()); -} diff --git a/packages/nps/gameMessageProcessors/index.ts b/packages/nps/gameMessageProcessors/index.ts deleted file mode 100644 index 1528a1bd5..000000000 --- a/packages/nps/gameMessageProcessors/index.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { GameMessage } from "../messageStructs/GameMessage.js"; -import type { UserStatus } from "../messageStructs/UserStatus.js"; -import { processCheckPlateText } from "./processCheckPlateText.js"; -import { processCheckProfileName } from "./processCheckProfileName.js"; -import { processCreateProfile } from "./processCreateProfile.js"; -import { processDeleteProfile } from "./processDeleteProfile.js"; -import { processEncryptedGameCommand } from "./processEncryptedGameCommand.js"; -import { processGameLogin } from "./processGameLogin.js"; -import { processGameLogout } from "./processGameLogout.js"; -import { processFirstBuddy } from "./processGetFirstBuddy.js"; -import { processGetProfileInfo } from "./processGetProfileInfo.js"; -import { processGetProfileMaps } from "./processGetProfileMaps.js"; -import { processPing } from "./processPing.js"; -import { processSelectPersona } from "./processSelectPersona.js"; -import { processUserLogin } from "./processUserLogin.js"; - -export type GameSocketCallback = (messages: Buffer[]) => void; - -export type MessageProcessor = ( - connectionId: string, - userStatus: UserStatus, - message: GameMessage, - socketCallback: GameSocketCallback, -) => Promise; - -export class MessageProcessorError extends Error { - constructor(id: number, message: string) { - super(`MessageProcessorError: ${id}: ${message}`); - } -} - -export const gameMessageProcessors = new Map([]); - -export function populateGameMessageProcessors( - processors: Map, -): void { - processors.set(256, processUserLogin); // 0x100 - processors.set(535, processPing); // 0x217 - processors.set(1281, processGameLogin); // 0x501 - processors.set(1283, processSelectPersona); // 0x503 - processors.set(1287, processCreateProfile); // 0x507 - processors.set(1291, processFirstBuddy); // 0x50b - processors.set(1295, processGameLogout); // 0x50f - processors.set(1298, processDeleteProfile); // 0x512 - processors.set(1305, processGetProfileInfo); // 0x519 - processors.set(1330, processGetProfileMaps); // 0x532 - processors.set(1331, processCheckProfileName); // 0x533 - processors.set(1332, processCheckPlateText); // 0x534 - processors.set(4353, processEncryptedGameCommand); // 0x1101 -} - -export function getGameMessageProcessor(messageId: number) { - if (gameMessageProcessors.has(messageId) === true) { - return gameMessageProcessors.get(messageId) as MessageProcessor; - } - - return undefined; -} - -export class PortMapError extends Error { - port: number; - constructor(port: number, message: string) { - super(message); - this.name = "PortMapError"; - this.port = port; - } - - override toString(): string { - return `${this.name}: ${this.message}`; - } -} - -export const portToMessageTypes = new Map([]); - -export function populatePortToMessageTypes(portMap: Map): void { - portMap.set(7003, "Game"); - portMap.set(8226, "Game"); - portMap.set(8227, "Game"); - portMap.set(8228, "Game"); - portMap.set(9000, "Game"); - portMap.set(43300, "Server"); -} - -export function getPortMessageType(port: number): string { - if (portToMessageTypes.has(port) === true) { - // @ts-expect-error - Since has() is true, the return value is NOT undefined - return portToMessageTypes.get(port); - } - throw new PortMapError(port, "No message type found"); -} diff --git a/packages/nps/gameMessageProcessors/lobbyCommands.ts b/packages/nps/gameMessageProcessors/lobbyCommands.ts deleted file mode 100644 index 8cbffa986..000000000 --- a/packages/nps/gameMessageProcessors/lobbyCommands.ts +++ /dev/null @@ -1,15 +0,0 @@ -// 030c0004cdcdcdcd - -import { getLobMiniRiffList } from "./getLobMiniRiffList.js"; -import { getLobMiniUserList } from "./getLobMiniUserList.js"; - -// 0128000800000000 - -export type lobbyCommandProcessor = ( - commandId: number, - data: Buffer, -) => Promise; - -export const lobbyCommandMap = new Map(); -lobbyCommandMap.set(0x30c, getLobMiniRiffList); -lobbyCommandMap.set(0x128, getLobMiniUserList); diff --git a/packages/nps/gameMessageProcessors/processCheckPlateText.ts b/packages/nps/gameMessageProcessors/processCheckPlateText.ts deleted file mode 100644 index c7981f18c..000000000 --- a/packages/nps/gameMessageProcessors/processCheckPlateText.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { GameMessage } from "../messageStructs/GameMessage.js"; -import type { UserStatus } from "../messageStructs/UserStatus.js"; -import { getLenString } from "../src/utils/pureGet.js"; -import { sendNPSAck } from "../src/utils/sendNPSAck.js"; -import type { GameSocketCallback } from "./index.js"; -import { getServerLogger } from "rusty-motors-shared"; - -const defaultLogger = getServerLogger("nps.processCheckPlateText"); - -export async function processCheckPlateText( - _connectionId: string, - _userStatus: UserStatus, - message: GameMessage, - socketCallback: GameSocketCallback, -): Promise { - defaultLogger.info("processCheckPlateText called"); - const plateType = message.getDataAsBuffer().readUInt32BE(0); - - const requestedPlateText = getLenString(message.getDataAsBuffer(), 4, false); - - defaultLogger.info( - `Requested plate text: ${requestedPlateText} for plate type ${plateType}`, - ); - - sendNPSAck(socketCallback); - return Promise.resolve(); -} diff --git a/packages/nps/gameMessageProcessors/processCheckProfileName.ts b/packages/nps/gameMessageProcessors/processCheckProfileName.ts deleted file mode 100644 index 3993edaaa..000000000 --- a/packages/nps/gameMessageProcessors/processCheckProfileName.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { GameMessage } from "../messageStructs/GameMessage.js"; -import { getLenString } from "../src/utils/pureGet.js"; -import type { GameSocketCallback } from "./index.js"; - -import type { UserStatus } from "../messageStructs/UserStatus.js"; -import { getServerLogger } from "rusty-motors-shared"; - -const defaultLogger = getServerLogger("nps.processCheckProfileName"); - -export async function processCheckProfileName( - _connectionId: string, - _userStatus: UserStatus, - message: GameMessage, - socketCallback: GameSocketCallback, -): Promise { - defaultLogger.info("processCheckProfileName called"); - const customerId = message.serialize().readUInt32BE(8); - - const requestedPersonaName = getLenString(message.serialize(), 12, false); - - defaultLogger.info( - `Requested persona name: ${requestedPersonaName} for customer ${customerId}`, - ); - - const response = new GameMessage(0); - response.header.setId(0x601); - - const responseBytes = response.serialize(); - - socketCallback([responseBytes]); - return Promise.resolve(); -} diff --git a/packages/nps/gameMessageProcessors/processCreateProfile.ts b/packages/nps/gameMessageProcessors/processCreateProfile.ts deleted file mode 100644 index 244175ce0..000000000 --- a/packages/nps/gameMessageProcessors/processCreateProfile.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { GameMessage } from "../messageStructs/GameMessage.js"; -import { GameProfile } from "../messageStructs/GameProfile.js"; -import type { UserStatus } from "../messageStructs/UserStatus.js"; -import type { GameSocketCallback } from "./index.js"; -import { getServerLogger } from "rusty-motors-shared"; - -export async function processCreateProfile( - _connectionId: string, - _userStatus: UserStatus, - message: GameMessage, - socketCallback: GameSocketCallback, -): Promise { - const defaultLogger = getServerLogger("nps.processCreateProfile"); - // Log the request - defaultLogger.info(`ProcessCreateProfile request: ${message.toString()}`); - - const createProfileMessage = GameProfile.fromBytes(message.getDataAsBuffer()); - - // Log the request - defaultLogger.info(`ProcessCreateProfile request: ${createProfileMessage.toString()}`); - - // TODO: Add the profile - - // TODO: Send the response - const response = new GameMessage(257); - response.header.setId(0x601); - - response.setData(message.getData()); - - // Log the response - defaultLogger.info(`ProcessCreateProfile response: ${response.toString()}`); - - socketCallback([response.serialize()]); - return Promise.resolve(); -} diff --git a/packages/nps/gameMessageProcessors/processDeleteProfile.ts b/packages/nps/gameMessageProcessors/processDeleteProfile.ts deleted file mode 100644 index 2d727d222..000000000 --- a/packages/nps/gameMessageProcessors/processDeleteProfile.ts +++ /dev/null @@ -1,108 +0,0 @@ -import crypto from "node:crypto"; -import fs from "node:fs"; -import { GameMessage } from "../messageStructs/GameMessage.js"; -import { SessionKey } from "../messageStructs/SessionKey.js"; -import { getLenString } from "../src/utils/pureGet.js"; -import type { GameSocketCallback } from "./index.js"; - -import type { UserStatus } from "../messageStructs/UserStatus.js"; -import { getServerLogger } from "rusty-motors-shared"; - -const defaultLogger = getServerLogger("nps.processDeleteProfile"); - -export function loadPrivateKey(path: string): string { - const privateKey = fs.readFileSync(path); - - return privateKey.toString("utf8"); -} - -export function decryptSessionKey( - encryptedSessionKey: string, - privateKey: string, -): string { - const sessionKeyStructure = crypto.privateDecrypt( - privateKey, - Buffer.from(encryptedSessionKey, "hex"), - ); - - return sessionKeyStructure.toString("hex"); -} - -export function unpackUserLoginMessage(message: GameMessage): { - sessionKey: string; - gameId: string; - contextToken: string; -} { - // Get the context token - const ticket = getLenString(message.getDataAsBuffer(), 0, false); - - let dataOffset = ticket.length + 2; - - // The next data structure is a container with an empty id, a length, and a data structure - - // Skip the empty id - dataOffset += 2; - - // Get the next data length - const nextDataLength = message.getDataAsBuffer().readUInt16BE(dataOffset); - - // This value is the encrypted session key hex, stored as a string - const encryptedSessionKey = message - .getDataAsBuffer() - .subarray(dataOffset + 2, dataOffset + 2 + nextDataLength) - .toString("utf8"); - - // Load the private key - const privateKey = loadPrivateKey("./data/private_key.pem"); - - // Decrypt the session key - const sessionKey = decryptSessionKey(encryptedSessionKey, privateKey); - - // Unpack the session key - const sessionKeyStructure = SessionKey.fromBytes( - Buffer.from(sessionKey, "hex"), - ); - - // Update the data offset - dataOffset += 2 + nextDataLength; - - // Get the next data length - const nextDataLength2 = message.getDataAsBuffer().readUInt16BE(dataOffset); - - // This value is the game id (used by server to identify the game) - const gameId = message - .getDataAsBuffer() - .subarray(dataOffset + 2, dataOffset + 2 + nextDataLength2) - .toString("utf8"); - - // Update the data offset - dataOffset += 2 + nextDataLength2; - - // Return the session key, game id, and context token - return { - sessionKey: sessionKeyStructure.getKey(), - gameId, - contextToken: ticket, - }; -} - -export async function processDeleteProfile( - _connectionId: string, - _userStatus: UserStatus, - message: GameMessage, - socketCallback: GameSocketCallback, -): Promise { - defaultLogger.debug("processDeleteProfile called"); - // Log the message - defaultLogger.info(`Delete profile request: ${message.toString()}`); - - // TODO: Delete the profile - - // Create a new message - Login ACK - const loginACK = new GameMessage(0); - loginACK.header.setId(0x60c); - - // Send the ack - socketCallback([loginACK.serialize()]); - return Promise.resolve(); -} diff --git a/packages/nps/gameMessageProcessors/processEncryptedGameCommand.ts b/packages/nps/gameMessageProcessors/processEncryptedGameCommand.ts deleted file mode 100644 index 0147d4dc3..000000000 --- a/packages/nps/gameMessageProcessors/processEncryptedGameCommand.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { - GameMessage, - SerializableData, -} from "../messageStructs/GameMessage.js"; -import { - type EncryptionSession, - getEncryptionSession, - newEncryptionSession, - setEncryptionSession, -} from "../src/EncryptionSession.js"; -import { getAsHex } from "../src/utils/pureGet.js"; -import type { GameSocketCallback } from "./index.js"; -import { lobbyCommandMap } from "./lobbyCommands.js"; - -import type { UserStatus } from "../messageStructs/UserStatus.js"; -import { getServerLogger } from "rusty-motors-shared"; - -const defaultLogger = getServerLogger("nps.processEncryptedGameCommand"); - -export async function processEncryptedGameCommand( - connectionId: string, - userStatus: UserStatus, - message: GameMessage, - socketCallback: GameSocketCallback, -): Promise { - defaultLogger.debug("processEncryptedGameCommand called"); - defaultLogger.info(`Attempting to decrypt message: ${message.toString()}`); - - // Get the encryption session - let encryptionSession: EncryptionSession | undefined = - getEncryptionSession(connectionId); - - // If the encryption session doesn't exist, attempt to create it - if (typeof encryptionSession === "undefined") { - try { - // Create the encryption session - const newSession = newEncryptionSession({ - connectionId, - customerId: userStatus.getCustomerId(), - sessionKey: userStatus.getSessionKey().getKey().substring(0, 16), - }); - setEncryptionSession(newSession); - encryptionSession = newSession; - } catch (error) { - defaultLogger.error(`Error creating encryption session: ${error as string}`); - throw new Error("Error creating encryption session"); - } - - // Log the encryption session - defaultLogger.info(`Created encryption session for ${userStatus.getCustomerId()}`); - } - - // Attempt to decrypt the message - const decryptedbytes = encryptionSession.gameDecipher.update( - message.getDataAsBuffer(), - ); - - // Log the decrypted bytes - defaultLogger.info(`Decrypted bytes: ${getAsHex(decryptedbytes)}`); - - // Set the decrypted bytes as a new message - const decryptedMessage = new GameMessage(0); - decryptedMessage.deserialize(decryptedbytes); - - // Log the decrypted message id - defaultLogger.info(`Decrypted message ID: ${decryptedMessage.header.getId()}`); - - // Do we have a valid message processor? - const processor = lobbyCommandMap.get(decryptedMessage.header.getId()); - - if (typeof processor === "undefined") { - const err = `No processor found for message ID: ${decryptedMessage.header.getId()}`; - defaultLogger.fatal(err); - throw Error(err); - } - - // Process the message - const response = await processor( - decryptedMessage.header.getId(), - decryptedMessage.getDataAsBuffer(), - ); - - // Log the response - defaultLogger.info(`Response: ${response.length} bytes, ${getAsHex(response)}`); - - // Encrypt the response - const encryptedResponse = encryptionSession.gameCipher.update(response); - setEncryptionSession(encryptionSession); - - // Log the encrypted response - defaultLogger.info( - `Encrypted response: ${encryptedResponse.length} bytes, ${getAsHex( - encryptedResponse, - )}`, - ); - - const responsePacket = new GameMessage(0); - responsePacket.header.setId(0x1101); - - const responseData = new SerializableData(encryptedResponse.length); - responseData.deserialize(encryptedResponse); - - responsePacket.setData(responseData); - defaultLogger.info( - `Response packet: ${responsePacket.header.getLength()} bytes, ${getAsHex( - responsePacket.serialize(), - )}`, - ); - const responseBytes = responsePacket.serialize(); - - socketCallback([responseBytes]); - return Promise.resolve(); -} diff --git a/packages/nps/gameMessageProcessors/processGameLogin.ts b/packages/nps/gameMessageProcessors/processGameLogin.ts deleted file mode 100644 index 11af98812..000000000 --- a/packages/nps/gameMessageProcessors/processGameLogin.ts +++ /dev/null @@ -1,207 +0,0 @@ -import crypto from "node:crypto"; -import fs from "node:fs"; -import * as Sentry from "@sentry/node"; -import { getServerConfiguration } from "rusty-motors-shared"; -import { GameMessage } from "../messageStructs/GameMessage.js"; -import { SessionKey } from "../messageStructs/SessionKey.js"; -import { UserStatus } from "../messageStructs/UserStatus.js"; -import { getToken } from "../services/token.js"; -import { UserStatusManager } from "../src/UserStatusManager.js"; -import { getAsHex, getLenString } from "../src/utils/pureGet.js"; -import type { ISerializable } from "../types.js"; -import type { GameSocketCallback } from "./index.js"; -import { getServerLogger } from "rusty-motors-shared"; - -const defaultLogger = getServerLogger("nps.processGameLogin"); - -export function loadPrivateKey(path: string): string { - const privateKey = fs.readFileSync(path); - - return privateKey.toString("utf8"); -} - -export function decryptSessionKey( - encryptedSessionKey: string, - privateKey: string, -): string { - const sessionKeyStructure = crypto.privateDecrypt( - privateKey, - Buffer.from(encryptedSessionKey, "hex"), - ); - - return sessionKeyStructure.toString("hex"); -} - -export function unpackUserLoginMessage(message: ISerializable): { - sessionKey: string; - gameId: string; - contextToken: string; -} { - defaultLogger.debug("unpackUserLoginMessage called"); - defaultLogger.info(`Unpacking user login message: ${getAsHex(message.serialize())}`); - - // Get the context token - const ticket = getLenString(message.serialize(), 0, false); - - let dataOffset = ticket.length + 2; - - // The next data structure is a container with an empty id, a length, and a data structure - - // Skip the empty id - dataOffset += 2; - - // Get the next data length - const nextDataLength = message.serialize().readUInt16BE(dataOffset); - - // This value is the encrypted session key hex, stored as a string - const encryptedSessionKey = message - .serialize() - .subarray(dataOffset + 2, dataOffset + 2 + nextDataLength) - .toString("utf8"); - - // Load the private key - const privateKey = loadPrivateKey(getServerConfiguration().privateKeyFile); - - // Decrypt the session key - const sessionKey = decryptSessionKey(encryptedSessionKey, privateKey); - - defaultLogger.info( - `Decrypted session key: ${getAsHex(Buffer.from(sessionKey, "hex"))}`, - ); - - // Unpack the session key - const sessionKeyStructure = SessionKey.fromBytes( - Buffer.from(sessionKey, "hex"), - ); - - defaultLogger.info(`Session key structure: ${sessionKeyStructure.toString()}`); - - // Update the data offset - dataOffset += 2 + nextDataLength; - - // Get the next data length - const nextDataLength2 = message.serialize().readUInt16BE(dataOffset); - - // This value is the game id (used by server to identify the game) - const gameId = message - .serialize() - .subarray(dataOffset + 2, dataOffset + 2 + nextDataLength2) - .toString("utf8"); - - // Update the data offset - dataOffset += 2 + nextDataLength2; - - // Return the session key, game id, and context token - return { - sessionKey: sessionKeyStructure.getKey(), - gameId, - contextToken: ticket, - }; -} - -/** - * This is the initial connection to the Login server - */ -export async function processGameLogin( - _connectionId: string, - userStatus: UserStatus, - message: GameMessage, - socketCallback: GameSocketCallback, -): Promise { - Sentry.startSpan( - { - name: "processLogin", - op: "processLogin", - }, - () => { - defaultLogger.debug("processGameLogin called"); - - defaultLogger.info(`Login: ${message.toString()}`); - - // Unpack the message - try { - const { sessionKey, contextToken } = unpackUserLoginMessage( - message.getData(), - ); - - // Log the context token - defaultLogger.info(`Context token: ${contextToken}`); - - // Log the session key - defaultLogger.info(`Session key: ${sessionKey}`); - - // Look up the customer id - const user = getToken(contextToken); - - // If the user is not found, return an error - if (user === undefined) { - defaultLogger.error(`User not found for context token: ${contextToken}`); - - // Create a new message - Not found - const response = new GameMessage(0); - response.header.setId(0x602); - - // Send the message - twice - Sentry.startSpan( - { - name: "socketCallback", - op: "socketCallback", - }, - () => { - socketCallback([response.serialize()]); - }, - ); - Sentry.startSpan( - { - name: "socketCallback", - op: "socketCallback", - }, - () => { - socketCallback([response.serialize()]); - }, - ); - - return; - } - - // Log the user - defaultLogger.info(`User: ${user.customerId}`); - - // Create a new message - Login ACK - const loginACK = new GameMessage(0); - loginACK.header.setId(0x601); - - // Send the ack - socketCallback([loginACK.serialize()]); - - // Update the user status - userStatus.setCustomerId(user.customerId); - userStatus.setPersonaId(0); - userStatus.ban.set({ - initiator: "Molly", - startComment: "Because I said so", - }); - userStatus.setSessionKey(SessionKey.fromKeyString(sessionKey)); - - UserStatusManager.addUserStatus(userStatus); - // Create a new message - UserStatus - const userStatusMessage = new GameMessage(257); - userStatusMessage.header.setId(0x601); - - userStatusMessage.setData(userStatus); - - // Log the message - defaultLogger.info(`UserStatus: ${userStatusMessage.toString()}`); - - // Send the message - socketCallback([userStatusMessage.serialize()]); - socketCallback([userStatusMessage.serialize()]); - - return; - } catch (e) { - console.error(e); - } - }, - ); - return Promise.resolve(); -} diff --git a/packages/nps/gameMessageProcessors/processGameLogout.ts b/packages/nps/gameMessageProcessors/processGameLogout.ts deleted file mode 100644 index fc245bb24..000000000 --- a/packages/nps/gameMessageProcessors/processGameLogout.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { GameMessage } from "../messageStructs/GameMessage.js"; -import type { UserStatus } from "../messageStructs/UserStatus.js"; -import { sendNPSAck } from "../src/utils/sendNPSAck.js"; -import type { GameSocketCallback } from "./index.js"; - -export async function processGameLogout( - _connectionId: string, - _userStatus: UserStatus, - _message: GameMessage, - socketCallback: GameSocketCallback, -): Promise { - sendNPSAck(socketCallback); - return Promise.resolve(); -} diff --git a/packages/nps/gameMessageProcessors/processGetFirstBuddy.ts b/packages/nps/gameMessageProcessors/processGetFirstBuddy.ts deleted file mode 100644 index 59a08aea2..000000000 --- a/packages/nps/gameMessageProcessors/processGetFirstBuddy.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - GameMessage, - // ProfileList, - SerializableData, - getDWord, - // getGameProfilesForCustomerId, -} from "rusty-motors-nps"; -import type { GameSocketCallback } from "./index.js"; - -import type { UserStatus } from "rusty-motors-nps"; -import { getServerLogger } from "rusty-motors-shared"; - -const defaultLogger = getServerLogger("nps.processFirstBuddy"); - -export async function processFirstBuddy( - _connectionId: string, - _userStatus: UserStatus, - message: GameMessage, - socketCallback: GameSocketCallback, -): Promise { - defaultLogger.info("processFirstBuddy called"); - const profileId = getDWord(message.getDataAsBuffer(), 0, false); - - defaultLogger.info(`GetFirstBuddy profile: ${profileId}`); - - // TO: Look up the profiles for the customer ID - // const _profiles = getGameProfilesForCustomerId(profileId); - - // TO: Create a new NPSList of profiles - // const _list = new ProfileList(); - - const outMessage = new GameMessage(257); - outMessage.header.setId(0x614); - outMessage.setData(new SerializableData(4)); - - // Log the message - defaultLogger.info(`GetFirstBuddy: ${outMessage.toString()}`); - - defaultLogger.info("==========================================="); - - socketCallback([outMessage.serialize()]); - return Promise.resolve(); -} diff --git a/packages/nps/gameMessageProcessors/processGetProfileInfo.ts b/packages/nps/gameMessageProcessors/processGetProfileInfo.ts deleted file mode 100644 index 7f475d27f..000000000 --- a/packages/nps/gameMessageProcessors/processGetProfileInfo.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { - GameMessage, - ProfileList, - getAsHex, - getDWord, - getGameProfilesForCustomerId, -} from "rusty-motors-nps"; -import type { GameSocketCallback } from "./index.js"; - -import type { UserStatus } from "rusty-motors-nps"; -import { getServerLogger } from "rusty-motors-shared"; - -const defaultLogger = getServerLogger("nps.processGetProfileInfo"); - -export async function processGetProfileInfo( - _connectionId: string, - _userStatus: UserStatus, - message: GameMessage, - socketCallback: GameSocketCallback, -): Promise { - const customerId = getDWord(message.serialize(), 0, false); - - defaultLogger.info(`GetProfileInfo: ${customerId}`); - - // Look up the profiles for the customer ID - const profiles = getGameProfilesForCustomerId(customerId); - - // Create a new NPSList of profiles - const list = new ProfileList(); - - const outMessage = new GameMessage(0); - - // Add each profile to the list - if (profiles) { - outMessage.header.setId(0x607); - for (const profile of profiles) { - // Log the profile - defaultLogger.info(`GetProfileInfo: ${profile.toString()}`); // TODO: Remove this line - - list.addProfile(profile); - } - } else { - outMessage.header.setId(0x602); - } - - // Send the list back to the client - try { - // Log the message data - defaultLogger.info(`GetProfileInfo: ${getAsHex(list.serialize())}`); - - outMessage.setData(list); - - // Log the message - defaultLogger.info(`GetProfileInfo: ${outMessage.toString()}`); - - defaultLogger.info("==========================================="); - - socketCallback([outMessage.serialize()]); - return Promise.resolve(); - } catch (error) { - defaultLogger.error(`Error sending profile info: ${error as string}`); - throw new Error("Error sending profile info"); - } -} diff --git a/packages/nps/gameMessageProcessors/processGetProfileMaps.ts b/packages/nps/gameMessageProcessors/processGetProfileMaps.ts deleted file mode 100644 index a6d981c14..000000000 --- a/packages/nps/gameMessageProcessors/processGetProfileMaps.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { - GameMessage, - ProfileList, - getAsHex, - getDWord, - getGameProfilesForCustomerId, -} from "rusty-motors-nps"; -import type { GameSocketCallback } from "./index.js"; -import type { UserStatus } from "rusty-motors-nps"; -import { getServerLogger } from "rusty-motors-shared"; - -export async function processGetProfileMaps( - _connectionId: string, - _userStatus: UserStatus, - message: GameMessage, - socketCallback: GameSocketCallback, -): Promise { - const defaultLogger = getServerLogger("nps.processGetProfileMaps"); - // This message is a version 257, but it's version is set to 0 - // This is a bug in the client, so we need to generate a new message - // with the correct version - const requestMessage = GameMessage.fromGameMessage(257, message); - - defaultLogger.info(`GetProfileMaps (257): ${requestMessage.toString()}`); - - const customerId = getDWord(requestMessage.getDataAsBuffer(), 0, false); - - defaultLogger.info(`GetProfileMaps: ${customerId}`); - - // Look up the profiles for the customer ID - const profiles = getGameProfilesForCustomerId(customerId); - - // Create a new NPSList of profiles - const list = new ProfileList(); - - // Add each profile to the list - if (profiles) { - for (const profile of profiles) { - // Log the profile - defaultLogger.info(`GetProfileMaps: ${profile.toString()}`); - - list.addProfile(profile); - } - } - - // Send the list back to the client - try { - const outMessage = new GameMessage(257); - outMessage.header.setId(0x607); - - // Log the message data - defaultLogger.info(`GetProfileMaps: ${getAsHex(outMessage.serialize())}`); - - outMessage.setData(list); - - // Log the message - defaultLogger.info(`GetProfileMaps: ${outMessage.toString()}`); - - defaultLogger.info("==========================================="); - - socketCallback([outMessage.serialize()]); - return Promise.resolve(); - } catch (error) { - defaultLogger.error(`Error sending profile info: ${error as string}`); - throw new Error("Error sending profile info"); - } -} diff --git a/packages/nps/gameMessageProcessors/processPing.ts b/packages/nps/gameMessageProcessors/processPing.ts deleted file mode 100644 index fc01ef2a3..000000000 --- a/packages/nps/gameMessageProcessors/processPing.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { GameMessage } from "rusty-motors-nps"; -import type { GameSocketCallback } from "./index.js"; - -import type { UserStatus } from "rusty-motors-nps"; -import { sendNPSAck } from "rusty-motors-nps"; -import { getServerLogger } from "rusty-motors-shared"; - -const defaultLogger = getServerLogger("nps.processPing"); - -export async function processPing( - _connectionId: string, - _userStatus: UserStatus, - message: GameMessage, - socketCallback: GameSocketCallback, -): Promise { - defaultLogger.info(`Ping: ${message.toString()}`); - - sendNPSAck(socketCallback); - return Promise.resolve(); -} diff --git a/packages/nps/gameMessageProcessors/processSelectPersona.ts b/packages/nps/gameMessageProcessors/processSelectPersona.ts deleted file mode 100644 index a6aa5ecbe..000000000 --- a/packages/nps/gameMessageProcessors/processSelectPersona.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { GameMessage, getDWord } from "rusty-motors-nps"; -import type { GameSocketCallback } from "./index.js"; - -import { UserStatus, UserStatusManager, sendNPSAck } from "rusty-motors-nps"; -import { getServerLogger } from "rusty-motors-shared"; - -const defaultLogger = getServerLogger("nps.processSelectPersona"); - -export async function processSelectPersona( - _connectionId: string, - _userStatus: UserStatus, - message: GameMessage, - socketCallback: GameSocketCallback, -): Promise { - defaultLogger.info(`SelectPersona: ${message.toString()}`); - - const customerId = getDWord(message.getDataAsBuffer(), 0, false); - - const personaId = getDWord(message.getDataAsBuffer(), 4, false); - - const shardId = getDWord(message.getDataAsBuffer(), 8, false); - - // Log the values - defaultLogger.info(`Customer ID: ${customerId}`); - defaultLogger.info(`Persona ID: ${personaId}`); - defaultLogger.info(`Shard ID: ${shardId}`); - - // Lookup the session - const existingStatus = UserStatusManager.getUserStatus(customerId); - - if (!existingStatus) { - defaultLogger.error(`UserStatus not found for customer ID ${customerId}`); - throw new Error(`UserStatus not found for customer ID ${customerId}`); - } - - defaultLogger.info( - `Setting persona ID to ${personaId} for ${existingStatus.getCustomerId()}`, - ); - - // Update the user status - existingStatus.setPersonaId(personaId); - - defaultLogger.info(`GameLogin: ${message.toString()}`); - - sendNPSAck(socketCallback); - return Promise.resolve(); -} diff --git a/packages/nps/gameMessageProcessors/processUserLogin.ts b/packages/nps/gameMessageProcessors/processUserLogin.ts deleted file mode 100644 index cba4342f1..000000000 --- a/packages/nps/gameMessageProcessors/processUserLogin.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { - GameMessage, - UserInfo, - getDWord, - getLenString, -} from "rusty-motors-nps"; -import type { GameSocketCallback } from "./index.js"; - -import type { UserStatus } from "rusty-motors-nps"; -import { UserStatusManager, getCustomerId } from "rusty-motors-nps"; -import { getServerLogger } from "rusty-motors-shared"; - -const defaultLogger = getServerLogger("nps.processUserLogin"); - -export async function processUserLogin( - _connectionId: string, - _userStatus: UserStatus, - message: GameMessage, - socketCallback: GameSocketCallback, -): Promise { - defaultLogger.info(`UserLogin: ${message.toString()}`); - - // This message is a BareMessageV0 - - const personaId = getDWord(message.getDataAsBuffer(), 0, false); - - const profileName = getLenString(message.getDataAsBuffer(), 4, false); - - // Lookup customerID from personaID - const customerID = getCustomerId(personaId); - - if (customerID === -1) { - defaultLogger.error(`CustomerID not found for personaID: ${personaId}`); - throw new Error(`CustomerID not found for personaID: ${personaId}`); - } - - defaultLogger.info(`LobbyLogin: ${personaId} ${profileName} ${customerID}`); - - const existingStatus = UserStatusManager.getUserStatus(customerID); - - if (typeof existingStatus === "undefined") { - defaultLogger.error(`UserStatus not found for customerID: ${customerID}`); - throw new Error(`UserStatus not found for customerID: ${customerID}`); - } - - // Update the user status - existingStatus.setPersonaId(personaId); - - _userStatus = existingStatus; - - defaultLogger.info(`LobbyLogin: ${message.toString()}`); - - const response = new UserInfo(personaId, profileName); - - defaultLogger.info(`Sending response: ${response.toString()}`); - - const responseMessage = new GameMessage(0); - responseMessage.header.setId(0x120); - - responseMessage.setData(response); - - defaultLogger.info(`Response message: ${responseMessage.toString()}`); - - const responseBytes = responseMessage.serialize(); - - socketCallback([responseBytes]); -} diff --git a/packages/nps/index.ts b/packages/nps/index.ts deleted file mode 100644 index ae866827b..000000000 --- a/packages/nps/index.ts +++ /dev/null @@ -1,50 +0,0 @@ -export { - MessageProcessorError, - PortMapError, - gameMessageProcessors, - getGameMessageProcessor, - getPortMessageType, - populateGameMessageProcessors, - populatePortToMessageTypes, - portToMessageTypes, - type GameSocketCallback, -} from "./gameMessageProcessors/index.js"; -export { processGameLogin } from "./gameMessageProcessors/processGameLogin.js"; -export { GameMessage, SerializableData } from "./messageStructs/GameMessage.js"; -export { MiniRiffInfo, MiniRiffList } from "./messageStructs/MiniRiffList.js"; -export { MiniUserInfo, MiniUserList } from "./messageStructs/MiniUserList.js"; -export { ProfileList } from "./messageStructs/ProfileList.js"; -export { UserInfo } from "./messageStructs/UserInfo.js"; -export { UserStatus } from "./messageStructs/UserStatus.js"; -export { - gameProfiles, - getCustomerId, - getGameProfilesForCustomerId, - createGameProfile, -} from "./services/profile.js"; -export { generateToken } from "./services/token.js"; -export { UserStatusManager } from "./src/UserStatusManager.js"; -export { isOnlyOneSet } from "./src/utils/pureCompare.js"; -export { - getAsHex, - getDWord, - getLenBlob, - getLenString, - getNBytes, - getShortBool, - getWord, -} from "./src/utils/pureGet.js"; -export { - put16, - put16BE, - put16LE, - put32, - put32BE, - put32LE, - put8, - putLenBlob, - putLenString, - putShortBool, -} from "./src/utils/purePut.js"; -export { sendNPSAck } from "./src/utils/sendNPSAck.js"; -export * from "./types.js"; diff --git a/packages/nps/messageStructs/BaseSerializable.ts b/packages/nps/messageStructs/BaseSerializable.ts deleted file mode 100644 index 8f99d762f..000000000 --- a/packages/nps/messageStructs/BaseSerializable.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { ISerializable } from "../types"; - -export class BaseSerializable implements ISerializable { - serialize(): Buffer { - throw new Error("Method not implemented."); - } - deserialize(_data: Buffer): void { - throw new Error("Method not implemented."); - } - getByteSize(): number { - throw new Error("Method not implemented."); - } - toString(): string { - throw new Error("Method not implemented."); - } -} diff --git a/packages/nps/messageStructs/GameMessage.ts b/packages/nps/messageStructs/GameMessage.ts deleted file mode 100644 index 6ab03454d..000000000 --- a/packages/nps/messageStructs/GameMessage.ts +++ /dev/null @@ -1,204 +0,0 @@ -import type { IMessage, IMessageHeader, ISerializable } from "rusty-motors-nps"; - -export class MessageHeader implements IMessageHeader { - private version: 0 | 257; - private id: number; - private length: number; - - constructor(version: 0 | 257, id: number, length: number) { - if (version !== 0 && version !== 257) { - throw new Error(`Invalid version ${parseInt(version)}`); - } - this.version = version; - this.id = id; - this.length = length !== 0 ? length : this.getByteSize(); - } - getDataOffset(): number { - return this.getVersion() === 0 ? 4 : 12; - } - getByteSize(): number { - return this.getVersion() === 0 ? 4 : 12; - } - - getVersion(): number { - return this.version; - } - getId(): number { - return this.id; - } - getLength(): number { - return this.length; - } - setVersion(version: 0 | 257): void { - if (version !== 0 && version !== 257) { - throw new Error(`Invalid version ${parseInt(version)}`); - } - this.version = version; - } - setId(id: number): void { - this.id = id; - } - setLength(length: number): void { - this.length = length; - } - - private serializeV0(): Buffer { - const buffer = Buffer.alloc(this.getByteSize()); - buffer.writeUInt16BE(this.id, 0); - buffer.writeUInt16BE(this.length, 2); - - return buffer; - } - - private serializeV1(): Buffer { - const buffer = Buffer.alloc(this.getByteSize()); - buffer.writeUInt16BE(this.id, 0); - buffer.writeUInt16BE(this.length, 2); - buffer.writeUInt16BE(this.version, 4); - buffer.writeUInt16BE(0, 6); - buffer.writeUInt32BE(this.length, 8); - - return buffer; - } - - serialize(): Buffer { - return this.version === 0 ? this.serializeV0() : this.serializeV1(); - } - - private deserializeV0(data: Buffer): void { - this.id = data.readUInt16BE(0); - this.length = data.readUInt16BE(2); - } - - private deserializeV1(data: Buffer): void { - this.id = data.readUInt16BE(0); - this.length = data.readUInt16BE(2); - // Skip version - // Skip padding - this.length = data.readUInt32BE(8); - } - - deserialize(data: Buffer): void { - if (data.length < 4) { - throw new Error( - `Data is too short. Expected at least 4 bytes, got ${data.length} bytes`, - ); - } - - if (this.version === 0) { - this.deserializeV0(data); - } else { - this.deserializeV1(data); - } - } -} - -export class SerializableData implements ISerializable { - private data: Buffer; - constructor(requestedSize: number) { - this.data = Buffer.alloc(requestedSize); - } - - serialize(): Buffer { - return this.data; - } - - deserialize(data: Buffer): void { - if (data.length > this.data.length) { - throw new Error( - `Data is too long. Expected at most ${this.data.length} bytes, got ${data.length} bytes`, - ); - } - this.data = data; - } - - getByteSize(): number { - return this.data.length; - } - - toString(): string { - return `EmptyData(length=${this.data.length}, data=${this.data.toString( - "hex", - )})`; - } -} - -export class GameMessage implements IMessage { - header: MessageHeader; - data: ISerializable; - - constructor(version: 0 | 257) { - this.header = new MessageHeader(version, 0, 0); - this.data = new SerializableData(0); - } - - getId() { - return this.header.getId(); - } - - getDataAsBuffer(): Buffer { - return this.data.serialize(); - } - - /** The message length is the length of the message data, not including the id */ - getByteSize(): number { - return this.header.getLength(); - } - getData(): ISerializable { - return this.data; - } - setData(data: ISerializable): void { - this.data = data; - this.header.setLength(data.getByteSize() + this.header.getByteSize()); - } - serialize(): Buffer { - const buffer = Buffer.alloc(this.getByteSize()); - const headerData = this.header.serialize(); - headerData.copy(buffer, 0); - const messageData = this.data.serialize(); - messageData.copy(buffer, this.header.getDataOffset()); - return buffer; - } - deserialize(data: Buffer): GameMessage { - if (data.length < this.header.getDataOffset()) { - throw new Error( - `Data is too short. Expected at least ${this.header.getDataOffset()} bytes, got ${ - data.length - } bytes`, - ); - } - this.header.deserialize(data); - const messageData = data.subarray(this.header.getDataOffset()); - - // Update the message data to the required size - this.data = new SerializableData(this.header.getLength()); - - this.data.deserialize(messageData.subarray(0, this.header.getLength())); - return this; - } - - toString(): string { - return `Id: ${this.header.getId()} - Length: ${this.header.getLength()} - Data: ${this.data.toString()}`; - } - - static identifyVersion(data: Buffer): 0 | 257 { - if (data.length < 6) { - return 0; - } - - const version = data.readUInt16BE(4); - if (version !== 257) { - return 0; - } - - return 257; - } - - static fromGameMessage(version: 0 | 257, source: GameMessage): GameMessage { - const message = new GameMessage(version); - message.deserialize(source.serialize()); - return message; - } -} diff --git a/packages/nps/messageStructs/GameProfile.ts b/packages/nps/messageStructs/GameProfile.ts deleted file mode 100644 index ff4f7c87e..000000000 --- a/packages/nps/messageStructs/GameProfile.ts +++ /dev/null @@ -1,193 +0,0 @@ -import type { ISerializable } from "rusty-motors-nps"; -import { putLenString } from "rusty-motors-nps"; -import { - getAsHex, - getLenBlob, - getLenString, - getShortBool, -} from "rusty-motors-nps"; - -export class GameProfile implements ISerializable { - customerId: number; // 4 bytes - profileName: string; // 32 bytes - max length - serverId: number; // 4 bytes - createStamp: number; // 4 bytes - lastLoginStamp: number; // 4 bytes - numberGames: number; // 4 bytes - profileId: number; // 4 bytes - isOnline: boolean; // 2 bytes - gamePurchaseStamp: number; // 4 bytes - gameSerialNumber: string; // 32 bytes - max length - timeOnline: number; // 4 bytes - timeInGame: number; // 4 bytes - gameBlob: Buffer; // 512 bytes - max length - personalBlob: Buffer; // 256 bytes - max length - pictureBlob: Buffer; // 1 byte - dnd: boolean; // 2 bytes - gameStartStamp: number; // 4 bytes - currentKey: string; // 400 bytes - max length - profileLevel: number; // 2 bytes - shardId: number; // 4 bytes - - constructor() { - this.customerId = 0; - this.profileName = ""; - this.serverId = 0; - this.createStamp = 0; - this.lastLoginStamp = 0; - this.numberGames = 0; - this.profileId = 0; - this.isOnline = false; - this.gamePurchaseStamp = 0; - this.gameSerialNumber = ""; - this.timeOnline = 0; - this.timeInGame = 0; - this.gameBlob = Buffer.alloc(0); - this.personalBlob = Buffer.alloc(0); - this.pictureBlob = Buffer.alloc(0); - this.dnd = false; - this.gameStartStamp = 0; - this.currentKey = ""; - this.profileLevel = 0; - this.shardId = 0; - } - serialize(): Buffer { - return this.toBytes(); - } - deserialize(data: Buffer): GameProfile { - return GameProfile.fromBytes(data); - } - getByteSize(): number { - throw new Error("Method not implemented."); - } - - static new(): GameProfile { - return new GameProfile(); - } - - static fromBytes(data: Buffer): GameProfile { - const message = new GameProfile(); - let offset = 0; - message.customerId = data.readUInt32BE(offset); - offset += 4; - message.profileName = getLenString(data, offset, false); - offset += message.profileName.length + 2; - message.serverId = data.readUInt32BE(offset); - offset += 4; - message.createStamp = data.readUInt32BE(offset); - offset += 4; - message.lastLoginStamp = data.readUInt32BE(offset); - offset += 4; - message.numberGames = data.readUInt32BE(offset); - offset += 4; - message.profileId = data.readUInt32BE(offset); - offset += 4; - message.isOnline = getShortBool(data, offset); - offset += 2; - message.gamePurchaseStamp = data.readUInt32BE(offset); - offset += 4; - message.gameSerialNumber = getLenString(data, offset, false); - offset += message.gameSerialNumber.length + 2; - message.timeOnline = data.readUInt32BE(offset); - offset += 4; - message.timeInGame = data.readUInt32BE(offset); - offset += 4; - message.gameBlob = getLenBlob(data, offset, false); - offset += message.gameBlob.length + 2; - message.personalBlob = getLenBlob(data, offset, false); - offset += message.personalBlob.length + 2; - message.pictureBlob = data.subarray(offset, offset + 1); - offset += message.pictureBlob.length; - message.dnd = getShortBool(data, offset); - offset += 2; - message.gameStartStamp = data.readUInt32BE(offset); - offset += 4; - message.currentKey = getLenString(data, offset, false); - offset += message.currentKey.length + 2; - message.profileLevel = data.readUInt16BE(offset); - offset += 2; - message.shardId = data.readUInt32BE(offset); - - return message; - } - - toBytes(): Buffer { - const buffer = Buffer.alloc(this.getSize()); - let offset = 0; - buffer.writeUInt32BE(this.customerId, offset); - offset += 4; // offset is now 4 - buffer.writeUInt16BE(3341, offset); - offset += 2; // offset is now 6 - buffer.writeUInt32BE(this.profileId, offset); - offset += 4; // offset is now 10 - buffer.writeUInt32BE(this.shardId, offset); - offset += 4; // offset is now 14 - offset += 2; // offset is now 16 - putLenString(buffer, offset, this.profileName, false); - - // buffer.writeUInt32BE(this.serverId, offset); - // offset += this.profileName.length + 2; - // buffer.writeUInt32BE(this.createStamp, offset); - // offset += 4; - // buffer.writeUInt32BE(this.lastLoginStamp, offset); - // offset += 4; - // buffer.writeUInt32BE(this.numberGames, offset); - // offset += 4; - // putShortBool(buffer, offset, this.isOnline); - // offset += 2; - // buffer.writeUInt32BE(this.gamePurchaseStamp, offset); - // offset += 4; - // putLenString(buffer, offset, this.gameSerialNumber, false); - // offset += this.gameSerialNumber.length + 2; - // buffer.writeUInt32BE(this.timeOnline, offset); - // offset += 4; - // buffer.writeUInt32BE(this.timeInGame, offset); - // offset += 4; - // putLenBlob(buffer, offset, this.gameBlob, false); - // offset += this.gameBlob.length + 2; - // putLenBlob(buffer, offset, this.personalBlob, false); - // offset += this.personalBlob.length + 2; - // this.pictureBlob.copy(buffer, offset, 0, 1); - // offset += 1; - // putShortBool(buffer, offset, this.dnd); - // offset += 2; - // buffer.writeUInt32BE(this.gameStartStamp, offset); - // offset += 4; - // putLenString(buffer, offset, this.currentKey, false); - // offset += this.currentKey.length + 2; - // buffer.writeUInt16BE(this.profileLevel, offset); - // offset += 2; - return buffer; - } - toString(): string { - return `GameProfile: - customerID: ${this.customerId} - profileName: ${this.profileName} - serverId: ${this.serverId} - createStamp: ${this.createStamp} - lastLoginStamp: ${this.lastLoginStamp} - numberGames: ${this.numberGames} - profileId: ${this.profileId} - isOnline: ${this.isOnline} - gamePurchaseStamp: ${this.gamePurchaseStamp} - gameSerialNumber: ${this.gameSerialNumber} - timeOnline: ${this.timeOnline} - timeInGame: ${this.timeInGame} - gameBlob: ${getAsHex(this.gameBlob)} - personalBlob: ${getAsHex(this.personalBlob)} - pictureBlob: ${getAsHex(this.pictureBlob)} - dnd: ${this.dnd} - gameStartStamp: ${this.gameStartStamp} - currentKey: ${this.currentKey} - profileLevel: ${this.profileLevel} - shardId: ${this.shardId} - `; - } - toHex(): string { - return getAsHex(this.toBytes()); - } - - getSize(): number { - return 52; - } -} diff --git a/packages/nps/messageStructs/MiniRiffList.ts b/packages/nps/messageStructs/MiniRiffList.ts deleted file mode 100644 index b3c43ab23..000000000 --- a/packages/nps/messageStructs/MiniRiffList.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { putLenString } from "rusty-motors-nps"; - -import { BaseSerializable } from "./BaseSerializable.js"; -import { getServerLogger } from "rusty-motors-shared"; - -const defaultLogger = getServerLogger("nps.MiniRiffList"); - - -// const channelRecordSize = 40; - -export class MiniRiffInfo extends BaseSerializable { - riffName: string; // 32 bytes - max length - riffId: number; // 4 bytes - population: number; // 2 bytes - - constructor(riffName: string, riffId: number, population: number) { - super(); - if (riffName.length > 32) { - throw new Error(`Riff name too long: ${riffName}`); - } - - this.riffName = riffName; - this.riffId = riffId; - this.population = population; - } - - override serialize(): Buffer { - const buffer = Buffer.alloc(this.getByteSize()); - let offset = 0; - putLenString(buffer, offset, this.riffName, false); - offset += 2 + this.riffName.length + 1; - buffer.writeUInt32BE(this.riffId, offset); - offset += 4; - buffer.writeUInt16BE(this.population, offset); - defaultLogger.debug(`MiniRiffInfo: ${this.toString()} - ${buffer.toString("hex")}`); - return buffer; - } - override getByteSize(): number { - return 4 + this.riffName.length + 1 + 4 + 2; - } - override toString(): string { - return `MiniRiffInfo(riffName=${this.riffName}, riffId=${this.riffId}, population=${this.population})`; - } -} - -export class MiniRiffList extends BaseSerializable { - private riffs: MiniRiffInfo[] = []; - - override serialize(): Buffer { - return this.toBytes(); - } - override getByteSize(): number { - return this.getSize(); - } - - getMaxRiffs(): number { - return this.riffs.length; - } - - addRiff(riff: MiniRiffInfo): void { - this.riffs.push(riff); - } - - toBytes(): Buffer { - const buffer = Buffer.alloc(this.getSize()); - let offset = 0; - buffer.writeUInt32BE(this.riffs.length, offset); - offset += 4; - for (const riff of this.riffs) { - const riffBuffer = riff.serialize(); - riffBuffer.copy(buffer, offset); - offset += riff.getByteSize(); - } - - defaultLogger.debug(`MiniRiffList: ${this.toString()} - ${buffer.toString("hex")}`); - return buffer; - } - override toString(): string { - return `MiniRiffList(riffs=${this.riffs})`; - } - toHex(): string { - return this.toBytes().toString("hex"); - } - - getSize(): number { - let size = 4; - for (const riff of this.riffs) { - size += riff.getByteSize(); - } - return size; - } -} diff --git a/packages/nps/messageStructs/MiniUserList.ts b/packages/nps/messageStructs/MiniUserList.ts deleted file mode 100644 index 3de0001f1..000000000 --- a/packages/nps/messageStructs/MiniUserList.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { putLenString } from "rusty-motors-nps"; -import { BaseSerializable } from "./BaseSerializable.js"; - -export class MiniUserInfo extends BaseSerializable { - userId: number; // 4 bytes - userName: string; // 32 bytes - max length - - constructor(userId: number, userName: string) { - super(); - if (userName.length > 32) { - throw new Error(`User name too long: ${userName}`); - } - - this.userId = userId; - this.userName = userName; - } - - override serialize(): Buffer { - const buffer = Buffer.alloc(this.getByteSize()); - let offset = 0; - buffer.writeUInt32BE(this.userId, offset); - offset += 4; - putLenString(buffer, offset, this.userName, false); - return buffer; - } - override getByteSize(): number { - return 4 + 4 + this.userName.length + 1; - } - override toString(): string { - return `MiniUserInfo(userId=${this.userId}, userName=${this.userName})`; - } -} - -export class MiniUserList extends BaseSerializable { - private channelId: number; // 4 bytes - private channelUsers: MiniUserInfo[] = []; - - constructor(channelId: number) { - super(); - this.channelId = channelId; - } - - addChannelUser(user: MiniUserInfo): void { - this.channelUsers.push(user); - } - - override serialize(): Buffer { - const buffer = Buffer.alloc(this.getByteSize()); - let offset = 0; - buffer.writeUInt32BE(this.channelId, offset); - offset += 4; - buffer.writeUInt32BE(this.channelUsers.length, offset); - offset += 4; - this.channelUsers.forEach((user) => { - const userBuffer = user.serialize(); - userBuffer.copy(buffer, offset); - offset += userBuffer.length; - }); - return buffer; - } - override getByteSize(): number { - return ( - 16 + this.channelUsers.reduce((acc, user) => acc + user.getByteSize(), 0) - ); - } - override toString(): string { - return `MiniUserList(channelId=${this.channelId}, channelUsers=${this.channelUsers})`; - } -} diff --git a/packages/nps/messageStructs/NPSList.ts b/packages/nps/messageStructs/NPSList.ts deleted file mode 100644 index b60418f76..000000000 --- a/packages/nps/messageStructs/NPSList.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { ISerializable } from "../types.ts"; - -export class NPSList implements ISerializable { - serialize(): Buffer { - throw new Error("Method not implemented."); - } - deserialize(_data: Buffer): void { - throw new Error("Method not implemented."); - } - getByteSize(): number { - throw new Error("Method not implemented."); - } - - toBytes(): Buffer { - throw new Error("Method not implemented."); - } - toString(): string { - throw new Error("Method not implemented."); - } - toHex(): string { - throw new Error("Method not implemented."); - } - setData(_data: Buffer): void { - throw new Error("Method not implemented."); - } - getData(): Buffer { - throw new Error("Method not implemented."); - } - - getSize(): number { - return 0; - } -} diff --git a/packages/nps/messageStructs/ProfileList.ts b/packages/nps/messageStructs/ProfileList.ts deleted file mode 100644 index a1d84f0a7..000000000 --- a/packages/nps/messageStructs/ProfileList.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { ISerializable } from "rusty-motors-nps"; -import { GameProfile } from "./GameProfile.js"; -import { NPSList } from "./NPSList.js"; - -export class ProfileList extends NPSList implements ISerializable { - override serialize(): Buffer { - return this.toBytes(); - } - override getByteSize(): number { - return this.getSize(); - } - maxProfiles = 0; // 1 byte - private profiles: GameProfile[] = []; - - getMaxProfiles(): number { - return this.maxProfiles; - } - - addProfile(profile: GameProfile): void { - this.profiles.push(profile); - this.maxProfiles = this.profiles.length; - } - - override toBytes(): Buffer { - const buffer = Buffer.alloc(this.getSize()); - let offset = 0; - buffer.writeUInt16BE(this.maxProfiles, offset); - offset += 2; - for (const profile of this.profiles) { - const profileBuffer = profile.toBytes(); - profileBuffer.copy(buffer, offset); - offset += profile.getSize(); - } - return buffer; - } - override toString(): string { - return `ProfileList(maxProfiles=${this.maxProfiles}, profiles=${this.profiles})`; - } - - override getSize(): number { - let size = 4; - for (const profile of this.profiles) { - size += profile.getSize(); - } - return size; - } -} diff --git a/packages/nps/messageStructs/SessionKey.ts b/packages/nps/messageStructs/SessionKey.ts deleted file mode 100644 index 807ec76e6..000000000 --- a/packages/nps/messageStructs/SessionKey.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { getAsHex, isOnlyOneSet } from "rusty-motors-nps"; -import { BaseSerializable } from "./BaseSerializable.js"; -import { getServerLogger } from "rusty-motors-shared"; - -const defaultLogger = getServerLogger("nps.SessionKey"); - -export class SessionKey extends BaseSerializable { - private key: Buffer = Buffer.alloc(0); - private timestamp: number = 0; - private _isSet: boolean = false; - - constructor({ key, timestamp }: { key?: Buffer; timestamp?: number }) { - super(); - if (isOnlyOneSet(key, timestamp)) { - throw new Error("Both key and timestamp must be set if one is set"); - } - - if (typeof key !== "undefined" && typeof timestamp !== "undefined") { - defaultLogger.debug(`SessionKey: key=${getAsHex(key)}, timestamp=${timestamp}`); - this.key = key; - this.timestamp = timestamp; - this._isSet = true; - } - } - override serialize(): Buffer { - return this.toBytes(); - } - override deserialize(data: Buffer): void { - SessionKey.fromBytes(data); - } - override getByteSize(): number { - throw new Error("Method not implemented."); - } - - static fromBytes(bytes: Buffer): SessionKey { - defaultLogger.debug("SessionKey.fromBytes"); - const keyLength = bytes.readUInt16BE(0); - - // Set the data offset - const dataOffset = 2 + keyLength; - - const key = bytes.subarray(2, dataOffset); - - defaultLogger.debug(`SessionKey.fromBytes: key=${getAsHex(key)}`); - - // Get the timestamp - const timestamp = bytes.readUInt32BE(dataOffset); - - return new SessionKey({ - key, - timestamp, - }); - } - - static fromKeyString(key: string): SessionKey { - const keyBuffer = Buffer.from(key, "hex"); - - return new SessionKey({ - key: keyBuffer, - timestamp: 0, - }); - } - - getKey(): string { - return this.key.toString("hex"); - } - - override toString(): string { - return `SessionKey(key=${this.getKey()}, timestamp=${this.timestamp})`; - } - - toHex(): string { - return getAsHex(this.toBytes()); - } - - toBytes(): Buffer { - if (!this.isSet()) { - throw new Error("Session key is not set"); - } - - const keyLength = this.key.length; - const timestamp = this.timestamp; - - const buffer = Buffer.alloc(2 + keyLength + 4); - - buffer.writeUInt16BE(keyLength, 0); - this.key.copy(buffer, 2); - buffer.writeUInt32BE(timestamp, 2 + keyLength); - - return buffer; - } - - getSize(): number { - return this.key.length + 6; - } - - getData(): Buffer { - throw new Error("Method not implemented."); - } - - setData(): void { - throw new Error("Method not implemented."); - } - - isSet(): boolean { - return this._isSet; - } -} diff --git a/packages/nps/messageStructs/UserAction.ts b/packages/nps/messageStructs/UserAction.ts deleted file mode 100644 index dbf60e3d5..000000000 --- a/packages/nps/messageStructs/UserAction.ts +++ /dev/null @@ -1,139 +0,0 @@ -import type { ISerializable } from "rusty-motors-nps"; -import { getAsHex } from "rusty-motors-nps"; -import { getServerLogger } from "rusty-motors-shared"; - -const defaultLogger = getServerLogger("nps.UserAction"); - - -export class UserAction implements ISerializable { - private name: string; - private _endTimeMaybe = 0; // 4 bytes - private _b1 = 0; // 1 byte - private _b2 = 0; // 1 byte - private _initiator = ""; // 64 bytes - private _startComment = ""; // 256 bytes - private _endComment = ""; // 256 bytes - - constructor(name: string) { - this.name = name; - } - - serialize(): Buffer { - try { - const buffer = Buffer.alloc(this.getSize()); - let offset = 0; - buffer.writeUInt32BE(this._endTimeMaybe, offset); - offset += 4; - buffer.writeUInt8(this._b1, offset); - offset += 1; - buffer.writeUInt8(this._b2, offset); - offset += 1; - buffer.write(this._initiator, offset, 0x40, "utf8"); - offset += 0x40; - buffer.write(this._startComment, offset, 0x100, "utf8"); - offset += 0x100; - buffer.write(this._endComment, offset, 0x100, "utf8"); - - return buffer; - } catch (error: unknown) { - defaultLogger.error(`Error serializing UserAction: ${error as string}`); - throw error; - } - } - deserialize(data: Buffer): void { - try { - this._endTimeMaybe = data.readUInt32BE(0); - this._b1 = data.readUInt8(4); - this._b2 = data.readUInt8(5); - this._initiator = data.toString("utf8", 6, 0x46); - this._startComment = data.toString("utf8", 0x46, 0x146); - this._endComment = data.toString("utf8", 0x146, 0x246); - } catch (error) { - defaultLogger.error(`Error deserializing UserAction: ${error as string}`); - throw error; - } - } - getByteSize(): number { - throw new Error("Method not implemented."); - } - setData(): void { - throw new Error("Method not implemented."); - } - getData(): Buffer { - throw new Error("Method not implemented."); - } - - static fromBytes(name: string, bytes: Buffer): UserAction { - const userAction = new UserAction(name); - userAction.deserialize(bytes); - - return userAction; - } - - toBytes(): Buffer { - return this.serialize(); - } - toString(): string { - return `UserAction: ${this.name} - ${this._initiator} - ${this._startComment} - ${this._endComment}`; - } - toHex(): string { - return getAsHex(this.serialize()); - } - - getSize(): number { - return 4 + 1 + 1 + 0x40 + 0x100 + 0x100; - } - - public set({ - endTimeMaybe, - b1, - b2, - initiator, - startComment, - endComment, - }: { - endTimeMaybe?: number; - b1?: number; - b2?: number; - initiator: string; - startComment: string; - endComment?: string; - }): void { - this._endTimeMaybe = endTimeMaybe || 0; - this._b1 = b1 || 0; - this._b2 = b2 || 0; - this._initiator = initiator; - this._startComment = startComment; - this._endComment = endComment || ""; - } - - public clear(): void { - this._endTimeMaybe = 0; - this._b1 = 0; - this._b2 = 0; - this._initiator = ""; - this._startComment = ""; - this._endComment = ""; - } - - updateEmptyValuesFrom(other: UserAction) { - if (this._endTimeMaybe === 0) { - this._endTimeMaybe = other._endTimeMaybe; - } - if (this._b1 === 0) { - this._b1 = other._b1; - } - if (this._b2 === 0) { - this._b2 = other._b2; - } - if (this._initiator === "") { - this._initiator = other._initiator; - } - if (this._startComment === "") { - this._startComment = other._startComment; - } - if (this._endComment === "") { - this._endComment = other._endComment; - } - } -} diff --git a/packages/nps/messageStructs/UserInfo.ts b/packages/nps/messageStructs/UserInfo.ts deleted file mode 100644 index 8e8dd1865..000000000 --- a/packages/nps/messageStructs/UserInfo.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { BaseSerializable } from "./BaseSerializable.js"; - -export class UserInfo extends BaseSerializable { - private profileId: number; // 4 bytes - private profileName: string; // 32 bytes - max length - private userData; // 64 bytes - - constructor(id: number, name: string) { - super(); - if (name.length > 31) { - throw new Error( - `Profile name too long: ${name}, max length is 31, got ${name.length}`, - ); - } - this.profileId = id; - this.profileName = name; - this.userData = Buffer.alloc(64); - } - - override serialize(): Buffer { - const buffer = Buffer.alloc(this.getByteSize()); - let offset = 0; - buffer.writeInt32BE(this.profileId, offset); - offset += 4; - buffer.writeUInt16BE(this.profileName.length, offset); - offset += 2; - buffer.write( - `${this.profileName}\0`, - offset, - this.profileName.length + 1, - "utf8", - ); - offset += this.profileName.length + 1; - this.userData.copy(buffer, offset); - return buffer; - } - override getByteSize(): number { - return 4 + 2 + this.profileName.length + 1 + 64; - } - override toString(): string { - return `Profile ID: ${this.profileId}, - Profile Name: ${this.profileName}`; - } -} diff --git a/packages/nps/messageStructs/UserStatus.ts b/packages/nps/messageStructs/UserStatus.ts deleted file mode 100644 index 8938d9f80..000000000 --- a/packages/nps/messageStructs/UserStatus.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { randomUUID } from "node:crypto"; -import { SessionKey } from "./SessionKey.js"; -import { UserAction } from "./UserAction.js"; -import { BaseSerializable } from "./BaseSerializable.js"; - -export class UserStatus extends BaseSerializable { - private _sessionId: string = ""; - private _remoteIp: string = ""; - private _machineId: string = ""; - private customerId: number = 0; - private personaId: number = 0; - private isCacheHit: boolean = false; - readonly ban: UserAction; - readonly gag: UserAction; - private sessionKey: SessionKey = new SessionKey({}); - - constructor({ - customerId, - personaId, - sessionKey, - }: { - customerId: number; - personaId?: number; - sessionKey?: SessionKey; - }) { - super(); - this._sessionId = randomUUID(); - this.customerId = customerId; - this.personaId = personaId || 0; - this.isCacheHit = false; - this.ban = new UserAction("ban"); - this.gag = new UserAction("gag"); - this.sessionKey = sessionKey || new SessionKey({}); - } - override serialize(): Buffer { - return this.toBytes(); - } - override getByteSize(): number { - return this.getSize(); - } - - getSessionId(): string { - return this._sessionId; - } - - getRemoteIp(): string { - return this._remoteIp; - } - - setRemoteIp(value: string) { - if (this._remoteIp !== "" && this._remoteIp !== value) { - throw new Error("Remote IP is already set and cannot be changed"); - } - this._remoteIp = value; - } - - getMachineId(): string { - return this._machineId; - } - - setMachineId(value: string) { - if (this._machineId !== "" && this._machineId !== value) { - throw new Error("Machine ID is already set and cannot be changed"); - } - this._machineId = value; - } - - static new(): UserStatus { - return new UserStatus({ - customerId: 0, - }); - } - - static fromBytes(bytes: Buffer): UserStatus { - let offset = 0; - const customerId = bytes.readUInt32BE(offset); - offset += 4; - const personaId = bytes.readUInt32BE(offset); - offset += 4; - // Skip isCacheHit - offset += 1; - const ban = UserAction.fromBytes("ban", bytes.subarray(offset)); - offset += ban.getSize(); - const gag = UserAction.fromBytes("gag", bytes.subarray(offset)); - offset += gag.getSize(); - const sessionKey = SessionKey.fromBytes(bytes.subarray(offset)); - - return new UserStatus({ - customerId, - personaId, - sessionKey, - }); - } - - toBytes(): Buffer { - const buffer = Buffer.alloc(this.getSize()); - - if (this.sessionKey === null) { - throw new Error("Session key is required"); - } - - let offset = 0; - buffer.writeUInt32BE(this.customerId, offset); - offset += 4; - buffer.writeUInt32BE(this.personaId, offset); - offset += 4; - buffer.writeUInt8(this.isCacheHit ? 1 : 0, offset); - offset += 1; - this.ban.toBytes().copy(buffer, offset); - offset += this.ban.getSize(); - this.gag.toBytes().copy(buffer, offset); - offset += this.gag.getSize(); - this.sessionKey.toBytes().copy(buffer, offset); - offset += this.sessionKey.getSize(); - - return buffer; - } - - getSize(): number { - return ( - 4 + - 4 + - 1 + - this.ban.getSize() + - this.gag.getSize() + - this.sessionKey.getSize() + - 4 + - 64 - ); - } - - getCustomerId(): number { - return this.customerId; - } - - setCustomerId(customerId: number) { - this.customerId = customerId; - } - - getPersonaId(): number { - return this.personaId; - } - - setPersonaId(personaId: number) { - this.personaId = personaId; - } - - getSessionKey(): SessionKey { - return this.sessionKey; - } - - setSessionKey(sessionKey: SessionKey) { - this.sessionKey = sessionKey; - } - - override toString(): string { - return `UserStatus: - Customer ID: ${this.customerId} - Persona ID: ${this.personaId} - Is Cache Hit: ${this.isCacheHit} - Ban: ${this.ban.toString()} - Gag: ${this.gag.toString()} - Session Key: ${this.sessionKey.toString()}`; - } - - toHex(): string { - return this.toBytes().toString("hex"); - } -} diff --git a/packages/nps/package.json b/packages/nps/package.json deleted file mode 100644 index be6d43dd7..000000000 --- a/packages/nps/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "rusty-motors-nps", - "version": "1.0.0-next.0", - "exports": { - ".": { - "import": "./index.js", - "require": "./index.js" - } - }, - "type": "module", - "scripts": { - "check": "tsc", - "lint": "npx @biomejs/biome lint --write .", - "format": "npx @biomejs/biome format --write .", - "test": "vitest run --coverage" - }, - "keywords": [], - "author": "", - "license": "AGPL-3.0", - "dependencies": { - "@sentry/profiling-node": "9.12.0", - "short-unique-id": "^5.2.2" - }, - "description": "", - "devDependencies": { - "@vitest/coverage-v8": "3.1.1", - "vitest": "^3.1.1" - } -} \ No newline at end of file diff --git a/packages/nps/services/profile.ts b/packages/nps/services/profile.ts deleted file mode 100644 index c5372d02c..000000000 --- a/packages/nps/services/profile.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { GameProfile } from "../messageStructs/GameProfile.js"; - -export const gameProfiles: GameProfile[] = []; - -export function getGameProfilesForCustomerId( - customerId: number, -): GameProfile[] { - const profiles: GameProfile[] = []; - for (const profile of gameProfiles.values()) { - if (profile.customerId === customerId) { - profiles.push(profile); - } - } - return profiles; -} - -export function getCustomerId(profileId: number): number { - for (const profile of gameProfiles.values()) { - if (profile.profileId === profileId) { - return profile.customerId; - } - } - return -1; -} - -export function gameProfileExists(profileName: string): boolean { - for (const profile of gameProfiles.values()) { - if (profile.profileName === profileName) { - return true; - } - } - return false; -} - - - -export function deleteGameProfile(profileId: number): void { - for (const [index, profile] of gameProfiles.entries()) { - if (profile.profileId === profileId) { - gameProfiles.splice(index, 1); - return; - } - } -} - -export function createGameProfile(): GameProfile { - const profile = GameProfile.new(); - - return profile; -} diff --git a/packages/nps/services/token.ts b/packages/nps/services/token.ts deleted file mode 100644 index 05e1ce7be..000000000 --- a/packages/nps/services/token.ts +++ /dev/null @@ -1,68 +0,0 @@ -import ShortUniqueId from "short-unique-id"; - -const uid = new ShortUniqueId.default(); - -export type TokenRecord = { - customerId: number; - token: string; -}; - -export const activeTokens = new Map([]); - -export function generateTokenRecord(customerId: number): TokenRecord { - const token = uid.stamp(34); - - return { - customerId, - token, - }; -} - -export function generateToken(customerId: number): string { - if (typeof customerId !== "number") { - throw new Error("Invalid customer ID"); - } - const tokenRecord = generateTokenRecord(customerId); - activeTokens.set(tokenRecord.token, tokenRecord); - return tokenRecord.token; -} - -export function isTokenExpired(token: string): boolean { - const issuedAt = uid.parseStamp(token).getTime(); - - // 30 minutes - const expirationTime = Date.now() - 1800000; - - if (issuedAt < expirationTime) { - return true; - } - - return false; -} - -export function getToken(token: string): TokenRecord | undefined { - if (activeTokens.has(token)) { - return activeTokens.get(token); - } - return undefined; -} - -export function deleteToken(token: string): void { - activeTokens.delete(token); -} - -export function deleteExpiredTokens(): void { - for (const token of activeTokens.keys()) { - if (isTokenExpired(token)) { - deleteToken(token); - } - } -} - -export function getCustomerId(token: string): number | undefined { - const tokenRecord = getToken(token); - if (typeof tokenRecord !== "undefined" && !isTokenExpired(token)) { - return tokenRecord.customerId; - } - return undefined; -} diff --git a/packages/nps/services/types.ts b/packages/nps/services/types.ts deleted file mode 100644 index 3cdab9c5c..000000000 --- a/packages/nps/services/types.ts +++ /dev/null @@ -1,18 +0,0 @@ -export type Part = { - PartID: number; - ParentPartID: number; - BrandedPartID: number; - RepairPrice: number; - JunkPrice: number; - Wear: number; - AttachmentPoint: number; - Damage: number; -}; - -export type Vehicle = { - VehicleID: number; - SkinID: number; - Flags: number; - Delta: number; - Damage: number; -}; diff --git a/packages/nps/src/EncryptionSession.ts b/packages/nps/src/EncryptionSession.ts deleted file mode 100644 index a190e1360..000000000 --- a/packages/nps/src/EncryptionSession.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { - Cipher, - Decipher, - createCipheriv, - createDecipheriv, -} from "node:crypto"; - -export type EncryptionSession = { - connectionId: string; - customerId: number; - sessionKey: string; - gameCipher: Cipher; - gameDecipher: Decipher; -}; - -export const encryptionSessions = new Map([]); - -export function setEncryptionSession( - encryptionSession: EncryptionSession, -): void { - encryptionSessions.set(encryptionSession.connectionId, encryptionSession); -} - -export function getEncryptionSession( - connectionId: string, -): EncryptionSession | undefined { - if (encryptionSessions.has(connectionId)) { - return encryptionSessions.get(connectionId); - } - return undefined; -} - -export function deleteEncryptionSession(connectionId: string): void { - encryptionSessions.delete(connectionId); -} - -export function newEncryptionSession({ - connectionId, - customerId, - sessionKey, -}: { - connectionId: string; - customerId: number; - sessionKey: string; -}): EncryptionSession { - /** - * Create a new encryption session - * - * While insecure, the use of DES is required for compatibility with the game - */ - const gameCipher = createCipheriv( - "des-cbc", - Buffer.from(sessionKey, "hex"), - Buffer.alloc(8), - ); - gameCipher.setAutoPadding(false); - /** - * Create a new decryption session - * - * While insecure, the use of DES is required for compatibility with the game - */ - const gameDecipher = createDecipheriv( - "des-cbc", - Buffer.from(sessionKey, "hex"), - Buffer.alloc(8), - ); - gameDecipher.setAutoPadding(false); - const encryptionSession = { - connectionId, - customerId, - sessionKey, - gameCipher, - gameDecipher, - }; - setEncryptionSession(encryptionSession); - return encryptionSession; -} diff --git a/packages/nps/src/UserStatusManager.ts b/packages/nps/src/UserStatusManager.ts deleted file mode 100644 index 0cb9b2d14..000000000 --- a/packages/nps/src/UserStatusManager.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { UserStatus } from "../messageStructs/UserStatus.js"; - -export class UserStatusManager { - static newUserStatus() { - return new UserStatus({ customerId: 0 }); - } - static _instance: UserStatusManager; - - static getInstance() { - if (!UserStatusManager._instance) { - UserStatusManager._instance = new UserStatusManager(); - } - return UserStatusManager._instance; - } - private _userStatuses: Map = new Map(); - - public addUserStatus(userStatus: UserStatus) { - this._userStatuses.set(userStatus.getCustomerId(), userStatus); - } - - public removeUserStatus(customerId: number) { - this._userStatuses.delete(customerId); - } - - public getUserStatus(customerId: number): UserStatus | undefined { - return this._userStatuses.get(customerId); - } - - public getUserStatuses(): UserStatus[] { - return Array.from(this._userStatuses.values()); - } - - public clearUserStatuses() { - this._userStatuses.clear(); - } - - static addUserStatus(userStatus: UserStatus) { - UserStatusManager.getInstance().addUserStatus(userStatus); - } - - static removeUserStatus(customerId: number) { - UserStatusManager.getInstance().removeUserStatus(customerId); - } - - static getUserStatus(customerId: number): UserStatus | undefined { - const userStatus = - UserStatusManager.getInstance().getUserStatus(customerId); - - if (userStatus) { - return userStatus; - } - - const newUserStatus = new UserStatus({ customerId }); - - UserStatusManager.getInstance().addUserStatus(newUserStatus); - - return newUserStatus; - } - - static getUserStatusBySessionId(sessionId: string): UserStatus | undefined { - const userStatus = UserStatusManager.getInstance() - .getUserStatuses() - .find((userStatus) => { - return userStatus.getSessionId() === sessionId; - }); - - if (userStatus) { - return userStatus; - } - - return undefined; - } - - getUserStatuseByMachineIdAndIp( - machineId: string, - remoteIp: string, - ): UserStatus | undefined { - return Array.from(this._userStatuses.values()).find((userStatus) => { - return ( - userStatus.getMachineId() === machineId && - userStatus.getRemoteIp() === remoteIp - ); - }); - } - - static getUserStatuses(): UserStatus[] { - return UserStatusManager.getInstance().getUserStatuses(); - } -} diff --git a/packages/nps/src/utils/pureCompare.ts b/packages/nps/src/utils/pureCompare.ts deleted file mode 100644 index a67969281..000000000 --- a/packages/nps/src/utils/pureCompare.ts +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Checks if a given number is zero. - * - * @param n - The number to check. - * @returns `true` if the number is zero, `false` otherwise. - */ -export function isZero(n: number): boolean { - // Return true if n is zero - return n === 0; -} - -/** - * Checks if a value is undefined. - * - * @param n - The value to check. - * @returns `true` if the value is undefined, `false` otherwise. - */ -export function isUndefined(n: unknown): boolean { - // Return true if n is undefined - return typeof n === "undefined"; -} - -/** - * Compares two numbers and returns true if the first number is less than the second number. - * - * @param a - The first number to compare. - * @param b - The second number to compare. - * @returns True if `a` is less than `b`, otherwise false. - */ -export function lessThan(a: number, b: number): boolean { - // Return true if a < b - return a < b; -} -/** - * Checks if the first number is less than or equal to the second number. - * - * @param a - The first number. - * @param b - The second number. - * @returns `true` if `a` is less than or equal to `b`, `false` otherwise. - */ -export function lessThanOrEqual(a: number, b: number): boolean { - // Return true if a <= b - return a <= b; -} -/** - * Checks if the first number is greater than the second number. - * - * @param a - The first number. - * @param b - The second number. - * @returns `true` if `a` is greater than `b`, `false` otherwise. - */ -export function greaterThan(a: number, b: number): boolean { - // Return true if a > b - return a > b; -} - -/** - * Checks if the first number is greater than or equal to the second number. - * - * @param a - The first number to compare. - * @param b - The second number to compare. - * @returns `true` if `a` is greater than or equal to `b`, `false` otherwise. - */ -export function greaterThanOrEqual(a: number, b: number): boolean { - // Return true if a >= b - return a >= b; -} - -/** - * Checks if both numbers are zero. - * - * @param a - The first number. - * @param b - The second number. - * @returns True if both numbers are zero, false otherwise. - */ -export function areBothZero(a: number, b: number): boolean { - // Return true if both a and b are zero - return isZero(a) && isZero(b); -} - -/** - * Checks if both values are undefined. - * - * @param a - The first value to compare. - * @param b - The second value to compare. - * @returns True if both values are undefined, false otherwise. - */ -export function areBothUndefined(a: unknown, b: unknown): boolean { - // Return true if both a and b are undefined - return typeof a === "undefined" && typeof b === "undefined"; -} - -/** - * Checks if both values are set. - * - * @param a - The first value to check. - * @param b - The second value to check. - * @returns True if both values are set, false otherwise. - */ -export function areBothSet(a: unknown, b: unknown): boolean { - // Return true if both a and b are set - return !isUndefined(a) && !isUndefined(b); -} - -/** - * Checks if only one of the two values is set. - * - * @param a - The first value to compare. - * @param b - The second value to compare. - * @returns True if only one of the values is set, false otherwise. - */ -export function isOnlyOneSet(a: unknown, b: unknown): boolean { - // Return true if only one of a and b is set - return !areBothSet(a, b) && (!isUndefined(a) || !isUndefined(b)); -} diff --git a/packages/nps/src/utils/pureGet.ts b/packages/nps/src/utils/pureGet.ts deleted file mode 100644 index 642881b63..000000000 --- a/packages/nps/src/utils/pureGet.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { greaterThanOrEqual } from "./pureCompare.js"; - -/** - * Retrieves a word from a buffer at the specified offset. - * - * @param bytes - The buffer containing the word. - * @param offset - The offset at which to retrieve the word. - * @param isLE - Indicates whether the word is in little-endian format. - * @returns The word retrieved from the buffer. - */ -export function getWord(bytes: Buffer, offset: number, isLE: boolean): number { - // Get the word at the offset - return isLE ? bytes.readUInt16LE(offset) : bytes.readUInt16BE(offset); -} - -/** - * Retrieves a dword from the given buffer at the specified offset. - * - * @param bytes - The buffer containing the dword. - * @param offset - The offset at which the dword is located. - * @param isLE - Indicates whether the buffer is in little-endian format. - * @returns The retrieved dword. - */ -export function getDWord(bytes: Buffer, offset: number, isLE: boolean): number { - // Get the dword at the offset - return isLE ? bytes.readUInt32LE(offset) : bytes.readUInt32BE(offset); -} - -/** - * Get the first n bytes of a buffer. - * If the buffer is shorter than n bytes, return the whole buffer - */ -export function getNBytes(bytes: Buffer, n: number): Buffer { - const bufferLength = bytes.length; - - const cutLength = greaterThanOrEqual(bufferLength, n) ? n : bufferLength; - - // Get the first n bytes - return bytes.subarray(0, cutLength); -} - -/** - * Converts a buffer of bytes to a hexadecimal string representation. - * If the number of hex characters is odd, a leading zero is added. - * - * @param bytes - The buffer of bytes to convert. - * @returns The hexadecimal string representation of the buffer. - */ -export function getAsHex(bytes: Buffer): string { - // Convert the buffer to a hexadecimal string - const hex = bytes.toString("hex"); - return hex.length % 2 === 0 ? hex : `0${hex}`; -} - -/** - * Retrieves a string from a buffer based on the given offset and length. - * - * @param bytes - The buffer containing the string. - * @param offset - The starting position of the string in the buffer. - * @param isLE - A boolean indicating whether the buffer is in little-endian format. - * @returns The extracted string. - */ -export function getLenString( - bytes: Buffer, - offset: number, - isLE: boolean, -): string { - // Get the length of the string - const strLen = getWord(bytes, offset, isLE); - - // Get the string - return bytes.subarray(offset + 2, offset + 2 + strLen).toString("utf8"); -} - -/** - * Retrieves a blob from a buffer based on the given offset and length. - * - * @param bytes - The buffer containing the blob. - * @param offset - The starting offset of the blob in the buffer. - * @param isLE - A boolean indicating whether the buffer is in little-endian format. - * @returns The extracted blob as a new buffer. - */ -export function getLenBlob( - bytes: Buffer, - offset: number, - isLE: boolean, -): Buffer { - // Get the length of the blob - const blobLen = getDWord(bytes, offset, isLE); - - // Get the blob - return bytes.subarray(offset + 2, offset + 2 + blobLen); -} - -/** - * Retrieves a 2-byte boolean value from the specified buffer at the given offset. - * - * @param bytes - The buffer containing the boolean value. - * @param offset - The offset at which the boolean value is located in the buffer. - * @returns The boolean value retrieved from the buffer. - */ -export function getShortBool(bytes: Buffer, offset: number): boolean { - // Get a 2 byte boolean - return bytes.readUInt16LE(offset) === 1; -} diff --git a/packages/nps/src/utils/purePut.ts b/packages/nps/src/utils/purePut.ts deleted file mode 100644 index 09180bba5..000000000 --- a/packages/nps/src/utils/purePut.ts +++ /dev/null @@ -1,105 +0,0 @@ -export function put16( - bytes: Buffer, - offset: number, - word: number, - isLE: boolean, -): Buffer { - // Put the word at the offset - if (isLE) { - bytes.writeUInt16LE(word, offset); - } else { - bytes.writeUInt16BE(word, offset); - } - return bytes; -} - -export function put8(bytes: Buffer, offset: number, byte: number): Buffer { - // Put the byte at the offset - bytes.writeUInt8(byte, offset); - return bytes; -} - -export function put16BE(bytes: Buffer, offset: number, word: number): Buffer { - return put16(bytes, offset, word, false); -} - -export function put16LE(bytes: Buffer, offset: number, word: number): Buffer { - return put16(bytes, offset, word, true); -} - -export function put32( - bytes: Buffer, - offset: number, - word: number, - isLE: boolean, -): Buffer { - // Put the word at the offset - if (isLE) { - bytes.writeUInt32LE(word, offset); - } else { - bytes.writeUInt32BE(word, offset); - } - return bytes; -} - -export function put32BE(bytes: Buffer, offset: number, word: number): Buffer { - return put32(bytes, offset, word, false); -} - -export function put32LE(bytes: Buffer, offset: number, word: number): Buffer { - return put32(bytes, offset, word, true); -} - -export function putLenString( - bytes: Buffer, - offset: number, - str: string, - isLE: boolean, -): Buffer { - // Get the length of the string - const strLen = str.length + 1; - - // Put the length of the string - if (isLE) { - bytes.writeUInt32LE(strLen, offset); - } else { - bytes.writeUInt32BE(strLen, offset); - } - - // Put the string - bytes.write(str.concat("\0"), offset + 4, strLen, "utf8"); - - return bytes; -} - -export function putLenBlob( - bytes: Buffer, - offset: number, - blob: Buffer, - isLE: boolean, -): Buffer { - // Get the length of the blob - const blobLen = blob.length; - - // Put the length of the blob - if (isLE) { - bytes.writeUInt32LE(blobLen, offset); - } else { - bytes.writeUInt32BE(blobLen, offset); - } - - // Put the blob - blob.copy(bytes, offset + 4); - - return bytes; -} - -export function putShortBool( - bytes: Buffer, - offset: number, - bool: boolean, -): Buffer { - // Put a 2 byte boolean - bytes.writeUInt16LE(bool ? 1 : 0, offset); - return bytes; -} diff --git a/packages/nps/src/utils/sendNPSAck.ts b/packages/nps/src/utils/sendNPSAck.ts deleted file mode 100644 index 177f6138d..000000000 --- a/packages/nps/src/utils/sendNPSAck.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { GameSocketCallback } from "../../gameMessageProcessors/index.js"; -import { GameMessage } from "../../messageStructs/GameMessage.js"; - -export function sendNPSAck(socketCallback: GameSocketCallback) { - const response = new GameMessage(0); - response.header.setId(519); - - const responseBytes = response.serialize(); - - socketCallback([responseBytes]); -} diff --git a/packages/nps/test/BaseSerializable.test.ts b/packages/nps/test/BaseSerializable.test.ts deleted file mode 100644 index a822ca8e9..000000000 --- a/packages/nps/test/BaseSerializable.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { beforeEach, describe, expect, it } from "vitest"; -import { BaseSerializable } from "../messageStructs/BaseSerializable.js"; - -describe("BaseSerializable", () => { - let baseSerializable: BaseSerializable; - - beforeEach(() => { - baseSerializable = new BaseSerializable(); - }); - - it("should throw an error when calling serialize", () => { - expect(() => { - baseSerializable.serialize(); - }).toThrow("Method not implemented."); - }); - - it("should throw an error when calling deserialize", () => { - expect(() => { - baseSerializable.deserialize(Buffer.from([])); - }).toThrow("Method not implemented."); - }); - - it("should throw an error when calling getByteSize", () => { - expect(() => { - baseSerializable.getByteSize(); - }).toThrow("Method not implemented."); - }); - - it("should throw an error when calling toString", () => { - expect(() => { - baseSerializable.toString(); - }).toThrow("Method not implemented."); - }); -}); diff --git a/packages/nps/test/index.test.ts b/packages/nps/test/index.test.ts deleted file mode 100644 index 1b1ba700e..000000000 --- a/packages/nps/test/index.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { describe, expect, it } from "vitest"; - -describe("example", () => { - it("should pass", () => { - expect(true).toBe(true); - }); -}); diff --git a/packages/nps/test/pureCompare.test.ts b/packages/nps/test/pureCompare.test.ts deleted file mode 100644 index e8867d20c..000000000 --- a/packages/nps/test/pureCompare.test.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { - isZero, - isUndefined, - lessThan, - lessThanOrEqual, - greaterThan, - greaterThanOrEqual, - areBothZero, - areBothUndefined, - areBothSet, - isOnlyOneSet, -} from "../src/utils/pureCompare"; - -describe("isZero", () => { - it("returns true if the number is zero", () => { - expect(isZero(0)).toBe(true); - }); - - it("returns false if the number is not zero", () => { - expect(isZero(5)).toBe(false); - }); -}); - -describe("isUndefined", () => { - it("returns true if the value is undefined", () => { - expect(isUndefined(undefined)).toBe(true); - }); - - it("returns false if the value is not undefined", () => { - expect(isUndefined(5)).toBe(false); - }); -}); - -describe("lessThan", () => { - it("returns true if the first number is less than the second number", () => { - expect(lessThan(2, 5)).toBe(true); - }); - - it("returns false if the first number is greater than or equal to the second number", () => { - expect(lessThan(5, 2)).toBe(false); - expect(lessThan(5, 5)).toBe(false); - }); -}); - -describe("lessThanOrEqual", () => { - it("returns true if the first number is less than or equal to the second number", () => { - expect(lessThanOrEqual(2, 5)).toBe(true); - expect(lessThanOrEqual(5, 5)).toBe(true); - }); - - it("returns false if the first number is greater than the second number", () => { - expect(lessThanOrEqual(5, 2)).toBe(false); - }); -}); - -describe("greaterThan", () => { - it("returns true if the first number is greater than the second number", () => { - expect(greaterThan(5, 2)).toBe(true); - }); - - it("returns false if the first number is less than or equal to the second number", () => { - expect(greaterThan(2, 5)).toBe(false); - expect(greaterThan(5, 5)).toBe(false); - }); -}); - -describe("greaterThanOrEqual", () => { - it("returns true if the first number is greater than or equal to the second number", () => { - expect(greaterThanOrEqual(5, 2)).toBe(true); - expect(greaterThanOrEqual(5, 5)).toBe(true); - }); - - it("returns false if the first number is less than the second number", () => { - expect(greaterThanOrEqual(2, 5)).toBe(false); - }); -}); - -describe("areBothZero", () => { - it("returns true if both numbers are zero", () => { - expect(areBothZero(0, 0)).toBe(true); - }); - - it("returns false if at least one of the numbers is not zero", () => { - expect(areBothZero(0, 5)).toBe(false); - expect(areBothZero(5, 0)).toBe(false); - expect(areBothZero(5, 5)).toBe(false); - }); -}); - -describe("areBothUndefined", () => { - it("returns true if both values are undefined", () => { - expect(areBothUndefined(undefined, undefined)).toBe(true); - }); - - it("returns false if at least one of the values is not undefined", () => { - expect(areBothUndefined(undefined, 5)).toBe(false); - expect(areBothUndefined(5, undefined)).toBe(false); - expect(areBothUndefined(5, 5)).toBe(false); - }); -}); - -describe("areBothSet", () => { - it("returns true if both values are set", () => { - expect(areBothSet(5, 10)).toBe(true); - }); - - it("returns false if at least one of the values is not set", () => { - expect(areBothSet(undefined, 10)).toBe(false); - expect(areBothSet(5, undefined)).toBe(false); - expect(areBothSet(undefined, undefined)).toBe(false); - }); -}); - -describe("isOnlyOneSet", () => { - it("returns true if only one of the values is set", () => { - expect(isOnlyOneSet(5, undefined)).toBe(true); - expect(isOnlyOneSet(undefined, 10)).toBe(true); - }); - - it("returns false if both values are set or both values are not set", () => { - expect(isOnlyOneSet(5, 10)).toBe(false); - expect(isOnlyOneSet(undefined, undefined)).toBe(false); - }); -}); diff --git a/packages/nps/test/pureGet.test.ts b/packages/nps/test/pureGet.test.ts deleted file mode 100644 index 788f2cfb9..000000000 --- a/packages/nps/test/pureGet.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { - getWord, - getDWord, - getNBytes, - getAsHex, - getLenString, - getLenBlob, - getShortBool, -} from "../src/utils/pureGet"; - -describe("getWord", () => { - it("returns the word at the specified offset in little-endian format", () => { - const bytes = Buffer.from([0x01, 0x00, 0x02, 0x00]); - expect(getWord(bytes, 0, true)).toBe(1); - expect(getWord(bytes, 2, true)).toBe(2); - }); - - it("returns the word at the specified offset in big-endian format", () => { - const bytes = Buffer.from([0x00, 0x01, 0x00, 0x02]); - expect(getWord(bytes, 0, false)).toBe(1); - expect(getWord(bytes, 2, false)).toBe(2); - }); -}); - -describe("getDWord", () => { - it("returns the dword at the specified offset in little-endian format", () => { - const bytes = Buffer.from([0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00]); - expect(getDWord(bytes, 0, true)).toBe(1); - expect(getDWord(bytes, 4, true)).toBe(2); - }); - - it("returns the dword at the specified offset in big-endian format", () => { - const bytes = Buffer.from([0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02]); - expect(getDWord(bytes, 0, false)).toBe(1); - expect(getDWord(bytes, 4, false)).toBe(2); - }); -}); - -describe("getNBytes", () => { - it("returns the first n bytes of a buffer", () => { - const bytes = Buffer.from([0x01, 0x02, 0x03, 0x04]); - expect(getNBytes(bytes, 2)).toEqual(Buffer.from([0x01, 0x02])); - expect(getNBytes(bytes, 4)).toEqual(Buffer.from([0x01, 0x02, 0x03, 0x04])); - expect(getNBytes(bytes, 6)).toEqual(Buffer.from([0x01, 0x02, 0x03, 0x04])); - }); -}); - -describe("getAsHex", () => { - it("returns the hexadecimal string representation of a buffer", () => { - const bytes1 = Buffer.from([0x01, 0x02, 0x03]); - const bytes2 = Buffer.from([0x01, 0x02, 0x03, 0x04]); - expect(getAsHex(bytes1)).toBe("010203"); - expect(getAsHex(bytes2)).toBe("01020304"); - }); - - it("adds a leading zero if the length of the hexadecimal string is odd", () => { - const bytes = Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05]); - expect(getAsHex(bytes)).toBe("0102030405"); - }); -}); - -describe("getLenString", () => { - it("returns the string from a buffer based on the given offset and length in little-endian format", () => { - const bytes = Buffer.from([0x05, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f]); - expect(getLenString(bytes, 0, true)).toBe("Hello"); - }); - - it("returns the string from a buffer based on the given offset and length in big-endian format", () => { - const bytes = Buffer.from([0x00, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]); - expect(getLenString(bytes, 0, false)).toBe("Hello"); - }); -}); - -describe("getLenBlob", () => { - it("returns the blob from a buffer based on the given offset and length in little-endian format", () => { - const bytes = Buffer.from([0x05, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05]); - expect(getLenBlob(bytes, 0, true)).toEqual( - Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05]), - ); - }); - - it("returns the blob from a buffer based on the given offset and length in big-endian format", () => { - const bytes = Buffer.from([0x00, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05]); - expect(getLenBlob(bytes, 0, false)).toEqual( - Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05]), - ); - }); -}); - -describe("getShortBool", () => { - it("returns the 2-byte boolean value from the specified buffer at the given offset", () => { - const bytes = Buffer.from([0x01, 0x00, 0x00, 0x00]); - expect(getShortBool(bytes, 0)).toBe(true); - expect(getShortBool(bytes, 2)).toBe(false); - }); -}); diff --git a/packages/nps/tsconfig.json b/packages/nps/tsconfig.json deleted file mode 100644 index 0a8e12fbc..000000000 --- a/packages/nps/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "./../../tsconfig.base.json", - "compilerOptions": { - "incremental": true, - "composite": true - }, - "include": ["index.ts", "src", "**/*.ts"] -} diff --git a/packages/nps/types.ts b/packages/nps/types.ts deleted file mode 100644 index 953848790..000000000 --- a/packages/nps/types.ts +++ /dev/null @@ -1,23 +0,0 @@ -export interface ISerializable { - serialize(): Buffer; - deserialize(data: Buffer): void; - getByteSize(): number; - toString(): string; -} - -export interface IMessageHeader extends ISerializable { - getVersion(): number; - getId(): number; - getLength(): number; - getDataOffset(): number; - setVersion(version: number): void; - setId(id: number): void; - setLength(length: number): void; -} - -export interface IMessage extends ISerializable { - header: IMessageHeader; - getData(): ISerializable; - getDataAsBuffer(): Buffer; - setData(data: ISerializable): void; -} diff --git a/packages/persona/index.ts b/packages/persona/index.ts index a7d969060..9d565a149 100644 --- a/packages/persona/index.ts +++ b/packages/persona/index.ts @@ -1,2 +1,2 @@ export { getPersonasByPersonaId } from "./src/getPersonasByPersonaId.js"; -export { receivePersonaData } from "./src/receivePersonaData.js"; +export { receivePersonaData, getHandlers } from "./src/receivePersonaData.js"; diff --git a/packages/persona/src/PersonaMapsMessage.ts b/packages/persona/src/PersonaMapsMessage.ts index f7feca5f4..9dda5a951 100644 --- a/packages/persona/src/PersonaMapsMessage.ts +++ b/packages/persona/src/PersonaMapsMessage.ts @@ -5,27 +5,27 @@ import { NPSHeader } from "rusty-motors-shared"; * * This is type UserGameData */ -export class PersonaRecord { - customerId: number; - personaName: string; - serverDataId: number; - createDate: number; - lastLogin: number; - numberOfGames: number; - personaId: number; - isOnline: number; - purchaseTimestamp: number; - gameSerialNumber: string; - timeOnline: number; - timeInGame: number; - extraData: Buffer; - personaData: Buffer; - pictureData: Buffer; - dnd: number; - startedPlayingTimestamp: number; - hashedKey: string; - personaLevel: number; - shardId: number; +export class PersonaRecord { + customerId: number; // 4 + personaName: string; // 33 + serverDataId: number; // 4 + createDate: number; // 4 + lastLogin: number; // 4 + numberOfGames: number; // 4 + personaId: number; // 4 + isOnline: number; // 2 + purchaseTimestamp: number; // 4 + gameSerialNumber: string; // 33 + timeOnline: number; // 4 + timeInGame: number; // 4 + extraData: Buffer; // 512 + personaData: Buffer; // 256 + pictureData: Buffer; // 1 + dnd: number; // 2 + startedPlayingTimestamp: number; // 4 + hashedKey: string; // 400 + personaLevel: number; // 2 + shardId: number; // 2 constructor() { this.customerId = 0; this.personaName = ""; @@ -289,7 +289,7 @@ export class PersonaMapsMessage extends NPSMessage { */ deserialize(buffer: Buffer): PersonaMapsMessage { try { - this._header._doDeserialize(buffer); + this._header.deserialize(buffer); this.setBuffer(buffer.subarray(NPSHeader.size())); this.raw = buffer; return this; @@ -312,7 +312,7 @@ export class PersonaMapsMessage extends NPSMessage { } this._header.length = NPSHeader.size() + 2 + this._personaRecords.size(); const buffer = Buffer.alloc(this._header.length); - this._header._doSerialize().copy(buffer); + this._header.serialize().copy(buffer); // Write the persona count. This is known to be correct at offset 12 buffer.writeUInt16BE(this._personaRecords.personaCount(), 12); diff --git a/packages/persona/src/_gameLogout.ts b/packages/persona/src/_gameLogout.ts index 76ead3809..81d62e5bd 100644 --- a/packages/persona/src/_gameLogout.ts +++ b/packages/persona/src/_gameLogout.ts @@ -36,7 +36,7 @@ export async function _gameLogout({ log.debug(`[${connectionId}] _npsLogoutGameUser response: ${responsePacket.toHexString()}`); const outboundMessage = new SerializedBufferOld(); - outboundMessage._doDeserialize(responsePacket._doSerialize()); + outboundMessage.deserialize(responsePacket.serialize()); return { connectionId, diff --git a/packages/persona/src/_getFirstBuddy.ts b/packages/persona/src/_getFirstBuddy.ts index 46ed8601a..6039b871e 100644 --- a/packages/persona/src/_getFirstBuddy.ts +++ b/packages/persona/src/_getFirstBuddy.ts @@ -20,7 +20,7 @@ export async function _getFirstBuddy({ }> { // This message is a versioned nps message const incomingMessage = new NPSMessage(); - incomingMessage._doDeserialize(message._doSerialize()); + incomingMessage.deserialize(message.serialize()); log.debug( `in _getFirstBuddy, incomingMessage: ${incomingMessage @@ -40,7 +40,7 @@ export async function _getFirstBuddy({ buddyCountMessage.buddyCount = 0; const outboundMessage1 = new SerializedBufferOld(); - outboundMessage1._doDeserialize(buddyCountMessage.serialize()); + outboundMessage1.deserialize(buddyCountMessage.serialize()); const buddyInfoMessage = new BuddyInfoMessage(); @@ -60,7 +60,7 @@ export async function _getFirstBuddy({ } const outboundMessage = new SerializedBufferOld(); - outboundMessage._doDeserialize(buddyInfoMessage.serialize()); + outboundMessage.deserialize(buddyInfoMessage.serialize()); log.debug( `in _getFirstBuddy, outboundMessage: ${outboundMessage1.toString()}`, diff --git a/packages/persona/src/_selectGamePersona.ts b/packages/persona/src/_selectGamePersona.ts index e1928a346..959167760 100644 --- a/packages/persona/src/_selectGamePersona.ts +++ b/packages/persona/src/_selectGamePersona.ts @@ -32,7 +32,7 @@ export async function _selectGamePersona({ const requestPacket = message; log.debug( `LegacyMsg request object from _npsSelectGamePersona ${requestPacket - ._doSerialize() + .serialize() .toString("hex")}`, ); @@ -47,12 +47,12 @@ export async function _selectGamePersona({ responsePacket.setBuffer(packetContent); log.debug( `LegacyMsg response object from _npsSelectGamePersona ${responsePacket - ._doSerialize() + .serialize() .toString("hex")} `, ); const outboundMessage = new SerializedBufferOld(); - outboundMessage.setBuffer(responsePacket._doSerialize()); + outboundMessage.setBuffer(responsePacket.serialize()); return { connectionId, diff --git a/packages/persona/src/handlers/getPersonaInfo.ts b/packages/persona/src/handlers/getPersonaInfo.ts index 5b13edc95..e9fb4a4f5 100644 --- a/packages/persona/src/handlers/getPersonaInfo.ts +++ b/packages/persona/src/handlers/getPersonaInfo.ts @@ -1,5 +1,4 @@ import { LegacyMessage, NPSMessage, SerializedBufferOld, ServerLogger } from "rusty-motors-shared"; -import { createGameProfile } from "rusty-motors-nps"; import { getPersonasByPersonaId } from "../getPersonasByPersonaId.js"; import { personaToString } from "../internal.js"; @@ -21,7 +20,7 @@ export async function getPersonaInfo({ }> { log.debug("getPersonaInfo..."); const requestPacket = new NPSMessage(); - requestPacket._doDeserialize(message.serialize()); + requestPacket.deserialize(message.serialize()); log.debug( `LegacyMsg request object from getPersonaInfo ${requestPacket.toString()}`, @@ -41,7 +40,7 @@ export async function getPersonaInfo({ log.debug(`Persona found: ${personaToString(persona[0])}`); - const profile = createGameProfile(); + const profgamemessageile = createGameProfile(); profile.customerId = persona[0]!.customerId; profile.profileId = persona[0]!.personaId; @@ -55,12 +54,12 @@ export async function getPersonaInfo({ responsePacket.setBuffer(profile.serialize()); log.debug( `LegacyMsg response object from getPersonaInfo ${responsePacket - ._doSerialize() + .serialize() .toString("hex")} `, ); const outboundMessage = new SerializedBufferOld(); - outboundMessage.setBuffer(responsePacket._doSerialize()); + outboundMessage.setBuffer(responsePacket.serialize()); return { connectionId, diff --git a/packages/persona/src/handlers/validatePersonaName.ts b/packages/persona/src/handlers/validatePersonaName.ts index 34115cb3e..a3baeaee2 100644 --- a/packages/persona/src/handlers/validatePersonaName.ts +++ b/packages/persona/src/handlers/validatePersonaName.ts @@ -42,7 +42,7 @@ export async function validatePersonaName({ ); const outboundMessage = new SerializedBufferOld(); - outboundMessage._doDeserialize(responsePacket.serialize()); + outboundMessage.deserialize(responsePacket.serialize()); return { connectionId, diff --git a/packages/persona/src/internal.ts b/packages/persona/src/internal.ts index 5630ffcdc..1e2d88296 100644 --- a/packages/persona/src/internal.ts +++ b/packages/persona/src/internal.ts @@ -191,7 +191,7 @@ async function getPersonaMaps({ const requestPacket = message; log.debug( `NPSMsg request object from _npsGetPersonaMaps ${requestPacket - ._doSerialize() + .serialize() .toString("hex")} `, ); @@ -243,7 +243,7 @@ async function getPersonaMaps({ ); const outboundMessage = new SerializedBufferOld(); - outboundMessage._doDeserialize(personaMapsMessage.serialize()); + outboundMessage.deserialize(personaMapsMessage.serialize()); return { connectionId, diff --git a/packages/persona/src/receivePersonaData.ts b/packages/persona/src/receivePersonaData.ts index 0125400b9..82d136b3e 100644 --- a/packages/persona/src/receivePersonaData.ts +++ b/packages/persona/src/receivePersonaData.ts @@ -7,6 +7,10 @@ import { import type { BufferSerializer } from "rusty-motors-shared-packets"; import { messageHandlers } from "./internal"; +export function getHandlers() { + return messageHandlers; +} + /** * * @@ -39,7 +43,7 @@ export async function receivePersonaData({ // The packet needs to be an NPSMessage const inboundMessage = new LegacyMessage(); - inboundMessage._doDeserialize(message.serialize()); + inboundMessage.deserialize(message.serialize()); const supportedHandler = messageHandlers.find((h) => { return h.opCode === inboundMessage._header.id; diff --git a/packages/shared-packets/src/GameMessageHeader.ts b/packages/shared-packets/src/GameMessageHeader.ts index 1945186a1..8785f0fc6 100644 --- a/packages/shared-packets/src/GameMessageHeader.ts +++ b/packages/shared-packets/src/GameMessageHeader.ts @@ -84,18 +84,18 @@ export class GameMessageHeader this.length = data.readUInt16BE(2); } - private assertV1Checksum(data: Buffer): void { - const length = data.readUInt16BE(2); - const checksum = data.readUInt32BE(8); - if (checksum !== length) { - throw new Error( - `Checksum mismatch. Expected ${length}, got ${checksum}`, - ); - } - } + // private assertV1Checksum(data: Buffer): void { + // const length = data.readUInt16BE(2); + // const checksum = data.readUInt32BE(8); + // if (checksum !== length) { + // throw new Error( + // `Checksum mismatch. Expected ${length}, got ${checksum}`, + // ); + // } + // } private deserializeV1(data: Buffer): void { - this.assertV1Checksum(data); + // this.assertV1Checksum(data); this.id = data.readUInt16BE(0); this.length = data.readUInt16BE(2); // Skip version diff --git a/packages/shared/index.ts b/packages/shared/index.ts index 0eb521bfa..e0b0e6b87 100644 --- a/packages/shared/index.ts +++ b/packages/shared/index.ts @@ -38,6 +38,7 @@ export type { State } from "./src/State.js"; export type { OnDataHandler, ServiceResponse } from "./src/State.js"; export { LegacyMessage } from "./src/LegacyMessage.js"; export { NPSHeader } from "./src/NPSHeader.js"; +export { _MSG_STRING } from "./src/_MSG_STRING.js"; export * from "./src/interfaces.js"; import * as Sentry from "@sentry/node"; import pino from "pino"; @@ -112,20 +113,55 @@ export const cloth_grey = argbToInt(255, 146, 143, 137); //grey export const cloth_white = argbToInt(255, 255, 255, 255); //white interface Logger { - info: (msg: string, obj?: unknown) => void; - warn: (msg: string, obj?: unknown) => void; - error: (msg: string, obj?: unknown) => void; - fatal: (msg: string, obj?: unknown) => void; - debug: (msg: string, obj?: unknown) => void; - trace: (msg: string, obj?: unknown) => void; + info: pino.LogFn; + warn: pino.LogFn; + error: pino.LogFn; + fatal: pino.LogFn; + debug: pino.LogFn; + trace: pino.LogFn; child: (obj: pino.Bindings) => Logger; } type LogLevel = "fatal" | "error" | "warn" | "info" | "debug" | "trace"; +type LoggeringGroup = "gateway" | "lobby" | "roomserver"; + +const shouldLog = (loggingGroup: LoggeringGroup) => { + const loggingGroups = process.env["MCO_LOGGING_GROUPS"]?.split(",") || []; + if (loggingGroups.includes("all") || loggingGroups.includes("*")) { + return true; + } + + return loggingGroups.includes(loggingGroup); +} + let logger: pino.Logger; -export function getServerLogger(name?: string): Logger { +export function getServerLogger(name?: string, loggingGroup?: LoggeringGroup): Logger { + if (loggingGroup && !shouldLog(loggingGroup)) { + return { + info: () => { + // do nothing + }, + warn: () => { + // do nothing + }, + error: () => { + // do nothing + }, + fatal: () => { + // do nothing + }, + debug: () => { + // do nothing + }, + trace: () => { + // do nothing + }, + child: () => getServerLogger(name, loggingGroup), + } + } + if (logger) { return logger.child({ name }); } @@ -137,7 +173,7 @@ export function getServerLogger(name?: string): Logger { console.warn(`Invalid log level: ${logLevel}. Defaulting to "debug"`); } - logger = pino({ + logger = pino.default({ name: loggerName, transport: { targets: [ @@ -166,14 +202,8 @@ export function getServerLogger(name?: string): Logger { return { info: logger.info.bind(logger), warn: logger.warn.bind(logger), - error: (msg: string, obj?: unknown) => { - if (obj instanceof Error) { - Sentry.captureException(obj); - } else if (obj) { - Sentry.captureException(new Error(msg), { extra: { context: obj } }); - } else { - Sentry.captureException(new Error(msg)); - } + error: (msg: unknown, obj?: any) => { + Sentry.captureException(msg); logger.error({ msg, obj }); }, fatal: logger.fatal.bind(logger), diff --git a/packages/shared/src/GameMessage.ts b/packages/shared/src/GameMessage.ts index e3f928a6a..a3bceb01f 100644 --- a/packages/shared/src/GameMessage.ts +++ b/packages/shared/src/GameMessage.ts @@ -21,34 +21,13 @@ export class GameMessage extends BufferSerializer { buffer.copy(this._recordData); } - /** @deprecated - Use deserialize instead */ - _doDeserialize(buffer: Buffer) { - this._header._doDeserialize(buffer); - this._recordData = Buffer.alloc(this._header._gameMessageLength - 4); - buffer.copy(this._recordData, 0, 8); - return this; - } - override deserialize(buffer: Buffer) { - this._header._doDeserialize(buffer); + this._header.deserialize(buffer); this._recordData = Buffer.alloc(this._header.length - 4); buffer.copy(this._recordData, 0, 8); return this; } - /** @deprecated - Use serialize instead */ - _doSerialize(): void { - this._header._gameMessageLength = 4 + this._recordData.length; - this._header.length = this._header._gameMessageLength + 4; - const buffer = Buffer.alloc(this._header.length); - let offset = 0; // offset is 0 - this._header.serialize().copy(buffer); - offset += this._header.size(); // offset is 8 - - this._recordData.copy(buffer, offset); - this.setBuffer(buffer); - } - override serialize() { this._header._gameMessageLength = 4 + this._recordData.length; this._header.length = this._header._gameMessageLength + 4; diff --git a/packages/shared/src/LegacyMessage.ts b/packages/shared/src/LegacyMessage.ts index 01d1b49d6..6a3753f3c 100644 --- a/packages/shared/src/LegacyMessage.ts +++ b/packages/shared/src/LegacyMessage.ts @@ -14,15 +14,6 @@ export class LegacyMessage extends SerializableMixin(AbstractSerializable) { this._header = new legacyHeader(); } - /** - * @deprecated Use `deserialize` instead - */ - override _doDeserialize(buffer: Buffer): LegacyMessage { - this._header._doDeserialize(buffer); - this.setBuffer(buffer.subarray(this._header._size)); - return this; - } - getMessageId() { return this._header.id; } @@ -38,24 +29,14 @@ export class LegacyMessage extends SerializableMixin(AbstractSerializable) { * @returns The current instance with the deserialized data. */ deserialize(buffer: Buffer) { - this._header._doDeserialize(buffer); + this._header.deserialize(buffer); this.setBuffer(buffer.subarray(this._header._size)); return this; } - /** - * @deprecated Use serialize instead - */ - override _doSerialize() { - const buffer = Buffer.alloc(this._header.length); - this._header._doSerialize().copy(buffer); - super.data.copy(buffer, this._header._size); - return buffer; - } - serialize() { const buffer = Buffer.alloc(this._header.length); - this._header._doSerialize().copy(buffer); + this._header.serialize().copy(buffer); super.data.copy(buffer, this._header._size); return buffer; } diff --git a/packages/shared/src/MessageHeader.ts b/packages/shared/src/MessageHeader.ts index 1986c65da..8229f3417 100644 --- a/packages/shared/src/MessageHeader.ts +++ b/packages/shared/src/MessageHeader.ts @@ -39,7 +39,7 @@ export class MessageHeader extends SerializedBufferOld { * @param {Buffer} buffer * @returns {MessageHeader} */ - override deserialize(buffer: Buffer): MessageHeader { + override deserialize(buffer: Buffer): this { this._messageId = buffer.readInt16BE(0); this._messageLength = buffer.readInt16BE(2); return this; @@ -52,18 +52,6 @@ export class MessageHeader extends SerializedBufferOld { return buffer; } - /** - * @param {Buffer} buffer - * @returns {MessageHeader} - */ - override _doDeserialize(buffer: Buffer): MessageHeader { - return this.deserialize(buffer); - } - - override _doSerialize() { - return this.serialize(); - } - override toString() { return `MessageHeader: ${JSON.stringify({ messageId: this._messageId, diff --git a/packages/shared/src/NPSHeader.ts b/packages/shared/src/NPSHeader.ts index f13ad751e..62365f36a 100644 --- a/packages/shared/src/NPSHeader.ts +++ b/packages/shared/src/NPSHeader.ts @@ -34,7 +34,7 @@ export class NPSHeader extends SerializableMixin(AbstractSerializable) { * @throws {Error} If the buffer is too short * @throws {Error} If the buffer is malformed */ - override _doDeserialize(buffer: Buffer): NPSHeader { + deserialize(buffer: Buffer): NPSHeader { if (buffer.length < this._size) { throw Error(`Buffer length ${buffer.length} is too short to deserialize`); } @@ -48,7 +48,7 @@ export class NPSHeader extends SerializableMixin(AbstractSerializable) { return this; } - override _doSerialize() { + serialize() { const buffer = Buffer.alloc(this._size); buffer.writeInt16BE(this.id, 0); buffer.writeInt16BE(this.length, 2); diff --git a/packages/shared/src/NPSMessage.ts b/packages/shared/src/NPSMessage.ts index edf1e9a7a..1664fa33d 100644 --- a/packages/shared/src/NPSMessage.ts +++ b/packages/shared/src/NPSMessage.ts @@ -14,19 +14,15 @@ export class NPSMessage extends SerializableMixin(AbstractSerializable) { this._header = new NPSHeader(); } - /** - * @param {Buffer} buffer - * @returns {NPSMessage} - */ - override _doDeserialize(buffer: Buffer): NPSMessage { - this._header._doDeserialize(buffer); + deserialize(buffer: Buffer) { + this._header.deserialize(buffer); this.setBuffer(buffer.subarray(this._header._size)); return this; } serialize() { const buffer = Buffer.alloc(this._header.length); - this._header._doSerialize().copy(buffer); + this._header.serialize().copy(buffer); this.data.copy(buffer, this._header._size); return buffer; } diff --git a/packages/shared/src/OldServerMessage.ts b/packages/shared/src/OldServerMessage.ts index b2fecfef2..242f7403d 100644 --- a/packages/shared/src/OldServerMessage.ts +++ b/packages/shared/src/OldServerMessage.ts @@ -24,12 +24,11 @@ export class OldServerMessage extends SerializedBufferOld implements IServerMess } /** - * @deprecated * @param {Buffer} buffer * @returns {OldServerMessage} */ - override _doDeserialize(buffer: Buffer): OldServerMessage { - this._header._doDeserialize(buffer); + override deserialize(buffer: Buffer): this { + this._header.deserialize(buffer); this.setBuffer(buffer.subarray(this._header._size)); if (this.data.length > 2) { this._msgNo = this.data.readInt16LE(0); @@ -47,7 +46,7 @@ export class OldServerMessage extends SerializedBufferOld implements IServerMess */ override serialize() { const buffer = Buffer.alloc(this._header.length + 2); - this._header._doSerialize().copy(buffer); + this._header.serialize().copy(buffer); this.data.copy(buffer, this._header._size); return buffer; } diff --git a/packages/shared/src/SerializedBufferOld.ts b/packages/shared/src/SerializedBufferOld.ts index f2d0322c3..5383d0b5f 100644 --- a/packages/shared/src/SerializedBufferOld.ts +++ b/packages/shared/src/SerializedBufferOld.ts @@ -15,17 +15,9 @@ export class SerializedBufferOld extends SerializableMixin( super(); } - /** - * @param {Buffer} buffer - * @returns {SerializedBufferOld} - */ - override _doDeserialize(buffer: Buffer): SerializedBufferOld { - this.setBuffer(buffer); - return this; - } - - deserialize(data: Buffer): void { + deserialize(data: Buffer): this { this.setBuffer(data); + return this; } serialize() { diff --git a/packages/shared/src/State.ts b/packages/shared/src/State.ts index 7d4798345..1aa3f2141 100644 --- a/packages/shared/src/State.ts +++ b/packages/shared/src/State.ts @@ -7,8 +7,10 @@ // eslint-disable-next-line no-unused-vars import { Cipher, Decipher } from "crypto"; -import { SerializedBufferOld } from "./SerializedBufferOld.js"; -import { BufferSerializer } from "rusty-motors-shared-packets"; +import { + BufferSerializer, + type SerializableInterface, +} from "rusty-motors-shared-packets"; import { ServerLogger } from "../index.js"; @@ -148,9 +150,9 @@ type OnDataHandlerArgs = { }; export interface ServiceResponse { - connectionId: string; - messages: SerializedBufferOld[]; -} + connectionId: string; + messages: SerializableInterface[]; + } export type OnDataHandler = ( args: OnDataHandlerArgs, diff --git a/packages/transactions/src/_MSG_STRING.ts b/packages/shared/src/_MSG_STRING.ts similarity index 87% rename from packages/transactions/src/_MSG_STRING.ts rename to packages/shared/src/_MSG_STRING.ts index 434a4f54c..b03ab3257 100644 --- a/packages/transactions/src/_MSG_STRING.ts +++ b/packages/shared/src/_MSG_STRING.ts @@ -13,7 +13,8 @@ export function _MSG_STRING(messageID: number): string { { id: 122, name: "MC_PLAYER_INFO" }, // 0x7a"} { id: 141, name: "MC_STOCK_CAR_INFO" }, // 0x8d { id: 142, name: "MC_PURCHASE_STOCK_CAR" }, // 0x8e - { id: 146, name: "MC_GET_COMPLETE_VEHICLE_INFO" }, // 0x92 + { id: 145, name: "MC_GET_COMPLETE_VEHICLE_INFO" }, // 0x91 + { id: 163, name: "MC_UPDATE_CACHED_VEHICLE" }, // 0xa3 { id: 172, name: "MC_GET_OWNED_VEHICLES" }, // 0xac"} { id: 173, name: "MC_OWNED_VEHICLES_LIST" }, // 0xad"} { id: 174, name: "MC_GET_OWNED_PARTS" }, // 0xae"} @@ -30,7 +31,8 @@ export function _MSG_STRING(messageID: number): string { { id: 389, name: "MC_GET_MCO_TUNABLES" }, // 0x185"} { id: 391, name: "MC_CLUB_GET_INVITATIONS" }, // 0x187 { id: 438, name: "MC_CLIENT_CONNECT_MSG" }, // 0x1b6 - { id: 440, name: "MC_TRACKING_MSG" }, + { id: 440, name: "MC_TRACKING_MSG" }, // 0x1b8 + { id: 490, name: "MC_CLASSIFIED_AD_GET_COUNT" }, // 0x1ea ]; const result = messageIds.find((id) => id.id === messageID); diff --git a/packages/shared/src/legacyHeader.ts b/packages/shared/src/legacyHeader.ts index 82cd394b0..74203f2d5 100644 --- a/packages/shared/src/legacyHeader.ts +++ b/packages/shared/src/legacyHeader.ts @@ -19,14 +19,13 @@ export class legacyHeader extends SerializableMixin(AbstractSerializable) { this.length = this._size; // 2 bytes } - /** - * @param {Buffer} buffer - */ - override _doDeserialize(buffer: Buffer) { + + + deserialize(buffer: Buffer) { if (buffer.length < 4) { throw Error(`Buffer length ${buffer.length} is too short to deserialize`); } - + try { this.id = buffer.readInt16BE(0); this.length = buffer.readInt16BE(2); @@ -38,7 +37,7 @@ export class legacyHeader extends SerializableMixin(AbstractSerializable) { return this; } - override _doSerialize() { + serialize() { const buffer = Buffer.alloc(this._size); buffer.writeInt16BE(this.id, 0); buffer.writeInt16BE(this.length, 2); diff --git a/packages/shared/src/messageFactory.ts b/packages/shared/src/messageFactory.ts index 343fb21d3..32426bf91 100644 --- a/packages/shared/src/messageFactory.ts +++ b/packages/shared/src/messageFactory.ts @@ -42,6 +42,10 @@ export class AbstractSerializable { return this.internalBuffer; } + set data(buffer: Buffer) { + this.internalBuffer = buffer; + } + /** * @param {Buffer} buffer */ @@ -71,15 +75,4 @@ export const SerializableMixin = ( super(); } - serialize() { - return this._doSerialize(); - } - - /** - * @param {Buffer} buffer - * @returns {AbstractSerializable} - */ - deserialize(buffer: Buffer): AbstractSerializable { - return this._doDeserialize(buffer); - } }; diff --git a/packages/shared/src/serverHeader.ts b/packages/shared/src/serverHeader.ts index 59e1bf72b..2228ccd44 100644 --- a/packages/shared/src/serverHeader.ts +++ b/packages/shared/src/serverHeader.ts @@ -33,7 +33,7 @@ export class serverHeader extends SerializableMixin(AbstractSerializable) { * @throws {Error} If the buffer is too short * @throws {Error} If the buffer is malformed */ - override _doDeserialize(buffer: Buffer): serverHeader { + deserialize(buffer: Buffer): serverHeader { if (buffer.length < this._size) { throw new Error( `Buffer length ${buffer.length} is too short to deserialize`, @@ -53,7 +53,7 @@ export class serverHeader extends SerializableMixin(AbstractSerializable) { return this; } - override _doSerialize() { + serialize() { const buffer = Buffer.alloc(this._size); buffer.writeInt16LE(this.length, 0); buffer.write(this.mcoSig, 2, 6, "utf8"); diff --git a/packages/transactions/index.ts b/packages/transactions/index.ts index 7f3f950bc..adf12d00c 100644 --- a/packages/transactions/index.ts +++ b/packages/transactions/index.ts @@ -1 +1,2 @@ +export { PurchaseStockCarMessage } from "./src/PurchaseStockCarMessage.js"; export { receiveTransactionsData } from "./src/internal.js"; diff --git a/packages/transactions/src/CarInfoMessage.ts b/packages/transactions/src/CarInfoMessage.ts new file mode 100644 index 000000000..651ad6dce --- /dev/null +++ b/packages/transactions/src/CarInfoMessage.ts @@ -0,0 +1,52 @@ +import { SerializedBufferOld } from "rusty-motors-shared"; +import { Vehicle } from "./Vehicle.js"; +import { Part } from "./Part.js"; + + +export class CarInfoMessage extends SerializedBufferOld { + _msgNo: number; + _ownerId: number; + _vehicle: Vehicle; + _numberOfParts: number; + _partList: Part[]; + constructor(ownerId: number) { + super(); + this._msgNo = 0; // 2 bytes + this._ownerId = ownerId; // 4 bytes + this._vehicle = new Vehicle(); + this._numberOfParts = 0; // 2 bytes + + /** @type {Part[]} */ + this._partList = []; // 34 bytes each + } + + override size() { + return 8 + this._vehicle.getByteSize() + this._partList.length * 34; + } + + override serialize() { + this._numberOfParts = this._partList.length; + const buffer = Buffer.alloc(this.size()); + let offset = 0; // offset is 0 + buffer.writeUInt16LE(this._msgNo, offset); + offset += 2; // offset is 2 + buffer.writeUInt32LE(this._ownerId, offset); + offset += 4; // offset is 6 + const vehicleBuffer = this._vehicle.serialize(); + vehicleBuffer.copy(buffer, offset); + offset += vehicleBuffer.length; + buffer.writeUint16LE(this._numberOfParts, offset); + offset += 2; // offset is 8 + for (const part of this._partList) { + const partBuffer = part.serialize(); + partBuffer.copy(buffer, offset); + offset += partBuffer.length; + } + + return buffer; + } + + override toString() { + return `CarInfoMessage: msgNo=${this._msgNo} ownerId=${this._ownerId} vehicle=${this._vehicle.toString()} numberOfParts=${this._numberOfParts}`; + } +} diff --git a/packages/transactions/src/EntryFeePurseMessage.ts b/packages/transactions/src/EntryFeePurseMessage.ts index 4d2323662..4c7d95a2a 100644 --- a/packages/transactions/src/EntryFeePurseMessage.ts +++ b/packages/transactions/src/EntryFeePurseMessage.ts @@ -14,6 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +import { BytableStructure } from "@rustymotors/binary"; import { SerializedBufferOld } from "rusty-motors-shared"; /** @@ -59,7 +60,7 @@ export class EntryFeePurseMessage extends SerializedBufferOld { offset += 1; // offset is 5 for (const entry of this._purseEntries) { entry.serialize().copy(buffer, offset); - offset += entry.size(); + offset += entry.serializeSize } // offset is now 4 + this._lobbyList.length * 563 return buffer; @@ -70,7 +71,7 @@ export class EntryFeePurseMessage extends SerializedBufferOld { } } -export class PurseEntry extends SerializedBufferOld { +export class PurseEntry extends BytableStructure { _entryFee: number; // 4 bytes _purse: number; // 4 bytes constructor() { @@ -79,7 +80,7 @@ export class PurseEntry extends SerializedBufferOld { this._purse = 0; } - override size() { + override get serializeSize() { return 8; } @@ -89,9 +90,9 @@ export class PurseEntry extends SerializedBufferOld { * @param {Buffer} data */ override deserialize(data: Buffer) { - if (data.length !== this.size()) { + if (data.length !== this.serializeSize) { throw Error( - `PurseEntry.deserialize() expected ${this.size()} bytes but got ${ + `PurseEntry.deserialize() expected ${this.serializeSize} bytes but got ${ data.length } bytes`, ); @@ -106,7 +107,7 @@ export class PurseEntry extends SerializedBufferOld { } override serialize() { - const buf = Buffer.alloc(this.size()); + const buf = Buffer.alloc(this.serializeSize); let offset = 0; // offset is 0 buf.writeUInt32LE(this._entryFee, offset); offset += 4; // offset is 4 diff --git a/packages/transactions/src/LobbyMessage.ts b/packages/transactions/src/LobbyMessage.ts index 7650d118d..1f76d4abb 100644 --- a/packages/transactions/src/LobbyMessage.ts +++ b/packages/transactions/src/LobbyMessage.ts @@ -14,6 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +import { BytableStructure } from "@rustymotors/binary"; import { SerializedBufferOld } from "rusty-motors-shared"; /** @@ -35,7 +36,7 @@ export class LobbyMessage extends SerializedBufferOld { } override size() { - return 5 + this._lobbyList.length * 563; + return 4 + this._lobbyList.length * 561; } /** @@ -59,7 +60,7 @@ export class LobbyMessage extends SerializedBufferOld { offset += 1; // offset is 5 for (const lobby of this._lobbyList) { lobby.serialize().copy(buffer, offset); - offset += lobby.size(); + offset += lobby.serializeSize; } // offset is now 4 + this._lobbyList.length * 563 return buffer; @@ -70,7 +71,7 @@ export class LobbyMessage extends SerializedBufferOld { } } -export class LobbyInfo extends SerializedBufferOld { +export class LobbyInfo extends BytableStructure { _lobbyId: number; _raceTypeId: number; _terfId: number; @@ -259,7 +260,7 @@ export class LobbyInfo extends SerializedBufferOld { return buf; } - override size() { + override get serializeSize() { return 563; } @@ -269,9 +270,9 @@ export class LobbyInfo extends SerializedBufferOld { * @param {Buffer} data */ override deserialize(data: Buffer) { - if (data.length !== this.size()) { + if (data.length !== this.serializeSize) { throw Error( - `LobbyInfo.deserialize() expected ${this.size()} bytes but got ${ + `LobbyInfo.deserialize() expected ${this.serializeSize} bytes but got ${ data.length } bytes`, ); @@ -472,7 +473,7 @@ export class LobbyInfo extends SerializedBufferOld { } override serialize() { - const buf = Buffer.alloc(this.size()); + const buf = Buffer.alloc(this.serializeSize); let offset = 0; // offset is 0 buf.writeUInt32LE(this._lobbyId, offset); offset += 4; // offset is 4 diff --git a/packages/transactions/src/Part.ts b/packages/transactions/src/Part.ts new file mode 100644 index 000000000..111de214b --- /dev/null +++ b/packages/transactions/src/Part.ts @@ -0,0 +1,56 @@ +import { SerializedBufferOld } from "rusty-motors-shared"; + + +export class Part extends SerializedBufferOld { + _partId: number; // 4 bytes + _parentPartId: number; // 4 bytes + _brandedPartId: number; // 4 bytes + _repairPrice: number; // 4 bytes + _junkPrice: number; // 4 bytes + _wear: number; // 4 bytes + _attachmentPoint: number; // 1 byte + _damage: number; // 1 byte + + constructor() { + super(); + this._partId = 0; // 4 bytes + this._parentPartId = 0; // 4 bytes + this._brandedPartId = 0; // 4 bytes + this._repairPrice = 0; // 4 bytes + this._junkPrice = 0; // 4 bytes + this._wear = 0; // 4 bytes + this._attachmentPoint = 0; // 1 byte + this._damage = 0; // 1 byte + } + + override size() { + return 26; + } + + override serialize() { + const buffer = Buffer.alloc(this.size()); + let offset = 0; + buffer.writeUInt32LE(this._partId, offset); + offset += 4; // offset is 4 + buffer.writeUInt32LE(this._parentPartId, offset); + offset += 4; // offset is 8 + buffer.writeUInt32LE(this._brandedPartId, offset); + offset += 4; // offset is 12 + buffer.writeUInt32LE(this._repairPrice, offset); + offset += 4; // offset is 16 + buffer.writeUInt32LE(this._junkPrice, offset); + offset += 4; // offset is 20 + buffer.writeUInt32LE(this._wear, offset); + offset += 4; // offset is 24 + buffer.writeUInt8(this._attachmentPoint, offset); + offset += 1; // offset is 25 + buffer.writeUInt8(this._damage, offset); + offset += 1; // offset is 26 + + return buffer; + } + + override toString() { + return `Part: partId=${this._partId} parentPartId=${this._parentPartId} brandedPartId=${this._brandedPartId} repairPrice=${this._repairPrice} junkPrice=${this._junkPrice} wear=${this._wear} attachmentPoint=${this._attachmentPoint} damage=${this._damage}`; + } +} diff --git a/packages/transactions/src/PartsAssemblyMessage.ts b/packages/transactions/src/PartsAssemblyMessage.ts index 4c2d5ea04..65ec5629c 100644 --- a/packages/transactions/src/PartsAssemblyMessage.ts +++ b/packages/transactions/src/PartsAssemblyMessage.ts @@ -5,17 +5,17 @@ export class PartsAssemblyMessage extends SerializedBufferOld { _ownerId: number; _numberOfParts: number; _partList: Part[]; - constructor(owerId: number) { + constructor(ownerId: number) { super(); this._msgNo = 0; // 2 bytes - this._ownerId = owerId; // 4 bytes - this._numberOfParts = 0; // 1 bytes + this._ownerId = ownerId; // 4 bytes + this._numberOfParts = 0; // 2 bytes /** @type {Part[]} */ this._partList = []; // 34 bytes each } override size() { - return 7 + this._partList.length * 34; + return 8 + this._partList.length * 34; } override serialize() { @@ -25,8 +25,8 @@ export class PartsAssemblyMessage extends SerializedBufferOld { offset += 2; // offset is 2 buffer.writeUInt32LE(this._ownerId, offset); offset += 4; // offset is 6 - buffer.writeInt8(this._numberOfParts, offset); - offset += 1; // offset is 7 + buffer.writeUint16LE(this._numberOfParts, offset); + offset += 2; // offset is 8 for (const part of this._partList) { const partBuffer = part.serialize(); partBuffer.copy(buffer, offset); @@ -46,8 +46,6 @@ export class Part extends SerializedBufferOld { _wear: number; // 4 bytes _attachmentPoint: number; // 1 byte _damage: number; // 1 byte - _retailPrice: number; // 4 bytes - _maxWear: number; // 4 bytes constructor() { super(); @@ -59,13 +57,10 @@ export class Part extends SerializedBufferOld { this._wear = 0; // 4 bytes this._attachmentPoint = 0; // 1 byte this._damage = 0; // 1 byte - this._retailPrice = 0; // 4 bytes - this._maxWear = 0; // 4 bytes - // 33 bytes total } override size() { - return 34; + return 26; } override serialize() { @@ -87,15 +82,11 @@ export class Part extends SerializedBufferOld { offset += 1; // offset is 25 buffer.writeUInt8(this._damage, offset); offset += 1; // offset is 26 - buffer.writeUInt32LE(this._retailPrice, offset); - offset += 4; // offset is 30 - buffer.writeUInt32LE(this._maxWear, offset); - // offset += 4; // offset is 34 return buffer; } override toString() { - return `Part: partId=${this._partId} parentPartId=${this._parentPartId} brandedPartId=${this._brandedPartId} repairPrice=${this._repairPrice} junkPrice=${this._junkPrice} wear=${this._wear} attachmentPoint=${this._attachmentPoint} damage=${this._damage} retailPrice=${this._retailPrice} maxWear=${this._maxWear}`; + return `Part: partId=${this._partId} parentPartId=${this._parentPartId} brandedPartId=${this._brandedPartId} repairPrice=${this._repairPrice} junkPrice=${this._junkPrice} wear=${this._wear} attachmentPoint=${this._attachmentPoint} damage=${this._damage}`; } } diff --git a/packages/transactions/src/PurchaseStockCarMessage.ts b/packages/transactions/src/PurchaseStockCarMessage.ts new file mode 100644 index 000000000..e83494bdd --- /dev/null +++ b/packages/transactions/src/PurchaseStockCarMessage.ts @@ -0,0 +1,40 @@ +import { ServerPacket } from "rusty-motors-shared-packets"; + +export class PurchaseStockCarMessage extends ServerPacket { + dealerId = 0; + brandedPardId = 0; + skinId = 0; + tradeInCarId = 0; + + constructor() { + super(); + this.messageId = 142; + } + + override getByteSize(): number { + return this.header.getByteSize() + + 2 + + 4 * 4; + } + + override serialize(): Buffer { + throw new Error("Method not implemented."); + } + + override deserialize(data: Buffer): ThisType { + this.header.deserialize(data.subarray(0, this.header.getByteSize())); + this._data = data.subarray(this.header.getByteSize()); + this._assertEnoughData(this._data, 16); + this.messageId = this._data.readUInt16LE(0); + this.dealerId = this._data.readUInt32LE(2); + this.brandedPardId = this._data.readUInt32LE(6); + this.skinId = this._data.readUInt32LE(10); + this.tradeInCarId = this._data.readUInt32LE(14); + + return this; + } + + override toString() { + return `PurchaseStockCarMessage: ${this.dealerId}, ${this.brandedPardId}, ${this.skinId}, ${this.tradeInCarId}`; + } +} diff --git a/packages/transactions/src/TClientConnectMessage.ts b/packages/transactions/src/TClientConnectMessage.ts index 87c64b02e..3f8da73fd 100644 --- a/packages/transactions/src/TClientConnectMessage.ts +++ b/packages/transactions/src/TClientConnectMessage.ts @@ -41,7 +41,7 @@ export class TClientConnectMessage extends OldServerMessage { */ override deserialize(buffer: Buffer) { let offset = 0; - this._header._doDeserialize(buffer); + this._header.deserialize(buffer); offset += this._header._size; this._msgNo = buffer.readUInt16LE(offset); offset += 2; @@ -55,12 +55,13 @@ export class TClientConnectMessage extends OldServerMessage { offset += 13; this._mcVersion = buffer.toString("utf8", offset, offset + 4); // 51 bytes + return this; } override serialize() { const buffer = Buffer.alloc(this.size()); let offset = 0; - buffer.copy(this._header._doSerialize(), offset); + buffer.copy(this._header.serialize(), offset); offset += this._header._size; buffer.writeUInt16LE(this._msgNo, offset); offset += 2; diff --git a/packages/transactions/src/TLoginMessage.ts b/packages/transactions/src/TLoginMessage.ts index ca1b1a351..b5031b844 100644 --- a/packages/transactions/src/TLoginMessage.ts +++ b/packages/transactions/src/TLoginMessage.ts @@ -81,7 +81,7 @@ export class TLoginMessage extends OldServerMessage { */ override deserialize(buffer: Buffer) { let offset = 0; - this._header._doDeserialize(buffer); + this._header.deserialize(buffer); offset += this._header._size; this._msgNo = buffer.readUInt16LE(offset); offset += 2; @@ -99,12 +99,13 @@ export class TLoginMessage extends OldServerMessage { offset += 13; this._mcVersion = buffer.toString("utf8", offset, offset + 4); // 40 bytes + return this; } override serialize() { const buffer = Buffer.alloc(this._size); let offset = 0; - buffer.copy(this._header._doSerialize(), offset); + buffer.copy(this._header.serialize(), offset); offset += this._header._size; buffer.writeUInt16LE(this._msgNo, offset); offset += 2; diff --git a/packages/transactions/src/Vehicle.ts b/packages/transactions/src/Vehicle.ts new file mode 100644 index 000000000..1191ca3df --- /dev/null +++ b/packages/transactions/src/Vehicle.ts @@ -0,0 +1,62 @@ +import { SerializedBufferOld } from "rusty-motors-shared"; + +export class Vehicle extends SerializedBufferOld { + _vehicleId: number; // 4 bytes + _skinId: number; // 4 bytes + _flags: number; // 4 bytes + _delta: number; // 4 bytes + _carClass: number; // 1 byte + _damageLength: number; // 2 bytes + _damage: Buffer; // 2000 bytes (max) + + constructor() { + super(); + this._vehicleId = 0; // 4 bytes + this._skinId = 0; // 4 bytes + this._flags = 0; // 4 bytes + this._delta = 0; // 4 bytes + this._carClass = 0; // 1 byte + this._damageLength = 1; // 2 bytes + this._damage = Buffer.alloc(2000); // 2000 bytes (max) + } + + override size() { + return 4 + 4 + 4 + 4 + 1 + 2 + (this._damageLength * 1); + } + + override serialize() { + const buffer = Buffer.alloc(this.size()); + let offset = 0; + buffer.writeUInt32LE(this._vehicleId, offset); + offset += 4; // offset is 4 + buffer.writeUInt32LE(this._skinId, offset); + offset += 4; // offset is 8 + buffer.writeUInt32LE(this._flags, offset); + offset += 4; // offset is 12 + buffer.writeUInt32LE(this._delta, offset); + offset += 4; // offset is 16 + buffer.writeUInt8(this._carClass, offset); + offset += 1; // offset is 17 + buffer.writeUInt16LE(this._damageLength, offset); + offset += 2; // offset is 19 + this._damage.copy(buffer, offset); + offset += this._damageLength; // offset is 19 + this._damageLength + + return buffer; + } + + toJSON() { + return { + vehicleId: this._vehicleId, + skinId: this._skinId, + flags: this._flags, + delta: this._delta, + carClass: this._carClass, + damageLength: this._damageLength, + }; + } + + override toString() { + return `Vehicle: vehicleId=${this._vehicleId} skinId=${this._skinId} flags=${this._flags} delta=${this._delta} carClass=${this._carClass} damageLength=${this._damageLength}`; + } +} diff --git a/packages/transactions/src/_buyCarFromDealer.ts b/packages/transactions/src/_buyCarFromDealer.ts index 4c1b5352b..afcbd0744 100644 --- a/packages/transactions/src/_buyCarFromDealer.ts +++ b/packages/transactions/src/_buyCarFromDealer.ts @@ -1,52 +1,14 @@ -import { OldServerMessage } from "rusty-motors-shared"; -import type { MessageHandlerArgs, MessageHandlerResult } from "./handlers.js"; -import { ServerPacket } from "rusty-motors-shared-packets"; +import { fetchStateFromDatabase, findSessionByConnectionId, OldServerMessage } from "rusty-motors-shared"; +import type { MessageHandlerArgs, MessageHandlerResult } from './handlers.js'; import { GenericReplyMessage } from "./GenericReplyMessage.js"; +import { addVehicle } from "./_getOwnedVehicles.js"; import { getServerLogger } from "rusty-motors-shared"; +import { PurchaseStockCarMessage } from './PurchaseStockCarMessage.js'; +import { purchaseCar } from "rusty-motors-database"; const defaultLogger = getServerLogger("handlers/_buyCarFromDealer"); -class PurchaseStockCarMessage extends ServerPacket { - dealerId = 0; - brandedPardId = 0; - skinId = 0; - tradeInCarId = 0; - - constructor() { - super(); - this.messageId = 142; - } - - override getByteSize(): number { - return this.header.getByteSize() - + 2 - + 4 * 4; - } - - override serialize(): Buffer { - throw new Error("Method not implemented."); - } - - override deserialize(data: Buffer): ThisType { - this.header.deserialize(data.subarray(0, this.header.getByteSize())); - this._data = data.subarray(this.header.getByteSize()); - this._assertEnoughData(this._data, 16); - this.messageId = this._data.readUInt16LE(0); - this.dealerId = this._data.readUInt32LE(2); - this.brandedPardId = this._data.readUInt32LE(6); - this.skinId = this._data.readUInt32LE(10); - this.tradeInCarId = this._data.readUInt32LE(14); - - return this; - } - - override toString() { - return `PurchaseStockCarMessage: ${this.dealerId}, ${this.brandedPardId}, ${this.skinId}, ${this.tradeInCarId}`; - } - -} - /** * @param {MessageHandlerArgs} args * @return {Promise} @@ -56,30 +18,65 @@ export async function _buyCarFromDealer({ packet, log = defaultLogger, }: MessageHandlerArgs): Promise { - const purchaseStockCarMessage = new PurchaseStockCarMessage(); - purchaseStockCarMessage.deserialize(packet.serialize()); - - log.debug(`[${connectionId}] Received PurchaseStockCarMessage: ${purchaseStockCarMessage.toString()}`); + const purchaseStockCarMessage = new PurchaseStockCarMessage(); + purchaseStockCarMessage.deserialize(packet.serialize()); + + log.debug( + `[${connectionId}] Received PurchaseStockCarMessage: ${purchaseStockCarMessage.toString()}`, + ); + + const session = findSessionByConnectionId( + fetchStateFromDatabase(), + connectionId, + ); + if (!session) { + log.error({ connectionId }, 'Session not found'); + throw new Error(`Session not found for connectionId: ${connectionId}`); + } // TODO: Implement car purchase logic here + // TODO: Get the new car ID from the database + const newCarId = await purchaseCar(session.gameId, purchaseStockCarMessage.dealerId, purchaseStockCarMessage.brandedPardId, purchaseStockCarMessage.skinId, purchaseStockCarMessage.tradeInCarId) + .then((newCarId) => { + log.debug({ connectionId }, 'Purchased car'); + return newCarId; + }) + .catch((error) => { + log.error({ connectionId, error }, 'Failed to purchase car'); + throw new Error('Failed to purchase car'); + }) + + log.debug( + `[${connectionId}] Purchased car with ID: ${newCarId}`, + ); + + // For now, just add a new car to the player's inventory + addVehicle( + session.gameId, + 1000, + purchaseStockCarMessage.brandedPardId, + purchaseStockCarMessage.skinId, + ); const replyPacket = new GenericReplyMessage(); replyPacket.msgNo = 103; // GenericReplyMessage replyPacket.msgReply = 142; // PurchaseStockCarMessage replyPacket.result.writeUInt32LE(101, 0); // MC_SUCCESS - replyPacket.data.writeUInt32LE(1000, 0); // New car ID - - + replyPacket.data.writeUInt32LE(newCarId, 0); - log.debug(`[${connectionId}] Sending GenericReplyMessage: ${replyPacket.toString()}`); + log.debug( + `[${connectionId}] Sending GenericReplyMessage: ${replyPacket.toString()}`, + ); - const responsePacket = new OldServerMessage(); - responsePacket._header.sequence = packet.sequenceNumber; - responsePacket._header.flags = 8; + const responsePacket = new OldServerMessage(); + responsePacket._header.sequence = packet.sequenceNumber; + responsePacket._header.flags = 8; - responsePacket.setBuffer(replyPacket.serialize()); + responsePacket.setBuffer(replyPacket.serialize()); - log.debug(`[${connectionId}] Sending response packet: ${responsePacket.toHexString()}`); + log.debug( + `[${connectionId}] Sending response packet: ${responsePacket.toHexString()}`, + ); - return { connectionId, messages: [responsePacket] }; + return { connectionId, messages: [responsePacket] }; } diff --git a/packages/transactions/src/_getFullCarInfo.ts b/packages/transactions/src/_getFullCarInfo.ts index 51c9dd7d0..f14fe8e33 100644 --- a/packages/transactions/src/_getFullCarInfo.ts +++ b/packages/transactions/src/_getFullCarInfo.ts @@ -1,49 +1,249 @@ -import { OldServerMessage } from "rusty-motors-shared"; +import { buildVehiclePartTreeFromDB, type TPart, getVehiclePartTree } from "rusty-motors-database"; +import { getServerLogger, OldServerMessage } from "rusty-motors-shared"; +import { MessageHandlerArgs, MessageHandlerResult } from "./handlers.js"; import { GenericRequestMessage } from "./GenericRequestMessage.js"; -import { OwnedVehicle, OwnedVehiclesMessage } from "./OwnedVehiclesMessage.js"; -import type { MessageHandlerArgs, MessageHandlerResult } from "./handlers.js"; +import { vehiclePartTreeToJSON } from "../../database/src/models/VehiclePartTree.js"; +const DAMAGE_SIZE = 2000; -const vehicleList = [ - { - personId: 1, - vehicleId: 1, - brandedPartId: 113, - }, -]; +export class VehicleStruct { + VehicleID: number = 0; // 4 bytes + SkinID: number = 0; // 4 bytes + Flags: number = 0; // 4 bytes + Delta: number = 0; // 4 bytes + CarClass: number = 0; // 1 byte + Damage: Buffer = Buffer.alloc(DAMAGE_SIZE); // buffer, max DAMAGE_SIZE + damageLengthOverride: number | null = null; -export async function _getFullCarInfo( - args: MessageHandlerArgs, -): Promise { - const getFullCarInfoMessage = new GenericRequestMessage(); - getFullCarInfoMessage.deserialize(args.packet.data); + serialize() { + try { + const buffer = Buffer.alloc(this.size()); + buffer.writeInt32LE(this.VehicleID, 0); // offset 0 + buffer.writeInt32LE(this.SkinID, 4); // offset 4 + buffer.writeInt32LE(this.Flags, 8); // offset 8 + buffer.writeInt32LE(this.Delta, 12); // offset 12 + buffer.writeInt8(this.CarClass, 16); // offset 16 + const damageLengthOverride = this.damageLengthOverride ?? this.Damage.length; + buffer.writeInt16LE(damageLengthOverride, 17); // offset 17 + if (this.Damage.length > 0) { + this.Damage.copy(buffer, 19); // offset 19 + } + return buffer; + } catch (error) { + getServerLogger("transactions/VehicleStruct").error( + `Error in VehicleStruct.serialize: ${error}`, + ); + throw error; + } + } - args.log.debug(`Received Message: ${getFullCarInfoMessage.toString()}`); + size() { + return 19 + this.Damage.length; + } - const carId = getFullCarInfoMessage.data.readUInt32LE(0); + toString() { + return ` + VehicleID: ${this.VehicleID} + SkinID: ${this.SkinID} + Flags: ${this.Flags} + Delta: ${this.Delta} + CarClass: ${this.CarClass} + Damage: ${this.Damage.toString("hex")} + `; + } +} + +export class PartStruct { + partId: number = 0; // 4 bytes + parentPartId: number | null = 0; // 4 bytes + brandedPartId: number = 0; // 4 bytes + repairCost: number = 0; // 4 bytes + junkyardValue: number = 0; // 4 bytes + wear: number = 0; // 4 bytes + attachmentPoint: number = 0; // 1 byte + damage: number = 0; // 1 byte + + serialize() { + const log = getServerLogger("transactions/PartStruct"); + try { + const buffer = Buffer.alloc(this.size()); + buffer.writeInt32LE(this.partId, 0); + buffer.writeInt32LE(this.parentPartId ?? 0, 4); + buffer.writeInt32LE(this.brandedPartId, 8); + buffer.writeInt32LE(this.repairCost, 12); + buffer.writeInt32LE(this.junkyardValue, 16); + buffer.writeInt32LE(this.wear, 20); + buffer.writeInt8(this.attachmentPoint, 24); + buffer.writeInt8(this.damage, 25); + return buffer; + } catch (error) { + log.error(`Error in PartStruct.serialize: ${error}`); + throw error; + } + } + + size() { + return 26; + } + + toString() { + return `partId: ${this.partId} parentPartId: ${this.parentPartId} brandedPartId: ${this.brandedPartId} repairCost: ${this.repairCost} junkyardValue: ${this.junkyardValue} wear: ${this.wear} attachmentPoint: ${this.attachmentPoint} damage: ${this.damage}`; + } +} + +class CarInfoStruct { + msgNo: number = 0; // 2 bytes + playerId: number = 0; // 4 bytes + vehicle: VehicleStruct = new VehicleStruct(); + noOfParts: number = 0; // 2 bytes + parts: PartStruct[] = []; + + serialize() { + const log = getServerLogger("transactions/CarInfoStruct"); + try { + const neededSize = 10 + this.vehicle.size() + this.noOfParts * 26; + + log.debug(`Needed size: ${neededSize}`); + + const buffer = Buffer.alloc(neededSize); + log.debug(`Writing msgNo: ${this.msgNo}`); + buffer.writeInt16LE(this.msgNo, 0); + log.debug(`Writing playerId: ${this.playerId}`); + buffer.writeInt32LE(this.playerId, 2); + log.debug(`Serializing vehicle`); + this.vehicle.serialize().copy(buffer, 6); + log.debug(`Writing noOfParts: ${this.noOfParts}`); + buffer.writeInt16LE(this.noOfParts, 6 + this.vehicle.size()); + let offset = 8 + this.vehicle.size(); + for (const part of this.parts) { + log.debug(`Serializing part: ${part}`); + part.serialize().copy(buffer, offset); + offset += part.size(); + } + return buffer; + } catch (error) { + log.error(`Error in CarInfoStruct.serialize: ${error}`); + throw error; + } + } + + size() { + return 10 + this.vehicle.size() + this.noOfParts * 26; + } + + toString() { + return `msgNo: ${this.msgNo} playerId: ${this.playerId} vehicle: ${this.vehicle} noOfParts: ${this.noOfParts} parts: ${this.parts}`; + } +} + +export type DBPart = { + partId: number; + parentPartId: number | null; + brandedPartId: number; + percentDamage: number; + itemWear: number; + attachmentPointId: number; + ownerId: number; + partName: string; + repairCost: number; + scrapValue: number; +}; + +/** + * @param {MessageHandlerArgs} args + * @return {Promise} + */ +export async function _getCompleteVehicleInfo({ + connectionId, + packet, + log = getServerLogger("transactions/getCompleteVehicleInfo"), +}: MessageHandlerArgs): Promise { + const getCompleteVehicleInfoMessage = new GenericRequestMessage(); + getCompleteVehicleInfoMessage.deserialize(packet.data); + + log.debug(`Received Message: ${getCompleteVehicleInfoMessage.toString()}`); + + const vehicleId = getCompleteVehicleInfoMessage.data.readInt32LE(); + const delta = getCompleteVehicleInfoMessage.data2.readInt32LE(); + + log.debug(`Requesting vehicleId: ${vehicleId} delta: ${delta}`); + + try { + const carInfo = new CarInfoStruct(); + + let vehicleFromCache = await getVehiclePartTree(vehicleId); + + if (typeof vehicleFromCache === "undefined") { + log.debug( + `Vehicle with id ${vehicleId} not found in cache, fetching from DB`, + ); + vehicleFromCache = await buildVehiclePartTreeFromDB(vehicleId); + } + + if (typeof vehicleFromCache === "undefined") { + throw new Error( + `Vehicle with id ${vehicleId} not found and not in DB`, + ); + } + + log.debug( + `Vehicle part tree successfully fetched: ${vehiclePartTreeToJSON( + vehicleFromCache, + )}`, + ); + + carInfo.msgNo = 123; + carInfo.playerId = 1; + + const vehicleStruct = new VehicleStruct(); + vehicleStruct.VehicleID = vehicleId; + vehicleStruct.SkinID = vehicleFromCache.skinId; + vehicleStruct.Flags = vehicleFromCache.flags; + vehicleStruct.Delta = 0; + vehicleStruct.CarClass = vehicleFromCache.class; + const damageInfo = + vehicleFromCache.damageInfo ?? Buffer.alloc(DAMAGE_SIZE); + vehicleStruct.Damage = damageInfo; + vehicleStruct.damageLengthOverride = 0; + + log.debug(`VehicleStruct: ${vehicleStruct}`); + + carInfo.vehicle = vehicleStruct; - + const parts: PartStruct[] = []; - const delta = getFullCarInfoMessage.data.readUInt32LE(4); + const tmpParts: TPart[] = vehicleFromCache.partTree.level1.parts.concat( + vehicleFromCache.partTree.level2.parts, + ); - const ownedVehiclesMessage = new OwnedVehiclesMessage(); + carInfo.noOfParts = tmpParts.length; - const vehicles = getVehiclesForPerson(personId); + for (const part of tmpParts) { + const partStruct = new PartStruct(); + partStruct.partId = part.part_id; + partStruct.parentPartId = part.parent_part_id; + partStruct.brandedPartId = part.branded_part_id; + partStruct.repairCost = part.repair_cost; + partStruct.junkyardValue = part.scrap_value; + partStruct.wear = part.item_wear; + partStruct.attachmentPoint = part.attachment_point_id ?? 0; + partStruct.damage = part.percent_damage; - for (const vehicle of vehicles) { - const ownedVehicle = new OwnedVehicle(); - ownedVehicle._vehicleId = vehicle.vehicleId; - ownedVehicle._brandedPartId = vehicle.brandedPartId; - ownedVehiclesMessage.addVehicle(ownedVehicle); - } + log.debug(`PartStruct: ${partStruct}`); - ownedVehiclesMessage._msgNo = 173; + parts.push(partStruct); + } - const responsePacket = new OldServerMessage(); - responsePacket._header.sequence = args.packet._header.sequence; - responsePacket._header.flags = 8; + carInfo.parts = parts; - responsePacket.setBuffer(ownedVehiclesMessage.serialize()); + const responsePacket = new OldServerMessage(); + responsePacket._header.sequence = packet.sequenceNumber; + responsePacket._header.flags = 8; + responsePacket.setBuffer(carInfo.serialize()); - return { connectionId: args.connectionId, messages: [responsePacket] }; + return { connectionId, messages: [responsePacket] }; + } catch (error) { + log.error(`Error in Fetching car: ${error}`); + throw error; + } } diff --git a/packages/transactions/src/_getOwnedVehicles.ts b/packages/transactions/src/_getOwnedVehicles.ts index 4a7267bd2..7ad20ead4 100644 --- a/packages/transactions/src/_getOwnedVehicles.ts +++ b/packages/transactions/src/_getOwnedVehicles.ts @@ -1,51 +1,64 @@ import { OldServerMessage } from "rusty-motors-shared"; import { GenericRequestMessage } from "./GenericRequestMessage.js"; import { OwnedVehicle, OwnedVehiclesMessage } from "./OwnedVehiclesMessage.js"; -import type { MessageHandlerArgs, MessageHandlerResult } from "./handlers.js" +import type { MessageHandlerArgs, MessageHandlerResult } from "./handlers.js"; import { getServerLogger } from "rusty-motors-shared"; +import { getOwnedVehiclesForPerson } from 'rusty-motors-database'; const defaultLogger = getServerLogger("handlers/_getOwnedVehicles"); -const vehicleList = [ - { - personId: 1, - vehicleId: 1, - brandedPartId: 113, - }, -]; +const vehicleList: { + personId: number; + vehicleId: number; + brandedPartId: number; + skinId: number; +}[] = []; -export function getVehiclesForPerson(personId: number) { - return vehicleList.filter((vehicle) => vehicle.personId === personId); + + +export async function getVehicleById(vehicleId: number) { + return vehicleList.find((vehicle) => vehicle.vehicleId === vehicleId); } -export async function _getOwnedVehicles( - { connectionId, packet, log = defaultLogger }: MessageHandlerArgs -): Promise { - const getOwnedVehiclesMessage = new GenericRequestMessage(); - getOwnedVehiclesMessage.deserialize(packet.data); +export async function _getOwnedVehicles({ + connectionId, + packet, + log = defaultLogger, +}: MessageHandlerArgs): Promise { + const getOwnedVehiclesMessage = new GenericRequestMessage(); + getOwnedVehiclesMessage.deserialize(packet.data); - log.debug(`Received Message: ${getOwnedVehiclesMessage.toString()}`); + log.debug(`Received Message: ${getOwnedVehiclesMessage.toString()}`); - const personId = getOwnedVehiclesMessage.data.readUInt32LE(0); + const personId = getOwnedVehiclesMessage.data.readUInt32LE(0); - const ownedVehiclesMessage = new OwnedVehiclesMessage(); + const ownedVehiclesMessage = new OwnedVehiclesMessage(); - const vehicles = getVehiclesForPerson(personId); + const vehicles = await getOwnedVehiclesForPerson(personId); - for (const vehicle of vehicles) { - const ownedVehicle = new OwnedVehicle(); - ownedVehicle._vehicleId = vehicle.vehicleId; - ownedVehicle._brandedPartId = vehicle.brandedPartId; - ownedVehiclesMessage.addVehicle(ownedVehicle); - } + for (const vehicle of vehicles) { + const ownedVehicle = new OwnedVehicle(); + ownedVehicle._vehicleId = vehicle.partId; + ownedVehicle._brandedPartId = vehicle.brandedPartId; + ownedVehiclesMessage.addVehicle(ownedVehicle); + } - ownedVehiclesMessage._msgNo = 173; + ownedVehiclesMessage._msgNo = 173; - const responsePacket = new OldServerMessage(); - responsePacket._header.sequence = packet.sequenceNumber; - responsePacket._header.flags = 8; + const responsePacket = new OldServerMessage(); + responsePacket._header.sequence = packet.sequenceNumber; + responsePacket._header.flags = 8; - responsePacket.setBuffer(ownedVehiclesMessage.serialize()); + responsePacket.setBuffer(ownedVehiclesMessage.serialize()); + + return { connectionId, messages: [responsePacket] }; +} - return { connectionId, messages: [responsePacket] }; +export function addVehicle( + personId: number, + vehicleId: number, + brandedPartId: number, + skinId: number, +) { + vehicleList.push({ personId, vehicleId, brandedPartId, skinId }); } diff --git a/packages/transactions/src/_updateCachedVehicle.ts b/packages/transactions/src/_updateCachedVehicle.ts new file mode 100644 index 000000000..e97ca5559 --- /dev/null +++ b/packages/transactions/src/_updateCachedVehicle.ts @@ -0,0 +1,42 @@ +import { getServerLogger, ServerLogger, findSessionByConnectionId, fetchStateFromDatabase, OldServerMessage } from "rusty-motors-shared"; +import { IServerMessage } from "rusty-motors-shared-packets"; +import { GenericReply } from "./GenericReplyMessage.js"; +import { GenericRequestMessage } from "./GenericRequestMessage.js"; +import type { MessageHandlerResult } from "./handlers.js"; + + +export async function _updateCachedVehicle({ + connectionId, packet, log = getServerLogger("transactions._updateCachedVehicle") +}: { + connectionId: string; + packet: IServerMessage; + log?: ServerLogger; +}): Promise { + const updateCachedVehicleMessage = new GenericRequestMessage(); + updateCachedVehicleMessage.deserialize(packet.data); + + log.debug(`Received Message: ${updateCachedVehicleMessage.toString()}`); + + const session = findSessionByConnectionId(fetchStateFromDatabase(), connectionId); + if (!session) { + log.error({ connectionId }, "Session not found"); + throw new Error(`Session not found for connectionId: ${connectionId}`); + } + + const carId = updateCachedVehicleMessage.data.readUInt32LE(0); + + log.debug({ connectionId, carId }, "Received updateCachedVehicle"); + + + const reply = new GenericReply(); + reply.msgNo = 101; + reply.msgReply = 163; + + const responsePacket = new OldServerMessage(); + responsePacket._header.sequence = packet.sequenceNumber; + responsePacket._header.flags = 8; + + responsePacket.setBuffer(reply.serialize()); + + return { connectionId, messages: [responsePacket] }; +} diff --git a/packages/transactions/src/getLobbies.ts b/packages/transactions/src/getLobbies.ts index 67f9d6724..53ccd567e 100644 --- a/packages/transactions/src/getLobbies.ts +++ b/packages/transactions/src/getLobbies.ts @@ -3,6 +3,7 @@ import { EntryFeePurseMessage, PurseEntry } from "./EntryFeePurseMessage.js"; import { LobbyInfo, LobbyMessage } from "./LobbyMessage.js"; import type { MessageHandlerArgs, MessageHandlerResult } from "./handlers.js"; import { getServerLogger } from "rusty-motors-shared"; +import { BytableServerMessage } from "@rustymotors/binary"; const defaultLogger = getServerLogger("handlers/getLobbies"); @@ -20,9 +21,9 @@ async function _getLobbies({ defaultLogger.debug(`[${connectionId}] Sending lobbies response...`); // Create new response packet - const lobbiesResponsePacket = new OldServerMessage(); - lobbiesResponsePacket._header.sequence = packet.sequenceNumber; - lobbiesResponsePacket._header.flags = 8; + const lobbiesResponsePacket = new BytableServerMessage(); + lobbiesResponsePacket.header.sequence = packet.sequenceNumber; + lobbiesResponsePacket.setFlags(8); const lobbyResponse = new LobbyMessage(); lobbyResponse._msgNo = 325; @@ -41,7 +42,7 @@ async function _getLobbies({ defaultLogger.debug(`[${connectionId}] Sending lobbyResponse: ${lobbyResponse.toString()}` ); - lobbiesResponsePacket.setBuffer(lobbyResponse.serialize()); + lobbiesResponsePacket.setBody(lobbyResponse.serialize()); // Handle purse entries const purseEntry = new PurseEntry(); @@ -63,7 +64,10 @@ async function _getLobbies({ return { connectionId, - messages: [lobbiesResponsePacket, perseEntriesResponsePacket], + messages: [ + lobbiesResponsePacket, + perseEntriesResponsePacket + ], }; } /** @@ -76,11 +80,11 @@ export async function getLobbies({ log = defaultLogger, }: MessageHandlerArgs): Promise { const result = await _getLobbies({ connectionId, packet, log }); - log.debug("Dumping Lobbies response packet..."); + log.debug(`[${connectionId}] Returning with ${result.messages.length} messages`); + log.debug(`[${connectionId}] Leaving getLobbies`); result.messages.forEach((msg) => { - log.debug(msg.toString()); + log.debug(`[${connectionId}] Sending response[str]: ${msg.toString()}`); }); - log.debug(result.messages.join().toString()); return { connectionId, messages: result.messages, diff --git a/packages/transactions/src/handlers.ts b/packages/transactions/src/handlers.ts index 215ea273c..1315f107a 100644 --- a/packages/transactions/src/handlers.ts +++ b/packages/transactions/src/handlers.ts @@ -31,6 +31,8 @@ import { login } from "./login.js"; import { trackingPing } from "./trackingPing.js"; import { _buyCarFromDealer } from "./_buyCarFromDealer.js"; import { IServerMessage } from "rusty-motors-shared-packets"; +import { _getCompleteVehicleInfo } from "./_getFullCarInfo.js"; +import { _updateCachedVehicle } from "./_updateCachedVehicle.js"; export interface MessageHandlerArgs { connectionId: string; @@ -65,10 +67,14 @@ export const messageHandlers: MessageHandler[] = [ name: "MC_LOGOUT", handler: _logout, }, - // { - // name: "MC_GET_COMPLETE_VEHICLE_INFO", - // handler: _getFullCarInfo, - // } + { + name: "MC_UPDATE_CACHED_VEHICLE", + handler: _updateCachedVehicle, + }, + { + name: "MC_GET_COMPLETE_VEHICLE_INFO", + handler: _getCompleteVehicleInfo, + }, { name: "MC_GET_LOBBIES", handler: getLobbies, diff --git a/packages/transactions/src/internal.ts b/packages/transactions/src/internal.ts index bd0729548..fbffbbb31 100644 --- a/packages/transactions/src/internal.ts +++ b/packages/transactions/src/internal.ts @@ -33,7 +33,7 @@ import { ServerPacket, type BufferSerializer, } from "rusty-motors-shared-packets"; -import { _MSG_STRING } from "./_MSG_STRING.js"; +import { _MSG_STRING } from "rusty-motors-shared"; @@ -65,7 +65,7 @@ async function processInput({ if (typeof result !== "undefined") { // Turn this into an OldServerMessage for compatibility const packet = new OldServerMessage(); - packet._doDeserialize(inboundMessage.serialize()); + packet.deserialize(inboundMessage.serialize()); try { const responsePackets = await result.handler({ @@ -81,9 +81,8 @@ async function processInput({ } } - throw Error( - `[${connectionId}] Unable to locate handler for message: ${currentMessageNo} (${currentMessageString})`, - ); + log.error({ connectionId, currentMessageNo, currentMessageString}, "No handler found") + throw Error(`No handler found for message ${currentMessageString}(${currentMessageNo})`) } /** @@ -114,8 +113,8 @@ export async function receiveTransactionsData({ const inboundMessage = new ServerPacket(); inboundMessage.deserialize(message.serialize()); - log.debug( - `[${connectionId}] Received message: ${inboundMessage.toHexString()}`, + log.debug({connectionId, seq: inboundMessage.getSequence()}, + `Received message: ${inboundMessage.toHexString()}`, ); let decryptedMessage: ServerPacket; @@ -132,11 +131,6 @@ export async function receiveTransactionsData({ throw Error(`[${connectionId}] Unable to locate encryption settings`); } - // log the old buffer - log.debug( - `[${connectionId}] Inbound buffer: ${inboundMessage.data.toHexString()}`, - ); - decryptedMessage = decryptMessage( encryptionSettings, inboundMessage, @@ -201,7 +195,7 @@ export async function receiveTransactionsData({ // Convert the outbound messages to SerializedBufferOld const outboundMessagesSerialized = outboundMessages.map((message) => { const serialized = new SerializedBufferOld(); - serialized._doDeserialize(message.serialize()); + serialized.deserialize(message.serialize()); return serialized; }); diff --git a/packages/transactions/src/login.ts b/packages/transactions/src/login.ts index c3d5ddd1e..8cf3ca162 100644 --- a/packages/transactions/src/login.ts +++ b/packages/transactions/src/login.ts @@ -66,7 +66,7 @@ export async function login({ const responsePacket = new OldServerMessage(); responsePacket._header.sequence = incomingPacket.getSequence(); - responsePacket._doDeserialize(outgoingPacket.serialize()); + responsePacket.deserialize(outgoingPacket.serialize()); return { connectionId, messages: [responsePacket] }; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7251fd847..9629789e1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,7 +40,7 @@ importers: version: 9.12.0 adminjs: specifier: ^7.8.15 - version: 7.8.15(@tiptap/extension-text-style@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)))(@types/babel__core@7.20.5)(@types/react@19.0.4)(debug@4.4.0) + version: 7.8.15(@types/babel__core@7.20.5)(@types/react@19.0.10)(debug@4.4.0) bcrypt: specifier: ^5.1.1 version: 5.1.1 @@ -122,19 +122,19 @@ importers: version: 9.24.0 '@nx/eslint': specifier: 20.7.2 - version: 20.7.2(@babel/traverse@7.26.5)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(@zkochan/js-yaml@0.0.7)(debug@4.4.0)(eslint@9.24.0(jiti@2.4.2))(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)) + version: 20.7.2(@babel/traverse@7.26.9)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(@zkochan/js-yaml@0.0.7)(debug@4.4.0)(eslint@9.24.0(jiti@2.4.2))(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)) '@nx/js': specifier: 20.7.2 - version: 20.7.2(@babel/traverse@7.26.5)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)) + version: 20.7.2(@babel/traverse@7.26.9)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)) '@nx/node': specifier: 20.7.2 - version: 20.7.2(@babel/traverse@7.26.5)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(@types/node@22.14.0)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(debug@4.4.0)(eslint@9.24.0(jiti@2.4.2))(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0))(ts-node@10.9.2(@swc/core@1.11.18(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.3))(typescript@5.8.3) + version: 20.7.2(@babel/traverse@7.26.9)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(@types/node@22.14.0)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(debug@4.4.0)(eslint@9.24.0(jiti@2.4.2))(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0))(ts-node@10.9.2(@swc/core@1.11.18(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.3))(typescript@5.8.3) '@nx/vite': specifier: 20.7.2 - version: 20.7.2(@babel/traverse@7.26.5)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0))(typescript@5.8.3)(vite@6.2.5(@types/node@22.14.0)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))(vitest@3.1.1) + version: 20.7.2(@babel/traverse@7.26.9)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0))(typescript@5.8.3)(vite@6.2.5(@types/node@22.14.0)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))(vitest@3.1.1) '@nx/web': specifier: 20.7.2 - version: 20.7.2(@babel/traverse@7.26.5)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)) + version: 20.7.2(@babel/traverse@7.26.9)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)) '@sentry/cli': specifier: ^2.43.0 version: 2.43.0 @@ -192,9 +192,6 @@ importers: globals: specifier: ^16.0.0 version: 16.0.0 - husky: - specifier: ^9.1.7 - version: 9.1.7 lint-staged: specifier: ^15.5.0 version: 15.5.0 @@ -247,6 +244,42 @@ importers: specifier: ^2.8.1 version: 2.8.1 + libs/@rustymotors/protocol: + dependencies: + '@mikro-orm/core': + specifier: ^6.4.3 + version: 6.4.3 + rusty-motors-shared: + specifier: workspace:1.0.0-next.0 + version: link:../../../packages/shared + rusty-motors-shared-packets: + specifier: workspace:1.0.0-next.0 + version: link:../../../packages/shared-packets + tslib: + specifier: ^2.8.1 + version: 2.8.1 + + libs/@rustymotors/roomserver: + dependencies: + '@mikro-orm/core': + specifier: ^6.4.7 + version: 6.4.7 + '@rustymotors/binary': + specifier: workspace:^ + version: link:../binary + rusty-motors-database: + specifier: workspace:1.0.0-next.0 + version: link:../../../packages/database + rusty-motors-shared: + specifier: workspace:1.0.0-next.0 + version: link:../../../packages/shared + rusty-motors-shared-packets: + specifier: workspace:1.0.0-next.0 + version: link:../../../packages/shared-packets + tslib: + specifier: ^2.8.1 + version: 2.8.1 + packages/cli: dependencies: '@sentry/profiling-node': @@ -274,6 +307,12 @@ importers: sequelize: specifier: ^6.37.7 version: 6.37.7(pg-hstore@2.3.4)(pg@8.14.1) + slonik: + specifier: ^46.4.0 + version: 46.4.0(zod@3.24.2) + zod: + specifier: ^3.24.2 + version: 3.24.2 devDependencies: '@vitest/coverage-v8': specifier: 3.1.1 @@ -290,6 +329,12 @@ importers: '@rustymotors/binary': specifier: workspace:^ version: link:../../libs/@rustymotors/binary + '@rustymotors/protocol': + specifier: workspace:^ + version: link:../../libs/@rustymotors/protocol + '@rustymotors/roomserver': + specifier: workspace:^ + version: link:../../libs/@rustymotors/roomserver fastify: specifier: ^5.2.2 version: 5.2.2 @@ -331,22 +376,9 @@ importers: fastify: specifier: ^5.2.2 version: 5.2.2 - devDependencies: - '@vitest/coverage-v8': - specifier: 3.1.1 - version: 3.1.1(vitest@3.1.1) - vitest: - specifier: ^3.1.1 - version: 3.1.1(@types/debug@4.1.12)(@types/node@22.14.0)(@vitest/ui@3.1.1)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) - - packages/nps: - dependencies: - '@sentry/profiling-node': - specifier: 9.12.0 - version: 9.12.0 - short-unique-id: - specifier: ^5.2.2 - version: 5.2.2 + rusty-motors-personas: + specifier: workspace:^ + version: link:../persona devDependencies: '@vitest/coverage-v8': specifier: 3.1.1 @@ -454,24 +486,20 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@babel/code-frame@7.25.7': - resolution: {integrity: sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==} - engines: {node: '>=6.9.0'} - '@babel/code-frame@7.26.2': resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.26.5': - resolution: {integrity: sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==} + '@babel/compat-data@7.26.8': + resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} engines: {node: '>=6.9.0'} - '@babel/core@7.26.0': - resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} + '@babel/core@7.26.9': + resolution: {integrity: sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==} engines: {node: '>=6.9.0'} - '@babel/generator@7.26.5': - resolution: {integrity: sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==} + '@babel/generator@7.26.9': + resolution: {integrity: sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==} engines: {node: '>=6.9.0'} '@babel/helper-annotate-as-pure@7.25.9': @@ -482,8 +510,8 @@ packages: resolution: {integrity: sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==} engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.25.9': - resolution: {integrity: sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==} + '@babel/helper-create-class-features-plugin@7.26.9': + resolution: {integrity: sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -541,10 +569,6 @@ packages: resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.25.7': - resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==} - engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.25.9': resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} engines: {node: '>=6.9.0'} @@ -557,16 +581,12 @@ packages: resolution: {integrity: sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.26.0': - resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} - engines: {node: '>=6.9.0'} - - '@babel/highlight@7.25.7': - resolution: {integrity: sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==} + '@babel/helpers@7.26.9': + resolution: {integrity: sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==} engines: {node: '>=6.9.0'} - '@babel/parser@7.26.5': - resolution: {integrity: sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==} + '@babel/parser@7.26.9': + resolution: {integrity: sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==} engines: {node: '>=6.0.0'} hasBin: true @@ -727,8 +747,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-async-generator-functions@7.25.9': - resolution: {integrity: sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==} + '@babel/plugin-transform-async-generator-functions@7.26.8': + resolution: {integrity: sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -817,8 +837,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-for-of@7.25.9': - resolution: {integrity: sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==} + '@babel/plugin-transform-for-of@7.26.9': + resolution: {integrity: sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -889,8 +909,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-nullish-coalescing-operator@7.26.5': - resolution: {integrity: sha512-OHqczNm4NTQlW1ghrVY43FPoiRzbmzNVbcgVnMKZN/RQYezHUSdjACjaX50CD3B7UIAjv39+MlsrVDb3v741FA==} + '@babel/plugin-transform-nullish-coalescing-operator@7.26.6': + resolution: {integrity: sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -991,8 +1011,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-runtime@7.25.9': - resolution: {integrity: sha512-nZp7GlEl+yULJrClz0SwHPqir3lc0zsPrDHQUcxGspSL7AKrexNSEfTbfqnDNJUO13bgKyfuOLMF8Xqtu8j3YQ==} + '@babel/plugin-transform-runtime@7.26.9': + resolution: {integrity: sha512-Jf+8y9wXQbbxvVYTM8gO5oEF2POdNji0NMltEkG7FtmzD9PVz7/lxpqSdTvwsjTMU5HIHuDVNf2SOxLkWi+wPQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1015,20 +1035,20 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-template-literals@7.25.9': - resolution: {integrity: sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==} + '@babel/plugin-transform-template-literals@7.26.8': + resolution: {integrity: sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typeof-symbol@7.25.9': - resolution: {integrity: sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==} + '@babel/plugin-transform-typeof-symbol@7.26.7': + resolution: {integrity: sha512-jfoTXXZTgGg36BmhqT3cAYK5qkmqvJpvNrPhaK/52Vgjhw4Rq29s9UqpWWV0D6yuRmgiFH/BUVlkl96zJWqnaw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typescript@7.26.5': - resolution: {integrity: sha512-GJhPO0y8SD5EYVCy2Zr+9dSZcEgaSmq5BLR0Oc25TOEhC+ba49vUAGZFjy8v79z9E1mdldq4x9d1xgh4L1d5dQ==} + '@babel/plugin-transform-typescript@7.26.8': + resolution: {integrity: sha512-bME5J9AC8ChwA7aEPJ6zym3w7aObZULHhbNLU0bKUhKsAkylkzUdq+0kdymh9rzi8nlNFl2bmldFBCKNJBUpuw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1057,8 +1077,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/preset-env@7.26.0': - resolution: {integrity: sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==} + '@babel/preset-env@7.26.9': + resolution: {integrity: sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1086,20 +1106,20 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/runtime@7.26.0': - resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} + '@babel/runtime@7.26.9': + resolution: {integrity: sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==} engines: {node: '>=6.9.0'} - '@babel/template@7.25.9': - resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} + '@babel/template@7.26.9': + resolution: {integrity: sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.26.5': - resolution: {integrity: sha512-rkOSPOw+AXbgtwUga3U4u8RpoK9FEFWBNAlTpcnkLFjL5CT+oyHNuUUC/xx6XefEJ16r38r8Bc/lfp6rYuHeJQ==} + '@babel/traverse@7.26.9': + resolution: {integrity: sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==} engines: {node: '>=6.9.0'} - '@babel/types@7.26.5': - resolution: {integrity: sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==} + '@babel/types@7.26.9': + resolution: {integrity: sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==} engines: {node: '>=6.9.0'} '@bcoe/v8-coverage@0.2.3': @@ -1312,8 +1332,8 @@ packages: resolution: {integrity: sha512-FIjEB/s3TSQBYnYA64GPkXJrOR6w5J52SSnl6gSoq1tp+4r9zLjaAsf65AgDv5emA4ypm90gVWv1XX0/bfHA/A==} hasBin: true - '@ecies/ciphers@0.2.1': - resolution: {integrity: sha512-ezMihhjW24VNK/2qQR7lH8xCQY24nk0XHF/kwJ1OuiiY5iEwQXOcKVSy47fSoHPRG8gVGXcK5SgtONDk5xMwtQ==} + '@ecies/ciphers@0.2.2': + resolution: {integrity: sha512-ylfGR7PyTd+Rm2PqQowG08BCKA22QuX8NzrL+LxAAvazN10DMwdJ2fWwAzRj05FI/M8vNFGm3cv9Wq/GFWCBLg==} engines: {bun: '>=1', deno: '>=2', node: '>=16'} peerDependencies: '@noble/ciphers': ^1.0.0 @@ -1798,8 +1818,8 @@ packages: prop-types: ^15.0.0 react: '>=0.14.0' - '@inquirer/figures@1.0.7': - resolution: {integrity: sha512-m+Trk77mp54Zma6xLkLuY+mvanPxlE4A7yNKs2HBiyZ4UkVs28Mv5c/pgWrHeInx+USHeX/WEPzjrWrcJiQgjw==} + '@inquirer/figures@1.0.10': + resolution: {integrity: sha512-Ey6176gZmeqZuY/W/nZiUyvmb1/qInjcpiZjXWi6nON+nxJpD1bxtSoBxNliGISae32n6OwbY+TSXPZ1CfS4bw==} engines: {node: '>=18'} '@isaacs/cliui@8.0.2': @@ -1871,8 +1891,8 @@ packages: resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - '@jridgewell/gen-mapping@0.3.5': - resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} '@jridgewell/resolve-uri@3.1.2': @@ -1904,6 +1924,14 @@ packages: resolution: {integrity: sha512-TzJJCFZCdyrVPt/K3UHdao8Iyj4xJSj2r0tYUCY4zNKwuUw6K3RlEYcWGUf85FWIAZJPpYqbv83WTb/H9OiyyQ==} engines: {node: '>= 18.12.0'} + '@mikro-orm/core@6.4.3': + resolution: {integrity: sha512-UTaqKs1bomYtGmEEZ8sNBOmW2OqT5NcMh+pBV2iJ6WLM5MuiIEuNhDMuvvPE5gNEwUzc1HyRhUV87bRDhDIGRg==} + engines: {node: '>= 18.12.0'} + + '@mikro-orm/core@6.4.7': + resolution: {integrity: sha512-ZePm7IRpW6/tGC6axCezI1/5YA3+MiDsbEj5KHgXDIxzHnftVfL3nbYlPlr0pW/UQSL5QcRqXxHIQz4P2OlFhg==} + engines: {node: '>= 18.12.0'} + '@mikro-orm/knex@6.4.12': resolution: {integrity: sha512-KMocJ4fdAbf52I/K25eV+dZDWXdVJpiIaBuIRt04m+SiJ7HZPP0OTDt/mexX3WHWW2m/d1byDNIZecjmV0eRSA==} engines: {node: '>= 18.12.0'} @@ -1938,16 +1966,16 @@ packages: '@napi-rs/wasm-runtime@0.2.7': resolution: {integrity: sha512-5yximcFK5FNompXfJFoWanu5l8v1hNGqNHh9du1xETp9HWk/B/PzvchX55WYOPaIeNglG8++68AAiauBAtbnzw==} - '@noble/ciphers@1.0.0': - resolution: {integrity: sha512-wH5EHOmLi0rEazphPbecAzmjd12I6/Yv/SiHdkA9LSycsQk7RuuTp7am5/o62qYr0RScE7Pc9icXGBbsr6cesA==} + '@noble/ciphers@1.2.1': + resolution: {integrity: sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==} engines: {node: ^14.21.3 || >=16} - '@noble/curves@1.6.0': - resolution: {integrity: sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==} + '@noble/curves@1.8.1': + resolution: {integrity: sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==} engines: {node: ^14.21.3 || >=16} - '@noble/hashes@1.5.0': - resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==} + '@noble/hashes@1.7.1': + resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==} engines: {node: ^14.21.3 || >=16} '@nodelib/fs.scandir@2.1.5': @@ -2343,8 +2371,8 @@ packages: '@remirror/core-constants@2.0.2': resolution: {integrity: sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==} - '@remix-run/router@1.21.0': - resolution: {integrity: sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==} + '@remix-run/router@1.23.0': + resolution: {integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==} engines: {node: '>=14.0.0'} '@rollup/plugin-babel@6.0.4': @@ -2414,183 +2442,103 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.24.0': - resolution: {integrity: sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm-eabi@4.34.3': - resolution: {integrity: sha512-8kq/NjMKkMTGKMPldWihncOl62kgnLYk7cW+/4NCUWfS70/wz4+gQ7rMxMMpZ3dIOP/xw7wKNzIuUnN/H2GfUg==} + '@rollup/rollup-android-arm-eabi@4.34.8': + resolution: {integrity: sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.24.0': - resolution: {integrity: sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-android-arm64@4.34.3': - resolution: {integrity: sha512-1PqMHiuRochQ6++SDI7SaRDWJKr/NgAlezBi5nOne6Da6IWJo3hK0TdECBDwd92IUDPG4j/bZmWuwOnomNT8wA==} + '@rollup/rollup-android-arm64@4.34.8': + resolution: {integrity: sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.24.0': - resolution: {integrity: sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-arm64@4.34.3': - resolution: {integrity: sha512-fqbrykX4mGV3DlCDXhF4OaMGcchd2tmLYxVt3On5oOZWVDFfdEoYAV2alzNChl8OzNaeMAGqm1f7gk7eIw/uDg==} + '@rollup/rollup-darwin-arm64@4.34.8': + resolution: {integrity: sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.24.0': - resolution: {integrity: sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.34.3': - resolution: {integrity: sha512-8Wxrx/KRvMsTyLTbdrMXcVKfpW51cCNW8x7iQD72xSEbjvhCY3b+w83Bea3nQfysTMR7K28esc+ZFITThXm+1w==} + '@rollup/rollup-darwin-x64@4.34.8': + resolution: {integrity: sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.34.3': - resolution: {integrity: sha512-lpBmV2qSiELh+ATQPTjQczt5hvbTLsE0c43Rx4bGxN2VpnAZWy77we7OO62LyOSZNY7CzjMoceRPc+Lt4e9J6A==} + '@rollup/rollup-freebsd-arm64@4.34.8': + resolution: {integrity: sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.34.3': - resolution: {integrity: sha512-sNPvBIXpgaYcI6mAeH13GZMXFrrw5mdZVI1M9YQPRG2LpjwL8DSxSIflZoh/B5NEuOi53kxsR/S2GKozK1vDXA==} + '@rollup/rollup-freebsd-x64@4.34.8': + resolution: {integrity: sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.24.0': - resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-gnueabihf@4.34.3': - resolution: {integrity: sha512-MW6N3AoC61OfE1VgnN5O1OW0gt8VTbhx9s/ZEPLBM11wEdHjeilPzOxVmmsrx5YmejpGPvez8QwGGvMU+pGxpw==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.24.0': - resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==} + '@rollup/rollup-linux-arm-gnueabihf@4.34.8': + resolution: {integrity: sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.34.3': - resolution: {integrity: sha512-2SQkhr5xvatYq0/+H6qyW0zvrQz9LM4lxGkpWURLoQX5+yP8MsERh4uWmxFohOvwCP6l/+wgiHZ1qVwLDc7Qmw==} + '@rollup/rollup-linux-arm-musleabihf@4.34.8': + resolution: {integrity: sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.24.0': - resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.34.3': - resolution: {integrity: sha512-R3JLYt8YoRwKI5shJsovLpcR6pwIMui/MGG/MmxZ1DYI3iRSKI4qcYrvYgDf4Ss2oCR3RL3F3dYK7uAGQgMIuQ==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-musl@4.24.0': - resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==} + '@rollup/rollup-linux-arm64-gnu@4.34.8': + resolution: {integrity: sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.34.3': - resolution: {integrity: sha512-4XQhG8v/t3S7Rxs7rmFUuM6j09hVrTArzONS3fUZ6oBRSN/ps9IPQjVhp62P0W3KhqJdQADo/MRlYRMdgxr/3w==} + '@rollup/rollup-linux-arm64-musl@4.34.8': + resolution: {integrity: sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.34.3': - resolution: {integrity: sha512-QlW1jCUZ1LHUIYCAK2FciVw1ptHsxzApYVi05q7bz2A8oNE8QxQ85NhM4arLxkAlcnS42t4avJbSfzSQwbIaKg==} + '@rollup/rollup-linux-loongarch64-gnu@4.34.8': + resolution: {integrity: sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': - resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-powerpc64le-gnu@4.34.3': - resolution: {integrity: sha512-kMbLToizVeCcN69+nnm20Dh0hrRIAjgaaL+Wh0gWZcNt8e542d2FUGtsyuNsHVNNF3gqTJrpzUGIdwMGLEUM7g==} + '@rollup/rollup-linux-powerpc64le-gnu@4.34.8': + resolution: {integrity: sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.24.0': - resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.34.3': - resolution: {integrity: sha512-YgD0DnZ3CHtvXRH8rzjVSxwI0kMTr0RQt3o1N92RwxGdx7YejzbBO0ELlSU48DP96u1gYYVWfUhDRyaGNqJqJg==} + '@rollup/rollup-linux-riscv64-gnu@4.34.8': + resolution: {integrity: sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.24.0': - resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.34.3': - resolution: {integrity: sha512-dIOoOz8altjp6UjAi3U9EW99s8nta4gzi52FeI45GlPyrUH4QixUoBMH9VsVjt+9A2RiZBWyjYNHlJ/HmJOBCQ==} + '@rollup/rollup-linux-s390x-gnu@4.34.8': + resolution: {integrity: sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.24.0': - resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.34.3': - resolution: {integrity: sha512-lOyG3aF4FTKrhpzXfMmBXgeKUUXdAWmP2zSNf8HTAXPqZay6QYT26l64hVizBjq+hJx3pl0DTEyvPi9sTA6VGA==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-musl@4.24.0': - resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==} + '@rollup/rollup-linux-x64-gnu@4.34.8': + resolution: {integrity: sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.34.3': - resolution: {integrity: sha512-usztyYLu2i+mYzzOjqHZTaRXbUOqw3P6laNUh1zcqxbPH1P2Tz/QdJJCQSnGxCtsRQeuU2bCyraGMtMumC46rw==} + '@rollup/rollup-linux-x64-musl@4.34.8': + resolution: {integrity: sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.24.0': - resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-arm64-msvc@4.34.3': - resolution: {integrity: sha512-ojFOKaz/ZyalIrizdBq2vyc2f0kFbJahEznfZlxdB6pF9Do6++i1zS5Gy6QLf8D7/S57MHrmBLur6AeRYeQXSA==} + '@rollup/rollup-win32-arm64-msvc@4.34.8': + resolution: {integrity: sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.24.0': - resolution: {integrity: sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==} + '@rollup/rollup-win32-ia32-msvc@4.34.8': + resolution: {integrity: sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.34.3': - resolution: {integrity: sha512-K/V97GMbNa+Da9mGcZqmSl+DlJmWfHXTuI9V8oB2evGsQUtszCl67+OxWjBKpeOnYwox9Jpmt/J6VhpeRCYqow==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.24.0': - resolution: {integrity: sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==} + '@rollup/rollup-win32-x64-msvc@4.34.8': + resolution: {integrity: sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.34.3': - resolution: {integrity: sha512-CUypcYP31Q8O04myV6NKGzk9GVXslO5EJNfmARNSzLF2A+5rmZUlDJ4et6eoJaZgBT9wrC2p4JZH04Vkic8HdQ==} - cpu: [x64] - os: [win32] - - '@sentry-internal/node-cpu-profiler@2.0.0': - resolution: {integrity: sha512-0pZId+HY/AbNs1+CoCi8wogBWTrRv+DYeOgbevhekzMr5HYsA6PRY21NtHBXMbu0WcswFwaveDKR+sOW1EDHAA==} + '@sentry-internal/node-cpu-profiler@2.1.0': + resolution: {integrity: sha512-/gPj8ARZ8Jw8gCQWToCiUyLoOxBDP8wuFNx07mAXegYiDa4NcIvo37ZzDWaTG+wjwa/LvCpHxHff6pejt4KOKg==} engines: {node: '>=18'} '@sentry/cli-darwin@2.43.0': @@ -2678,6 +2626,42 @@ packages: '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@slonik/driver@46.4.0': + resolution: {integrity: sha512-av8JnxmWzzAauvS6/2NxrYfrVxzopT/a8Iah1vS9OkNI55FaQxal+0QoSixOUqFN0/TLCv6UFZAI7xcUh8Cmzw==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3 + + '@slonik/errors@46.4.0': + resolution: {integrity: sha512-jwNJCGyOzOQtLsqH0jHw2vrtdsmNBK1rTRoofBLBCCDWZ1JfUvxSblr7FCiA5xtYMdNSd5FIMEUR/tHppu34IQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3 + + '@slonik/pg-driver@46.4.0': + resolution: {integrity: sha512-jamxmj7CIS22q0Ptr/U6XUV/W/x6jig2TwyqHYGmnmO22UQtD9oPzjDoJ/QgibvSBGdNgoXGLS2+QH0cL8c07A==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3 + + '@slonik/sql-tag@46.4.0': + resolution: {integrity: sha512-PINbDdPplmdBhTTPQDDrUCUEtiT4b4H/889WKy1cL8yz81Xd4RKGrejYZIGMCAYYFpgiDAQq4TxfkIGOTRjUUQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3 + + '@slonik/types@46.4.0': + resolution: {integrity: sha512-/7N4s0bgsWahl670Ej0n/TZtguAdAJxVziBO/XoVek0cn7Ovi9LzD9YJgmeUIqY/cc/WL0JKTOZf9QL24TQQFQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3 + + '@slonik/utilities@46.4.0': + resolution: {integrity: sha512-ZPLM5tR1brZBbznrftm+dugL5udtpwtx1bBvOyhyP1rsZywpSOAa4T35fG8grMkHc4XX3MAB2qtyqpO+iZ+qyA==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3 + '@styled-system/background@5.1.2': resolution: {integrity: sha512-jtwH2C/U6ssuGSvwTN3ri/IyjdHb8W9X/g8Y0JLcrH02G+BW3OS8kZdHphF1/YyRklnrKrBT2ngwGUK6aqqV3A==} @@ -2822,13 +2806,13 @@ packages: peerDependencies: '@tiptap/pm': ^2.0.0 - '@tiptap/extension-blockquote@2.8.0': - resolution: {integrity: sha512-m3CKrOIvV7fY1Ak2gYf5LkKiz6AHxHpg6wxfVaJvdBqXgLyVtHo552N+A4oSHOSRbB4AG9EBQ2NeBM8cdEQ4MA==} + '@tiptap/extension-blockquote@2.11.5': + resolution: {integrity: sha512-MZfcRIzKRD8/J1hkt/eYv49060GTL6qGR3NY/oTDuw2wYzbQXXLEbjk8hxAtjwNn7G+pWQv3L+PKFzZDxibLuA==} peerDependencies: '@tiptap/core': ^2.7.0 - '@tiptap/extension-bold@2.8.0': - resolution: {integrity: sha512-U1YkZBxDkSLNvPNiqxB5g42IeJHr27C7zDb/yGQN2xL4UBeg4O9xVhCFfe32f6tLwivSL0dar4ScElpaCJuqow==} + '@tiptap/extension-bold@2.11.5': + resolution: {integrity: sha512-OAq03MHEbl7MtYCUzGuwb0VpOPnM0k5ekMbEaRILFU5ZC7cEAQ36XmPIw1dQayrcuE8GZL35BKub2qtRxyC9iA==} peerDependencies: '@tiptap/core': ^2.7.0 @@ -2838,12 +2822,10 @@ packages: '@tiptap/core': ^2.0.0 '@tiptap/pm': ^2.0.0 - '@tiptap/extension-bullet-list@2.8.0': - resolution: {integrity: sha512-H4O2X0ozbc/ce9/XF1H98sqWVUdtt7jzy7hMBunwmY8ZxI4dHtcRkeg81CZbpKTqOqRrMCLWjE3M2tgiDXrDkA==} + '@tiptap/extension-bullet-list@2.11.5': + resolution: {integrity: sha512-VXwHlX6A/T6FAspnyjbKDO0TQ+oetXuat6RY1/JxbXphH42nLuBaGWJ6pgy6xMl6XY8/9oPkTNrfJw/8/eeRwA==} peerDependencies: '@tiptap/core': ^2.7.0 - '@tiptap/extension-list-item': ^2.7.0 - '@tiptap/extension-text-style': ^2.7.0 '@tiptap/extension-character-count@2.1.13': resolution: {integrity: sha512-FxPxS/Uqd4MgndInxXOcgNd225541Nsk1lT5e2uNTSNiQnG7dj7cSFG5KXGcSGLpGGt6e/E28WR6KLV+0/u+WA==} @@ -2851,8 +2833,8 @@ packages: '@tiptap/core': ^2.0.0 '@tiptap/pm': ^2.0.0 - '@tiptap/extension-code-block@2.8.0': - resolution: {integrity: sha512-POuA5Igx+Dto0DTazoBFAQTj/M/FCdkqRVD9Uhsxhv49swPyANTJRr05vgbgtHB+NDDsZfCawVh7pI0IAD/O0w==} + '@tiptap/extension-code-block@2.11.5': + resolution: {integrity: sha512-ksxMMvqLDlC+ftcQLynqZMdlJT1iHYZorXsXw/n+wuRd7YElkRkd6YWUX/Pq/njFY6lDjKiqFLEXBJB8nrzzBA==} peerDependencies: '@tiptap/core': ^2.7.0 '@tiptap/pm': ^2.7.0 @@ -2867,8 +2849,8 @@ packages: peerDependencies: '@tiptap/core': ^2.0.0 - '@tiptap/extension-dropcursor@2.8.0': - resolution: {integrity: sha512-rAFvx44YuT6dtS1c+ALw0ROAGI16l5L1HxquL4hR1gtxDcTieST5xhw5bkshXlmrlfotZXPrhokzqA7qjhZtJw==} + '@tiptap/extension-dropcursor@2.11.5': + resolution: {integrity: sha512-uIN7L3FU0904ec7FFFbndO7RQE/yiON4VzAMhNn587LFMyWO8US139HXIL4O8dpZeYwYL3d1FnDTflZl6CwLlg==} peerDependencies: '@tiptap/core': ^2.7.0 '@tiptap/pm': ^2.7.0 @@ -2879,14 +2861,14 @@ packages: '@tiptap/core': ^2.0.0 '@tiptap/pm': ^2.0.0 - '@tiptap/extension-gapcursor@2.8.0': - resolution: {integrity: sha512-Be1LWCmvteQInOnNVN+HTqc1XWsj1bCl+Q7et8qqNjtGtTaCbdCp8ppcH1SKJxNTM/RLUtPyJ8FDgOTj51ixCA==} + '@tiptap/extension-gapcursor@2.11.5': + resolution: {integrity: sha512-kcWa+Xq9cb6lBdiICvLReuDtz/rLjFKHWpW3jTTF3FiP3wx4H8Rs6bzVtty7uOVTfwupxZRiKICAMEU6iT0xrQ==} peerDependencies: '@tiptap/core': ^2.7.0 '@tiptap/pm': ^2.7.0 - '@tiptap/extension-hard-break@2.8.0': - resolution: {integrity: sha512-vqiIfviNiCmy/pJTHuDSCAGL2O4QDEdDmAvGJu8oRmElUrnlg8DbJUfKvn6DWQHNSQwRb+LDrwWlzAYj1K9u6A==} + '@tiptap/extension-hard-break@2.11.5': + resolution: {integrity: sha512-q9doeN+Yg9F5QNTG8pZGYfNye3tmntOwch683v0CCVCI4ldKaLZ0jG3NbBTq+mosHYdgOH2rNbIORlRRsQ+iYQ==} peerDependencies: '@tiptap/core': ^2.7.0 @@ -2895,14 +2877,14 @@ packages: peerDependencies: '@tiptap/core': ^2.0.0 - '@tiptap/extension-history@2.8.0': - resolution: {integrity: sha512-u5YS0J5Egsxt8TUWMMAC3QhPZaak+IzQeyHch4gtqxftx96tprItY7AD/A3pGDF2uCSnN+SZrk6yVexm6EncDw==} + '@tiptap/extension-history@2.11.5': + resolution: {integrity: sha512-b+wOS33Dz1azw6F1i9LFTEIJ/gUui0Jwz5ZvmVDpL2ZHBhq1Ui0/spTT+tuZOXq7Y/uCbKL8Liu4WoedIvhboQ==} peerDependencies: '@tiptap/core': ^2.7.0 '@tiptap/pm': ^2.7.0 - '@tiptap/extension-horizontal-rule@2.8.0': - resolution: {integrity: sha512-Sn/MI8WVFBoIYSIHA9NJryJIyCEzZdRysau8pC5TFnfifre0QV1ksPz2bgF+DyCD69ozQiRdBBHDEwKe47ZbfQ==} + '@tiptap/extension-horizontal-rule@2.11.5': + resolution: {integrity: sha512-3up2r1Du8/5/4ZYzTC0DjTwhgPI3dn8jhOCLu73m5F3OGvK/9whcXoeWoX103hYMnGDxBlfOje71yQuN35FL4A==} peerDependencies: '@tiptap/core': ^2.7.0 '@tiptap/pm': ^2.7.0 @@ -2912,8 +2894,8 @@ packages: peerDependencies: '@tiptap/core': ^2.0.0 - '@tiptap/extension-italic@2.8.0': - resolution: {integrity: sha512-PwwSE2LTYiHI47NJnsfhBmPiLE8IXZYqaSoNPU6flPrk1KxEzqvRI1joKZBmD9wuqzmHJ93VFIeZcC+kfwi8ZA==} + '@tiptap/extension-italic@2.11.5': + resolution: {integrity: sha512-9VGfb2/LfPhQ6TjzDwuYLRvw0A6VGbaIp3F+5Mql8XVdTBHb2+rhELbyhNGiGVR78CaB/EiKb6dO9xu/tBWSYA==} peerDependencies: '@tiptap/core': ^2.7.0 @@ -2923,25 +2905,23 @@ packages: '@tiptap/core': ^2.0.0 '@tiptap/pm': ^2.0.0 - '@tiptap/extension-list-item@2.8.0': - resolution: {integrity: sha512-o7OGymGxB0B9x3x2prp3KBDYFuBYGc5sW69O672jk8G52DqhzzndgPnkk0qUn8nXAUKuDGbJmpmHVA2kagqnRg==} + '@tiptap/extension-list-item@2.11.5': + resolution: {integrity: sha512-Mp5RD/pbkfW1vdc6xMVxXYcta73FOwLmblQlFNn/l/E5/X1DUSA4iGhgDDH4EWO3swbs03x2f7Zka/Xoj3+WLg==} peerDependencies: '@tiptap/core': ^2.7.0 - '@tiptap/extension-ordered-list@2.8.0': - resolution: {integrity: sha512-sCvNbcTS1+5QTTXwUPFa10vf5I1pr8sGcOTIh0G+a5ZkS5+6FxT12k7VLzPt39QyNbOi+77U2o4Xr4XyaEkfSg==} + '@tiptap/extension-ordered-list@2.11.5': + resolution: {integrity: sha512-Cu8KwruBNWAaEfshRQR0yOSaUKAeEwxW7UgbvF9cN/zZuKgK5uZosPCPTehIFCcRe+TBpRtZQh+06f/gNYpYYg==} peerDependencies: '@tiptap/core': ^2.7.0 - '@tiptap/extension-list-item': ^2.7.0 - '@tiptap/extension-text-style': ^2.7.0 - '@tiptap/extension-paragraph@2.8.0': - resolution: {integrity: sha512-XgxxNNbuBF48rAGwv7/s6as92/xjm/lTZIGTq9aG13ClUKFtgdel7C33SpUCcxg3cO2WkEyllXVyKUiauFZw/A==} + '@tiptap/extension-paragraph@2.11.5': + resolution: {integrity: sha512-YFBWeg7xu/sBnsDIF/+nh9Arf7R0h07VZMd0id5Ydd2Qe3c1uIZwXxeINVtH0SZozuPIQFAT8ICe9M0RxmE+TA==} peerDependencies: '@tiptap/core': ^2.7.0 - '@tiptap/extension-strike@2.8.0': - resolution: {integrity: sha512-ezkDiXxQ3ME/dDMMM7tAMkKRi6UWw7tIu+Mx7Os0z8HCGpVBk1gFhLlhEd8I5rJaPZr4tK1wtSehMA9bscFGQw==} + '@tiptap/extension-strike@2.11.5': + resolution: {integrity: sha512-PVfUiCqrjvsLpbIoVlegSY8RlkR64F1Rr2RYmiybQfGbg+AkSZXDeO0eIrc03//4gua7D9DfIozHmAKv1KN3ow==} peerDependencies: '@tiptap/core': ^2.7.0 @@ -2971,11 +2951,6 @@ packages: peerDependencies: '@tiptap/core': ^2.0.0 - '@tiptap/extension-text-style@2.8.0': - resolution: {integrity: sha512-jJp0vcZ2Ty7RvIL0VU6dm1y+fTfXq1lN2GwtYzYM0ueFuESa+Qo8ticYOImyWZ3wGJGVrjn7OV9r0ReW0/NYkQ==} - peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/extension-text@2.1.13': resolution: {integrity: sha512-zzsTTvu5U67a8WjImi6DrmpX2Q/onLSaj+LRWPh36A1Pz2WaxW5asZgaS+xWCnR+UrozlCALWa01r7uv69jq0w==} peerDependencies: @@ -3000,8 +2975,8 @@ packages: '@tiptap/starter-kit@2.1.13': resolution: {integrity: sha512-ph/mUR/OwPtPkZ5rNHINxubpABn8fHnvJSdhXFrY/q6SKoaO11NZXgegRaiG4aL7O6Sz4LsZVw6Sm0Ae+GJmrg==} - '@ts-morph/common@0.26.0': - resolution: {integrity: sha512-/RmKAtctStXqM5nECMQ46duT74Hoig/DBzhWXGHcodlDNrgRbsbwwHqSKFNbca6z9Xt/CUWMeXOsC9QEN1+rqw==} + '@ts-morph/common@0.26.1': + resolution: {integrity: sha512-Sn28TGl/4cFpcM+jwsH1wLncYq3FtN/BIpem+HOygfBWPT5pAeS5dB4VFVzV8FbnOKHpDLZmvAl4AjPEev5idA==} '@tsconfig/node-lts@22.0.1': resolution: {integrity: sha512-BwlbLiYurZKrj+Pa6etSE1jXmr3VEDdgJto1jEYKcpBVwZZSWVkCPyFEFYbHdIOaFMlSTtV206DYPlT109aqug==} @@ -3045,8 +3020,8 @@ packages: '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} - '@types/conventional-commits-parser@5.0.0': - resolution: {integrity: sha512-loB369iXNmAZglwWATL+WRe+CRMmmBPtpolYzIebFaX4YA3x+BEfLqhUAV9WanycKI3TG1IMr5bMJDajDKLlUQ==} + '@types/conventional-commits-parser@5.0.1': + resolution: {integrity: sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==} '@types/cross-spawn@6.0.6': resolution: {integrity: sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==} @@ -3096,8 +3071,8 @@ packages: '@types/mysql@2.15.26': resolution: {integrity: sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==} - '@types/node@22.10.10': - resolution: {integrity: sha512-X47y/mPNzxviAGY5TcYPtYL8JsY3kAq2n8fMmKoRCxq/c4v4pyGNCzM2R6+M5/umG4ZfHuT+sgqDYqWc9rJ6ww==} + '@types/node@22.13.5': + resolution: {integrity: sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==} '@types/node@22.14.0': resolution: {integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==} @@ -3122,8 +3097,8 @@ packages: peerDependencies: '@types/react': '*' - '@types/react@19.0.4': - resolution: {integrity: sha512-3O4QisJDYr1uTUMZHA2YswiQZRq+Pd8D+GdVFYikTutYsTz+QZgWkAPnP7rx9txoI6EXKcPiluMqWPFV3tT9Wg==} + '@types/react@19.0.10': + resolution: {integrity: sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==} '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -3317,11 +3292,6 @@ packages: resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} engines: {node: '>=0.4.0'} - acorn@8.12.1: - resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} - engines: {node: '>=0.4.0'} - hasBin: true - acorn@8.14.0: resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} engines: {node: '>=0.4.0'} @@ -3425,8 +3395,8 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - array-buffer-byte-length@1.0.1: - resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} array-ify@1.0.0: @@ -3440,8 +3410,8 @@ packages: resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} engines: {node: '>=0.10.0'} - assert-never@1.3.0: - resolution: {integrity: sha512-9Z3vxQ+berkL/JJo0dK+EY3Lp0s3NtSnP3VCLsh5HDcZPrh0M+KQRK5sWhUeyPPH+/RCxZqOxLMR+YC6vlviEQ==} + assert-never@1.4.0: + resolution: {integrity: sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==} assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} @@ -3467,8 +3437,8 @@ packages: avvio@9.0.0: resolution: {integrity: sha512-UbYrOXgE/I+knFG+3kJr9AgC7uNo8DG+FGGODpH9Bj1O1kL/QDjBXnTem9leD3VdQKtaHjV3O85DQ7hHh4IIHw==} - axios@1.7.9: - resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} + axios@1.8.1: + resolution: {integrity: sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==} axios@1.8.3: resolution: {integrity: sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==} @@ -3506,6 +3476,11 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + babel-plugin-polyfill-corejs3@0.11.1: + resolution: {integrity: sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + babel-plugin-polyfill-regenerator@0.6.3: resolution: {integrity: sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==} peerDependencies: @@ -3587,8 +3562,16 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - call-bind@1.0.7: - resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.3: + resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} engines: {node: '>= 0.4'} callsites@3.1.0: @@ -3614,8 +3597,8 @@ packages: camelize@1.0.1: resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} - caniuse-lite@1.0.30001692: - resolution: {integrity: sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==} + caniuse-lite@1.0.30001701: + resolution: {integrity: sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw==} chai@5.2.0: resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} @@ -3656,8 +3639,8 @@ packages: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} - cjs-module-lexer@1.4.1: - resolution: {integrity: sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==} + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} @@ -3927,14 +3910,10 @@ packages: cross-fetch@4.0.0: resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==} - cross-spawn@6.0.5: - resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==} + cross-spawn@6.0.6: + resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==} engines: {node: '>=4.8'} - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -4121,11 +4100,15 @@ packages: dottie@2.0.6: resolution: {integrity: sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - eciesjs@0.4.11: - resolution: {integrity: sha512-SmUG449n1w1YGvJD9R30tBGvpxTxA0cnn0rfvpFIBvmezfIhagLjsH2JG8HBHOLS8slXsPh48II7IDUTH/J3Mg==} + eciesjs@0.4.14: + resolution: {integrity: sha512-eJAgf9pdv214Hn98FlUzclRMYWF7WfoLlkS9nWMTm1qcCwn6Ad4EGD9lr9HXMBfSrZhYQujRE+p0adPRkctC6A==} engines: {bun: '>=1', deno: '>=2', node: '>=16'} ejs@3.1.10: @@ -4133,8 +4116,8 @@ packages: engines: {node: '>=0.10.0'} hasBin: true - electron-to-chromium@1.5.80: - resolution: {integrity: sha512-LTrKpW0AqIuHwmlVNV+cjFYTnXtM9K37OGhpe0ZI10ScPSxqVSryZHIY3WnCS5NSYbBODRTZyhRMS2h5FAEqAw==} + electron-to-chromium@1.5.108: + resolution: {integrity: sha512-tiGxpQmvXBEzrfU5ertmbCV/nG5yqCkC1G4T1SIKP335Y5rjXzPWmijR6XcoGXZvVoo4dknfdNe4Tl7lcIROLg==} emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} @@ -4171,8 +4154,8 @@ packages: error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - es-define-property@1.0.0: - resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} es-errors@1.3.0: @@ -4185,6 +4168,14 @@ packages: es-module-lexer@1.6.0: resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + esbuild@0.24.2: resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} engines: {node: '>=18'} @@ -4344,8 +4335,8 @@ packages: fast-diff@1.3.0: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} fast-json-stable-stringify@2.1.0: @@ -4357,6 +4348,10 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-printf@1.6.10: + resolution: {integrity: sha512-GwTgG9O4FVIdShhbVF3JxOgSBY2+ePGsu2V/UONgoCPzF9VY6ZdBMKsHKCYQHZwNk3qNouUolRDsgVxcVA5G1w==} + engines: {node: '>=10.0'} + fast-querystring@1.1.2: resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} @@ -4382,6 +4377,9 @@ packages: fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} @@ -4470,15 +4468,16 @@ packages: debug: optional: true - for-each@0.3.3: - resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} - foreground-child@3.3.0: - resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} - form-data@4.0.1: - resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} + form-data@4.0.2: + resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} engines: {node: '>= 6'} forwarded-parse@2.1.2: @@ -4494,6 +4493,10 @@ packages: fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fs-extra@11.2.0: + resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} + engines: {node: '>=14.14'} + fs-extra@11.3.0: resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==} engines: {node: '>=14.14'} @@ -4532,12 +4535,12 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.2.0: - resolution: {integrity: sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==} + get-east-asian-width@1.3.0: + resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==} engines: {node: '>=18'} - get-intrinsic@1.2.4: - resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} get-package-type@0.1.0: @@ -4549,6 +4552,14 @@ packages: engines: {node: '>=6.9.0'} hasBin: true + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stack-trace@3.1.1: + resolution: {integrity: sha512-E1rM+umbm9MlMp6zNSap+UI8VVWWmAoUxiAHp1Ron1FV2dM99mgMAHS1tGAGO/ceBjgOXz24GC47aLeNN1llrA==} + engines: {node: '>=18.0'} + get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -4557,8 +4568,8 @@ packages: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} - get-tsconfig@4.8.1: - resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} + get-tsconfig@4.10.0: + resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==} getopts@2.3.0: resolution: {integrity: sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==} @@ -4597,8 +4608,8 @@ packages: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true - glob@11.0.0: - resolution: {integrity: sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==} + glob@11.0.1: + resolution: {integrity: sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==} engines: {node: 20 || >=22} hasBin: true @@ -4635,8 +4646,9 @@ packages: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} - gopd@1.0.1: - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -4663,8 +4675,9 @@ packages: resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} engines: {node: '>=0.10.0'} - has-bigints@1.0.2: - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} @@ -4677,12 +4690,8 @@ packages: has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - has-proto@1.0.3: - resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} - engines: {node: '>= 0.4'} - - has-symbols@1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} has-tostringtag@1.0.2: @@ -4752,16 +4761,11 @@ packages: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} - husky@9.1.7: - resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} - engines: {node: '>=18'} - hasBin: true - i18next-browser-languagedetector@7.2.2: resolution: {integrity: sha512-6b7r75uIJDWCcCflmbof+sJ94k9UQO4X0YR62oUfqGI/GjCLVzlCwu8TFdRZIqVLzWbzNcmkmhfqKEr4TLz4HQ==} - i18next-http-backend@2.7.1: - resolution: {integrity: sha512-vPksHIckysGgykCD8JwCr2YsJEml9Cyw+Yu2wtb4fQ7xIn9RH/hkUDh5UkwnIzb0kSL4SJ30Ab/sCInhQxbCgg==} + i18next-http-backend@2.7.3: + resolution: {integrity: sha512-FgZxrXdRA5u44xfYsJlEBL4/KH3f2IluBpgV/7riW0YW2VEyM8FzVt2XHAOi6id0Ppj7vZvCZVpp5LrGXnc8Ig==} i18next@22.5.1: resolution: {integrity: sha512-8TGPgM3pAD+VRsMtUMNknRz3kzqwp/gPALrWMsDnmC1mKqJwpWyooQRLMcbTwq8z8YwSmuj+ZYvc+xCuEpkssA==} @@ -4788,8 +4792,8 @@ packages: immutable@4.3.7: resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==} - import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} import-in-the-middle@1.13.0: @@ -4828,8 +4832,8 @@ packages: resolution: {integrity: sha512-LJKFHCSeIRq9hanN14IlOtPSTe3lNES7TYDTE2xxdAy1LS5rYphajK1qtwvj3YmQXvvk0U2Vbmcni8P9EIQW9w==} engines: {node: '>=18'} - internal-slot@1.0.7: - resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} interpret@2.2.0: @@ -4843,34 +4847,35 @@ packages: resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==} engines: {node: '>= 10'} - is-arguments@1.1.1: - resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} + is-arguments@1.2.0: + resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} engines: {node: '>= 0.4'} - is-array-buffer@3.0.4: - resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-bigint@1.0.4: - resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} - is-boolean-object@1.1.2: - resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} engines: {node: '>= 0.4'} is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - is-core-module@2.15.1: - resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} - is-date-object@1.0.5: - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} engines: {node: '>= 0.4'} is-docker@2.2.1: @@ -4917,8 +4922,8 @@ packages: is-module@1.0.0: resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} - is-number-object@1.0.7: - resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} engines: {node: '>= 0.4'} is-number@7.0.0: @@ -4947,16 +4952,16 @@ packages: is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} - is-regex@1.1.4: - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} is-set@2.0.3: resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} engines: {node: '>= 0.4'} - is-shared-array-buffer@1.0.3: - resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} engines: {node: '>= 0.4'} is-stream@2.0.1: @@ -4967,12 +4972,12 @@ packages: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - is-string@1.0.7: - resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} engines: {node: '>= 0.4'} - is-symbol@1.0.4: - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} engines: {node: '>= 0.4'} is-text-path@1.0.1: @@ -4995,8 +5000,8 @@ packages: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} - is-weakset@2.0.3: - resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} is-wsl@2.2.0: @@ -5016,6 +5021,9 @@ packages: resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} engines: {node: '>=16'} + iso8601-duration@1.3.0: + resolution: {integrity: sha512-K4CiUBzo3YeWk76FuET/dQPH03WE04R94feo5TSKQCXpoXQt9E4yx2CnY737QZnSAI3PI4WlKo/zfqizGx52QQ==} + isobject@3.0.1: resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} engines: {node: '>=0.10.0'} @@ -5051,8 +5059,8 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jackspeak@4.0.2: - resolution: {integrity: sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==} + jackspeak@4.1.0: + resolution: {integrity: sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==} engines: {node: 20 || >=22} jake@10.9.2: @@ -5402,8 +5410,8 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.0.1: - resolution: {integrity: sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==} + lru-cache@11.0.2: + resolution: {integrity: sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==} engines: {node: 20 || >=22} lru-cache@5.1.1: @@ -5449,6 +5457,10 @@ packages: resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} hasBin: true + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + mdurl@2.0.0: resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} @@ -5482,6 +5494,14 @@ packages: resolution: {integrity: sha512-uOJdx0q9Hg0SKYtHeJ73Iu2PhlU8LoyhaMm2PH9n1kvqpyoqUme2vKpwWywELFpZKgXwtkeIA8Ce56caYb593Q==} engines: {node: '>= 18.12.0'} + mikro-orm@6.4.3: + resolution: {integrity: sha512-xDNzmLiL4EUTMOu9CbZ2d0sNIaUdH4RzDv4oqw27+u0/FPfvZTIagd+luxx1lWWqe/vg/iNtvqr5OcNQIYYrtQ==} + engines: {node: '>= 18.12.0'} + + mikro-orm@6.4.7: + resolution: {integrity: sha512-lRB92yekDcdQEHloNY9LWfNLyKd3WajxPSe3jBvyVR9gMIO7YsjhqxL0mYvoQi8MQzXcUTi1EkWG05IM3gidIw==} + engines: {node: '>= 18.12.0'} + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -5578,8 +5598,8 @@ packages: moment@2.30.1: resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} - mrmime@2.0.0: - resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} ms@2.1.2: @@ -5609,8 +5629,8 @@ packages: nice-try@1.0.5: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} - node-abi@3.68.0: - resolution: {integrity: sha512-7vbj10trelExNjFSBm5kTvZXXa7pZyKWx9RCKIyqe6I9Ev3IzGpQoqBP3a+cOdxY+pWj6VkP28n/2wWysBHD/A==} + node-abi@3.74.0: + resolution: {integrity: sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==} engines: {node: '>=10'} node-addon-api@5.1.0: @@ -5682,8 +5702,8 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - object-inspect@1.13.2: - resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} object-is@1.1.6: @@ -5698,8 +5718,8 @@ packages: resolution: {integrity: sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==} engines: {node: '>= 10'} - object.assign@4.1.5: - resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} obuf@1.1.2: @@ -5886,8 +5906,8 @@ packages: pg-connection-string@2.7.0: resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==} - pg-cursor@2.12.0: - resolution: {integrity: sha512-rppw54OnuYZfMUjiJI2zJMwAjjt2V9EtLUb+t7V5tqwSE5Jxod+7vA7Y0FI6Nq976jNLciA0hoVkwvjjB8qzEw==} + pg-cursor@2.12.3: + resolution: {integrity: sha512-2koS4+f+oCJnJ6pTdUlp3TkwOCjlPr9lmwcTDPeXZv3eiXDCxZeBC7T0+LldA9+mhqdcO8WDqbuFfdFEJm9YLw==} peerDependencies: pg: ^8 @@ -5908,12 +5928,17 @@ packages: peerDependencies: pg: '>=8.0' - pg-protocol@1.7.0: - resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==} + pg-protocol@1.7.1: + resolution: {integrity: sha512-gjTHWGYWsEgy9MsY0Gp6ZJxV24IjDqdpTW7Eh0x+WfJLFsm/TJx1MzL6T0D88mBvkpxotCQ6TwW6N+Kko7lhgQ==} pg-protocol@1.8.0: resolution: {integrity: sha512-jvuYlEkL03NRvOoyoRktBK7+qU5kOvlAwvmrH8sr3wbLrOdVWsRxQfz8mMy9sZFsqJ1hEWNfdWKI4SAmoL+j7g==} + pg-query-stream@4.7.3: + resolution: {integrity: sha512-WvsdwYXrvIXNZJflX/2MEQLjKs5TopZWeiyam1zcaFfhwQtL19ENOvGdzVsihGsbsNGdVRU5yiqg2G5p06UAbg==} + peerDependencies: + pg: ^8 + pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} @@ -5997,12 +6022,12 @@ packages: resolution: {integrity: sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==} engines: {node: '>=10'} - portfinder@1.0.32: - resolution: {integrity: sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==} + portfinder@1.0.33: + resolution: {integrity: sha512-+2jndHT63cL5MdQOwDm9OT2dIe11zVpjV+0GGRXdtO1wpPxv260NfVqoEXtYAi/shanmm3W4+yLduIe55ektTw==} engines: {node: '>= 0.12.0'} - possible-typed-array-names@1.0.0: - resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} postcss-value-parser@4.2.0: @@ -6107,8 +6132,8 @@ packages: prosemirror-collab@1.3.1: resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==} - prosemirror-commands@1.6.1: - resolution: {integrity: sha512-tNy4uaGWzvuUYXDke7B28krndIrdQJhSh0OLpubtwtEwFbjItOj/eoAfPvstBJyyV0S2+b5t4G+4XPXdxar6pg==} + prosemirror-commands@1.7.0: + resolution: {integrity: sha512-6toodS4R/Aah5pdsrIwnTYPEjW70SlO5a66oo5Kk+CIrgJz3ukOoS+FYDGqvQlAX5PxoGWDX1oD++tn5X3pyRA==} prosemirror-dropcursor@1.8.1: resolution: {integrity: sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw==} @@ -6131,20 +6156,20 @@ packages: prosemirror-menu@1.2.4: resolution: {integrity: sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA==} - prosemirror-model@1.23.0: - resolution: {integrity: sha512-Q/fgsgl/dlOAW9ILu4OOhYWQbc7TQd4BwKH/RwmUjyVf8682Be4zj3rOYdLnYEcGzyg8LL9Q5IWYKD8tdToreQ==} + prosemirror-model@1.24.1: + resolution: {integrity: sha512-YM053N+vTThzlWJ/AtPtF1j0ebO36nvbmDy4U7qA2XQB8JVaQp1FmB9Jhrps8s+z+uxhhVTny4m20ptUvhk0Mg==} prosemirror-schema-basic@1.2.3: resolution: {integrity: sha512-h+H0OQwZVqMon1PNn0AG9cTfx513zgIG2DY00eJ00Yvgb3UD+GQ/VlWW5rcaxacpCGT1Yx8nuhwXk4+QbXUfJA==} - prosemirror-schema-list@1.4.1: - resolution: {integrity: sha512-jbDyaP/6AFfDfu70VzySsD75Om2t3sXTOdl5+31Wlxlg62td1haUpty/ybajSfJ1pkGadlOfwQq9kgW5IMo1Rg==} + prosemirror-schema-list@1.5.0: + resolution: {integrity: sha512-gg1tAfH1sqpECdhIHOA/aLg2VH3ROKBWQ4m8Qp9mBKrOxQRW61zc+gMCI8nh22gnBzd1t2u1/NPLmO3nAa3ssg==} prosemirror-state@1.4.3: resolution: {integrity: sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==} - prosemirror-tables@1.5.0: - resolution: {integrity: sha512-VMx4zlYWm7aBlZ5xtfJHpqa3Xgu3b7srV54fXYnXgsAcIGRqKSrhiK3f89omzzgaAgAtDOV4ImXnLKhVfheVNQ==} + prosemirror-tables@1.6.4: + resolution: {integrity: sha512-TkDY3Gw52gRFRfRn2f4wJv5WOgAOXLJA2CQJYIJ5+kdFbfj3acR4JUW6LX2e1hiEBiUwvEhzH5a3cZ5YSztpIA==} prosemirror-trailing-node@2.0.9: resolution: {integrity: sha512-YvyIn3/UaLFlFKrlJB6cObvUhmwFNZVhy1Q8OpW/avoTbD/Y7H5EcjK4AZFKhmuS6/N6WkGgt7gWtBWDnmFvHg==} @@ -6156,8 +6181,8 @@ packages: prosemirror-transform@1.10.2: resolution: {integrity: sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ==} - prosemirror-view@1.34.3: - resolution: {integrity: sha512-mKZ54PrX19sSaQye+sef+YjBbNu2voNwLS1ivb6aD2IRmxRGW64HU9B644+7OfJStGLyxvOreKqEgfvXa91WIA==} + prosemirror-view@1.38.0: + resolution: {integrity: sha512-O45kxXQTaP9wPdXhp8TKqCR+/unS/gnfg9Q93svQcB3j0mlp2XSPAmsPefxHADwzC+fbNS404jqRxm3UQaGvgw==} proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} @@ -6181,11 +6206,10 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} deprecated: |- You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. - (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) - qs@6.13.1: - resolution: {integrity: sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==} + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} queue-microtask@1.2.3: @@ -6201,10 +6225,10 @@ packages: raf-schd@4.0.3: resolution: {integrity: sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==} - react-currency-input-field@3.9.0: - resolution: {integrity: sha512-OmkO0rRSGiNGbcO4F1wzC+Szm2A7tLRGtDAKF6t0xNrFr07q99AHo3BAn/68RTEG4iwqc2m2jekKZi33/8SV+Q==} + react-currency-input-field@3.10.0: + resolution: {integrity: sha512-GRmZogHh1e1LrmgXg/fKHSuRLYUnj/c/AumfvfuDMA0UX1mDR6u2NR0fzDemRdq4tNHNLucJeJ2OKCr3ehqyDA==} peerDependencies: - react: ^16.9.0 || ^17.0.0 || ^18.0.0 + react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-datepicker@4.25.0: resolution: {integrity: sha512-zB7CSi44SJ0sqo8hUQ3BF1saE/knn7u25qEMTO1CQGofY1VAKahO8k9drZtp0cfW1DMfoYLR3uSY1/uMvbEzbg==} @@ -6284,21 +6308,21 @@ packages: redux: optional: true - react-router-dom@6.28.1: - resolution: {integrity: sha512-YraE27C/RdjcZwl5UCqF/ffXnZDxpJdk9Q6jw38SZHjXs7NNdpViq2l2c7fO7+4uWaEfcwfGCv3RSg4e1By/fQ==} + react-router-dom@6.30.0: + resolution: {integrity: sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' react-dom: '>=16.8' - react-router@6.28.1: - resolution: {integrity: sha512-2omQTA3rkMljmrvvo6WtewGdVh45SpL9hGiCI9uUrwGGfNFDIvGK4gYJsKlJoNVi6AQZcopSCballL+QGOm7fA==} + react-router@6.30.0: + resolution: {integrity: sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==} engines: {node: '>=14.0.0'} peerDependencies: react: '>=16.8' - react-select@5.9.0: - resolution: {integrity: sha512-nwRKGanVHGjdccsnzhFte/PULziueZxGD8LL2WojON78Mvnq7LdAMEtu2frrwld1fr3geixg3iiMBIc/LLAZpw==} + react-select@5.10.0: + resolution: {integrity: sha512-k96gw+i6N3ExgDwPIg0lUPmexl1ygPe6u5BdQFNBhkpbwroIgCNXdubtIzHfThYXYYTubwOBafoMnn7ruEP1xA==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -6372,8 +6396,8 @@ packages: regenerator-transform@0.15.2: resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} - regexp.prototype.flags@1.5.3: - resolution: {integrity: sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==} + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} regexpu-core@6.2.0: @@ -6395,8 +6419,8 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - require-in-the-middle@7.4.0: - resolution: {integrity: sha512-X34iHADNbNDfr6OTStIAHWSAvvKQRYgLO6duASaVf7J2VA3lvmNYboAHOuLC2huav1IwgZJtyEcJCKVzFxOSMQ==} + require-in-the-middle@7.5.2: + resolution: {integrity: sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==} engines: {node: '>=8.6.0'} require-relative@0.8.7: @@ -6420,8 +6444,9 @@ packages: resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} engines: {node: '>=10'} - resolve@1.22.8: - resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} hasBin: true restore-cursor@3.1.0: @@ -6447,6 +6472,10 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} @@ -6460,6 +6489,10 @@ packages: engines: {node: 20 || >=22} hasBin: true + roarr@7.21.1: + resolution: {integrity: sha512-3niqt5bXFY1InKU8HKWqqYTYjtrBaxBMnXELXCXUYgtNYGUtZM5rB46HIC430AyacL95iEniGf7RgqsesykLmQ==} + engines: {node: '>=18.0'} + rollup-plugin-esbuild-minify@1.2.0: resolution: {integrity: sha512-M112JoRC8oUHKHQcXUQzSwXEnvriXtpy4rnaeqt/ZeknVbjymhdZRRUf+2fdR3k4jOQCZas0N4u1U2xbtRTrbg==} engines: {node: '>= 14.18'} @@ -6471,13 +6504,8 @@ packages: peerDependencies: rollup: ^1.20.0 || ^2.0.0 || ^3.0.0 || ^4.0.0 - rollup@4.24.0: - resolution: {integrity: sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - rollup@4.34.3: - resolution: {integrity: sha512-ORCtU0UBJyiAIn9m0llUXJXAswG/68pZptCrqxHG7//Z2DDzAUeyyY5hqf4XrsGlUxscMr9GkQ2QI7KTLqeyPw==} + rollup@4.34.8: + resolution: {integrity: sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -6491,8 +6519,8 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - rxjs@7.8.1: - resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} @@ -6500,6 +6528,10 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + safe-regex2@4.0.0: resolution: {integrity: sha512-Hvjfv25jPDVr3U+4LDzBuZPPOymELG3PYcSk5hcevooo1yxxamQL/bHs/GrEPGmMoMEwRrHVGiCA1pXi97B8Ew==} @@ -6522,6 +6554,9 @@ packages: secure-json-parse@3.0.1: resolution: {integrity: sha512-9QR7G96th4QJ2+dJwvZB+JoXyt8PN+DbEjOr6kL2/JU4KH8Eb2sFdU+gt8EDdzWDWoWH0uocDdfCoFzdVSixUA==} + semver-compare@1.0.0: + resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -6535,6 +6570,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + engines: {node: '>=10'} + hasBin: true + sequelize-pool@7.1.0: resolution: {integrity: sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==} engines: {node: '>= 10.0.0'} @@ -6572,6 +6612,10 @@ packages: tedious: optional: true + serialize-error@8.1.0: + resolution: {integrity: sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==} + engines: {node: '>=10'} + set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} @@ -6619,8 +6663,20 @@ packages: resolution: {integrity: sha512-MlRVyT5RYfDO2kUzBgOPlZriRzG+NIAuwSy1HBN8tahXyFi3+804GGi/mzjUsi6VxgiQuDgMnhoI2FqmSHX8Tg==} hasBin: true - side-channel@1.0.6: - resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} siginfo@2.0.0: @@ -6653,6 +6709,12 @@ packages: resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} engines: {node: '>=18'} + slonik@46.4.0: + resolution: {integrity: sha512-2CGTUww8iaxaQ/73sgC+2pQUDLAprAp278i50sY2QDvKLmwzdyIfFEkQIBVYI6WXhBZiuIShuaarTgjP1UxqzQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3 + sonic-boom@4.1.0: resolution: {integrity: sha512-NGipjjRicyJJ03rPiZCJYjwlsuP2d1/5QUviozRXC7S3WdVWNK5e3Ojieb9CCyfhq2UC+3+SRd9nG3I2lPRvUw==} @@ -6686,8 +6748,8 @@ packages: spdx-expression-parse@3.0.1: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - spdx-license-ids@3.0.20: - resolution: {integrity: sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==} + spdx-license-ids@3.0.21: + resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==} split2@3.2.2: resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} @@ -6713,6 +6775,10 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + stacktrace-parser@0.1.11: + resolution: {integrity: sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==} + engines: {node: '>=6'} + standard-version@9.5.0: resolution: {integrity: sha512-3zWJ/mmZQsOaO+fOlsa0+QK90pwhNd042qEcw6hKFNoLFs7peGyvPffpEBbK/DSGPbyOvli0mUIFv5A4qTjh2Q==} engines: {node: '>=10'} @@ -6729,10 +6795,13 @@ packages: resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - stop-iteration-iterator@1.0.0: - resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} + strict-event-emitter-types@2.0.0: + resolution: {integrity: sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA==} + string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -7038,6 +7107,10 @@ packages: resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} engines: {node: '>=8'} + type-fest@0.7.1: + resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} + engines: {node: '>=8'} + type-fest@0.8.1: resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} engines: {node: '>=8'} @@ -7111,8 +7184,8 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} - update-browserslist-db@1.1.2: - resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==} + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -7324,15 +7397,16 @@ packages: whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - which-boxed-primitive@1.0.2: - resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} which-collection@1.0.2: resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} engines: {node: '>= 0.4'} - which-typed-array@1.1.15: - resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} + which-typed-array@1.1.18: + resolution: {integrity: sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==} engines: {node: '>= 0.4'} which@1.3.1: @@ -7450,9 +7524,12 @@ packages: resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} engines: {node: '>=18'} + zod@3.24.2: + resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} + snapshots: - '@adminjs/design-system@4.1.1(@babel/core@7.26.0)(@tiptap/extension-text-style@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)))(@types/react@19.0.4)(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)': + '@adminjs/design-system@4.1.1(@babel/core@7.26.9)(@types/react@19.0.10)(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)': dependencies: '@hypnosphi/create-react-context': 0.3.1(prop-types@15.8.1)(react@18.3.1) '@tinymce/tinymce-react': 4.3.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -7474,7 +7551,7 @@ snapshots: '@tiptap/extension-typography': 2.1.13(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) '@tiptap/pm': 2.1.13 '@tiptap/react': 2.1.13(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/pm@2.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@tiptap/starter-kit': 2.1.13(@tiptap/extension-text-style@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)))(@tiptap/pm@2.1.13) + '@tiptap/starter-kit': 2.1.13(@tiptap/pm@2.1.13) date-fns: 2.30.0 flat: 5.0.2 hoist-non-react-statics: 3.3.2 @@ -7482,19 +7559,18 @@ snapshots: lodash: 4.17.21 polished: 4.3.1 react: 18.3.1 - react-currency-input-field: 3.9.0(react@18.3.1) + react-currency-input-field: 3.10.0(react@18.3.1) react-datepicker: 4.25.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-dom: 18.3.1(react@18.3.1) react-feather: 2.0.10(react@18.3.1) react-phone-input-2: 2.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-select: 5.9.0(@types/react@19.0.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-select: 5.10.0(@types/react@19.0.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-text-mask: 5.5.0(react@18.3.1) - styled-components: 5.3.9(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1) + styled-components: 5.3.9(@babel/core@7.26.9)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1) styled-system: 5.1.5 text-mask-addons: 3.8.0 transitivePeerDependencies: - '@babel/core' - - '@tiptap/extension-text-style' - '@types/react' - prop-types - react-is @@ -7502,34 +7578,29 @@ snapshots: '@ampproject/remapping@2.3.0': dependencies: - '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 - '@babel/code-frame@7.25.7': - dependencies: - '@babel/highlight': 7.25.7 - picocolors: 1.1.1 - '@babel/code-frame@7.26.2': dependencies: '@babel/helper-validator-identifier': 7.25.9 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.26.5': {} + '@babel/compat-data@7.26.8': {} - '@babel/core@7.26.0': + '@babel/core@7.26.9': dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.5 + '@babel/generator': 7.26.9 '@babel/helper-compilation-targets': 7.26.5 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) - '@babel/helpers': 7.26.0 - '@babel/parser': 7.26.5 - '@babel/template': 7.25.9 - '@babel/traverse': 7.26.5(supports-color@5.5.0) - '@babel/types': 7.26.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.9) + '@babel/helpers': 7.26.9 + '@babel/parser': 7.26.9 + '@babel/template': 7.26.9 + '@babel/traverse': 7.26.9(supports-color@5.5.0) + '@babel/types': 7.26.9 convert-source-map: 2.0.0 debug: 4.4.0(supports-color@5.5.0) gensync: 1.0.0-beta.2 @@ -7538,800 +7609,791 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.26.5': + '@babel/generator@7.26.9': dependencies: - '@babel/parser': 7.26.5 - '@babel/types': 7.26.5 - '@jridgewell/gen-mapping': 0.3.5 + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 + '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.1.0 '@babel/helper-annotate-as-pure@7.25.9': dependencies: - '@babel/types': 7.26.5 + '@babel/types': 7.26.9 '@babel/helper-compilation-targets@7.26.5': dependencies: - '@babel/compat-data': 7.26.5 + '@babel/compat-data': 7.26.8 '@babel/helper-validator-option': 7.25.9 browserslist: 4.24.4 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.25.9(@babel/core@7.26.0)': + '@babel/helper-create-class-features-plugin@7.26.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-member-expression-to-functions': 7.25.9 '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.0) + '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.9) '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/traverse': 7.26.5(supports-color@5.5.0) + '@babel/traverse': 7.26.9(supports-color@5.5.0) semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-regexp-features-plugin@7.26.3(@babel/core@7.26.0)': + '@babel/helper-create-regexp-features-plugin@7.26.3(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-annotate-as-pure': 7.25.9 regexpu-core: 6.2.0 semver: 6.3.1 - '@babel/helper-define-polyfill-provider@0.6.3(@babel/core@7.26.0)': + '@babel/helper-define-polyfill-provider@0.6.3(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-compilation-targets': 7.26.5 '@babel/helper-plugin-utils': 7.26.5 debug: 4.4.0(supports-color@5.5.0) lodash.debounce: 4.0.8 - resolve: 1.22.8 + resolve: 1.22.10 transitivePeerDependencies: - supports-color '@babel/helper-member-expression-to-functions@7.25.9': dependencies: - '@babel/traverse': 7.26.5(supports-color@5.5.0) - '@babel/types': 7.26.5 + '@babel/traverse': 7.26.9(supports-color@5.5.0) + '@babel/types': 7.26.9 transitivePeerDependencies: - supports-color '@babel/helper-module-imports@7.25.9(supports-color@5.5.0)': dependencies: - '@babel/traverse': 7.26.5(supports-color@5.5.0) - '@babel/types': 7.26.5 + '@babel/traverse': 7.26.9(supports-color@5.5.0) + '@babel/types': 7.26.9 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.26.5(supports-color@5.5.0) + '@babel/traverse': 7.26.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color '@babel/helper-optimise-call-expression@7.25.9': dependencies: - '@babel/types': 7.26.5 + '@babel/types': 7.26.9 '@babel/helper-plugin-utils@7.26.5': {} - '@babel/helper-remap-async-to-generator@7.25.9(@babel/core@7.26.0)': + '@babel/helper-remap-async-to-generator@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-wrap-function': 7.25.9 - '@babel/traverse': 7.26.5(supports-color@5.5.0) + '@babel/traverse': 7.26.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.26.5(@babel/core@7.26.0)': + '@babel/helper-replace-supers@7.26.5(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-member-expression-to-functions': 7.25.9 '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/traverse': 7.26.5(supports-color@5.5.0) + '@babel/traverse': 7.26.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color '@babel/helper-skip-transparent-expression-wrappers@7.25.9': dependencies: - '@babel/traverse': 7.26.5(supports-color@5.5.0) - '@babel/types': 7.26.5 + '@babel/traverse': 7.26.9(supports-color@5.5.0) + '@babel/types': 7.26.9 transitivePeerDependencies: - supports-color '@babel/helper-string-parser@7.25.9': {} - '@babel/helper-validator-identifier@7.25.7': {} - '@babel/helper-validator-identifier@7.25.9': {} '@babel/helper-validator-option@7.25.9': {} '@babel/helper-wrap-function@7.25.9': dependencies: - '@babel/template': 7.25.9 - '@babel/traverse': 7.26.5(supports-color@5.5.0) - '@babel/types': 7.26.5 + '@babel/template': 7.26.9 + '@babel/traverse': 7.26.9(supports-color@5.5.0) + '@babel/types': 7.26.9 transitivePeerDependencies: - supports-color - '@babel/helpers@7.26.0': + '@babel/helpers@7.26.9': dependencies: - '@babel/template': 7.25.9 - '@babel/types': 7.26.5 + '@babel/template': 7.26.9 + '@babel/types': 7.26.9 - '@babel/highlight@7.25.7': + '@babel/parser@7.26.9': dependencies: - '@babel/helper-validator-identifier': 7.25.7 - chalk: 2.4.2 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/parser@7.26.5': - dependencies: - '@babel/types': 7.26.5 + '@babel/types': 7.26.9 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/traverse': 7.26.5(supports-color@5.5.0) + '@babel/traverse': 7.26.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.9) transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/traverse': 7.26.5(supports-color@5.5.0) + '@babel/traverse': 7.26.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-decorators@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-proposal-decorators@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/core': 7.26.9 + '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.26.9) '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-decorators': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-syntax-decorators': 7.25.9(@babel/core@7.26.9) transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.0)': + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.26.0)': + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.26.0)': + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.26.0)': + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-decorators@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-syntax-decorators@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.26.0)': + '@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.0)': + '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.0)': + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.26.0)': + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.26.0)': + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.0)': + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.26.0)': + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.26.0)': + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.26.0)': + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) + '@babel/core': 7.26.9 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.9) '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-async-generator-functions@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-async-generator-functions@7.26.8(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.0) - '@babel/traverse': 7.26.5(supports-color@5.5.0) + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.9) + '@babel/traverse': 7.26.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-async-to-generator@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-async-to-generator@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) '@babel/helper-plugin-utils': 7.26.5 - '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.0) + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.9) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-block-scoped-functions@7.26.5(@babel/core@7.26.0)': + '@babel/plugin-transform-block-scoped-functions@7.26.5(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-block-scoping@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-block-scoping@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-class-properties@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-class-properties@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/core': 7.26.9 + '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.26.9) '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-class-static-block@7.26.0(@babel/core@7.26.0)': + '@babel/plugin-transform-class-static-block@7.26.0(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/core': 7.26.9 + '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.26.9) '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-classes@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-classes@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-compilation-targets': 7.26.5 '@babel/helper-plugin-utils': 7.26.5 - '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.0) - '@babel/traverse': 7.26.5(supports-color@5.5.0) + '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.9) + '@babel/traverse': 7.26.9(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/template': 7.25.9 + '@babel/template': 7.26.9 - '@babel/plugin-transform-destructuring@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-destructuring@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-dotall-regex@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-dotall-regex@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) + '@babel/core': 7.26.9 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.9) '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) + '@babel/core': 7.26.9 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.9) '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-exponentiation-operator@7.26.3(@babel/core@7.26.0)': + '@babel/plugin-transform-exponentiation-operator@7.26.3(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-for-of@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-for-of@7.26.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-function-name@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-function-name@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-compilation-targets': 7.26.5 '@babel/helper-plugin-utils': 7.26.5 - '@babel/traverse': 7.26.5(supports-color@5.5.0) + '@babel/traverse': 7.26.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-json-strings@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-json-strings@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-literals@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-literals@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-logical-assignment-operators@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-logical-assignment-operators@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-member-expression-literals@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-member-expression-literals@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-modules-amd@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-modules-amd@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/core': 7.26.9 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.9) '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-commonjs@7.26.3(@babel/core@7.26.0)': + '@babel/plugin-transform-modules-commonjs@7.26.3(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/core': 7.26.9 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.9) '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-systemjs@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-modules-systemjs@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/core': 7.26.9 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.9) '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.26.5(supports-color@5.5.0) + '@babel/traverse': 7.26.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-umd@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-modules-umd@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/core': 7.26.9 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.9) '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) + '@babel/core': 7.26.9 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.9) '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-new-target@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-new-target@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-nullish-coalescing-operator@7.26.5(@babel/core@7.26.0)': + '@babel/plugin-transform-nullish-coalescing-operator@7.26.6(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-numeric-separator@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-numeric-separator@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-object-rest-spread@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-object-rest-spread@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-compilation-targets': 7.26.5 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.9) - '@babel/plugin-transform-object-super@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-object-super@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.0) + '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.9) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-optional-catch-binding@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-optional-catch-binding@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-optional-chaining@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-optional-chaining@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-parameters@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-parameters@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/core': 7.26.9 + '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.26.9) '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-property-in-object@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-private-property-in-object@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.26.9) '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-property-literals@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-property-literals@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-react-display-name@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-react-display-name@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-react-jsx-development@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-react-jsx-development@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.0) + '@babel/core': 7.26.9 + '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.9) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) - '@babel/types': 7.26.5 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.9) + '@babel/types': 7.26.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-pure-annotations@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-react-pure-annotations@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-regenerator@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-regenerator@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 regenerator-transform: 0.15.2 - '@babel/plugin-transform-regexp-modifiers@7.26.0(@babel/core@7.26.0)': + '@babel/plugin-transform-regexp-modifiers@7.26.0(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) + '@babel/core': 7.26.9 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.9) '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-runtime@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-runtime@7.26.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) '@babel/helper-plugin-utils': 7.26.5 - babel-plugin-polyfill-corejs2: 0.4.12(@babel/core@7.26.0) - babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.26.0) - babel-plugin-polyfill-regenerator: 0.6.3(@babel/core@7.26.0) + babel-plugin-polyfill-corejs2: 0.4.12(@babel/core@7.26.9) + babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.26.9) + babel-plugin-polyfill-regenerator: 0.6.3(@babel/core@7.26.9) semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-shorthand-properties@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-shorthand-properties@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-spread@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-spread@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-template-literals@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-template-literals@7.26.8(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-typeof-symbol@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-typeof-symbol@7.26.7(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-typescript@7.26.5(@babel/core@7.26.0)': + '@babel/plugin-transform-typescript@7.26.8(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.26.9) '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.9) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) + '@babel/core': 7.26.9 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.9) '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-unicode-regex@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-unicode-regex@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) + '@babel/core': 7.26.9 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.9) '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) + '@babel/core': 7.26.9 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.9) '@babel/helper-plugin-utils': 7.26.5 - '@babel/preset-env@7.26.0(@babel/core@7.26.0)': + '@babel/preset-env@7.26.9(@babel/core@7.26.9)': dependencies: - '@babel/compat-data': 7.26.5 - '@babel/core': 7.26.0 + '@babel/compat-data': 7.26.8 + '@babel/core': 7.26.9 '@babel/helper-compilation-targets': 7.26.5 '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-validator-option': 7.25.9 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.0) - '@babel/plugin-syntax-import-assertions': 7.26.0(@babel/core@7.26.0) - '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.0) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.26.0) - '@babel/plugin-transform-arrow-functions': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-async-generator-functions': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-block-scoped-functions': 7.26.5(@babel/core@7.26.0) - '@babel/plugin-transform-block-scoping': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-class-static-block': 7.26.0(@babel/core@7.26.0) - '@babel/plugin-transform-classes': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-destructuring': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-dotall-regex': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-duplicate-keys': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-dynamic-import': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-exponentiation-operator': 7.26.3(@babel/core@7.26.0) - '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-for-of': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-json-strings': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-logical-assignment-operators': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-member-expression-literals': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-modules-amd': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.0) - '@babel/plugin-transform-modules-systemjs': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-modules-umd': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-new-target': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-nullish-coalescing-operator': 7.26.5(@babel/core@7.26.0) - '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-object-rest-spread': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-object-super': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-optional-catch-binding': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-property-literals': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-regenerator': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-regexp-modifiers': 7.26.0(@babel/core@7.26.0) - '@babel/plugin-transform-reserved-words': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-shorthand-properties': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-template-literals': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-typeof-symbol': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-unicode-escapes': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-unicode-property-regex': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-unicode-regex': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-unicode-sets-regex': 7.25.9(@babel/core@7.26.0) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.26.0) - babel-plugin-polyfill-corejs2: 0.4.12(@babel/core@7.26.0) - babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.26.0) - babel-plugin-polyfill-regenerator: 0.6.3(@babel/core@7.26.0) + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.9) + '@babel/plugin-syntax-import-assertions': 7.26.0(@babel/core@7.26.9) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.9) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.26.9) + '@babel/plugin-transform-arrow-functions': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-async-generator-functions': 7.26.8(@babel/core@7.26.9) + '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-block-scoped-functions': 7.26.5(@babel/core@7.26.9) + '@babel/plugin-transform-block-scoping': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-class-static-block': 7.26.0(@babel/core@7.26.9) + '@babel/plugin-transform-classes': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-destructuring': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-dotall-regex': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-duplicate-keys': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-dynamic-import': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-exponentiation-operator': 7.26.3(@babel/core@7.26.9) + '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-for-of': 7.26.9(@babel/core@7.26.9) + '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-json-strings': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-logical-assignment-operators': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-member-expression-literals': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-modules-amd': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.9) + '@babel/plugin-transform-modules-systemjs': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-modules-umd': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-new-target': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-nullish-coalescing-operator': 7.26.6(@babel/core@7.26.9) + '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-object-rest-spread': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-object-super': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-optional-catch-binding': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-property-literals': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-regenerator': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-regexp-modifiers': 7.26.0(@babel/core@7.26.9) + '@babel/plugin-transform-reserved-words': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-shorthand-properties': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-template-literals': 7.26.8(@babel/core@7.26.9) + '@babel/plugin-transform-typeof-symbol': 7.26.7(@babel/core@7.26.9) + '@babel/plugin-transform-unicode-escapes': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-unicode-property-regex': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-unicode-regex': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-unicode-sets-regex': 7.25.9(@babel/core@7.26.9) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.26.9) + babel-plugin-polyfill-corejs2: 0.4.12(@babel/core@7.26.9) + babel-plugin-polyfill-corejs3: 0.11.1(@babel/core@7.26.9) + babel-plugin-polyfill-regenerator: 0.6.3(@babel/core@7.26.9) core-js-compat: 3.40.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.26.0)': + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/types': 7.26.5 + '@babel/types': 7.26.9 esutils: 2.0.3 - '@babel/preset-react@7.26.3(@babel/core@7.26.0)': + '@babel/preset-react@7.26.3(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-validator-option': 7.25.9 - '@babel/plugin-transform-react-display-name': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-react-jsx-development': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-react-pure-annotations': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-react-display-name': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-react-jsx-development': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-react-pure-annotations': 7.25.9(@babel/core@7.26.9) transitivePeerDependencies: - supports-color - '@babel/preset-typescript@7.26.0(@babel/core@7.26.0)': + '@babel/preset-typescript@7.26.0(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-validator-option': 7.25.9 - '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.0) - '@babel/plugin-transform-typescript': 7.26.5(@babel/core@7.26.0) + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.9) + '@babel/plugin-transform-typescript': 7.26.8(@babel/core@7.26.9) transitivePeerDependencies: - supports-color - '@babel/register@7.25.9(@babel/core@7.26.0)': + '@babel/register@7.25.9(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 clone-deep: 4.0.1 find-cache-dir: 2.1.0 make-dir: 2.1.0 pirates: 4.0.6 source-map-support: 0.5.21 - '@babel/runtime@7.26.0': + '@babel/runtime@7.26.9': dependencies: regenerator-runtime: 0.14.1 - '@babel/template@7.25.9': + '@babel/template@7.26.9': dependencies: '@babel/code-frame': 7.26.2 - '@babel/parser': 7.26.5 - '@babel/types': 7.26.5 + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 - '@babel/traverse@7.26.5(supports-color@5.5.0)': + '@babel/traverse@7.26.9(supports-color@5.5.0)': dependencies: '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.5 - '@babel/parser': 7.26.5 - '@babel/template': 7.25.9 - '@babel/types': 7.26.5 + '@babel/generator': 7.26.9 + '@babel/parser': 7.26.9 + '@babel/template': 7.26.9 + '@babel/types': 7.26.9 debug: 4.4.0(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color - '@babel/types@7.26.5': + '@babel/types@7.26.9': dependencies: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 @@ -8417,7 +8479,7 @@ snapshots: '@commitlint/is-ignored@19.8.0': dependencies: '@commitlint/types': 19.8.0 - semver: 7.6.3 + semver: 7.7.1 '@commitlint/lint@19.8.0': dependencies: @@ -8482,7 +8544,7 @@ snapshots: '@commitlint/types@19.8.0': dependencies: - '@types/conventional-commits-parser': 5.0.0 + '@types/conventional-commits-parser': 5.0.1 chalk: 5.4.1 '@cspotcode/source-map-support@0.8.1': @@ -8504,13 +8566,13 @@ snapshots: '@databases/migrations-base@3.0.1': dependencies: - assert-never: 1.3.0 + assert-never: 1.4.0 chalk: 4.1.2 deep-equal: 2.2.3 interrogator: 2.0.1 is-interactive: 1.0.0 parameter-reducers: 2.1.0 - semver: 7.6.3 + semver: 7.7.1 '@databases/pg-config@3.2.0(typescript@5.8.3)': dependencies: @@ -8530,12 +8592,12 @@ snapshots: '@databases/migrations-base': 3.0.1 '@databases/pg': 5.5.0(typescript@5.8.3) '@databases/pg-config': 3.2.0(typescript@5.8.3) - assert-never: 1.3.0 + assert-never: 1.4.0 chalk: 4.1.2 interrogator: 2.0.1 is-interactive: 1.0.0 parameter-reducers: 2.1.0 - semver: 7.6.3 + semver: 7.7.1 sucrase: 3.35.0 transitivePeerDependencies: - pg-native @@ -8567,7 +8629,7 @@ snapshots: '@databases/pg-data-type-id': 3.0.0 '@databases/pg-schema-introspect': 4.2.0(typescript@5.8.3) '@databases/shared-print-types': 1.2.0 - assert-never: 1.3.0 + assert-never: 1.4.0 transitivePeerDependencies: - typescript @@ -8576,7 +8638,7 @@ snapshots: '@databases/pg-config': 3.2.0(typescript@5.8.3) '@databases/with-container': 2.1.1 '@types/cross-spawn': 6.0.6 - cross-spawn: 6.0.5 + cross-spawn: 6.0.6 modern-spawn: 1.0.0 ms: 2.1.3 parameter-reducers: 2.1.0 @@ -8587,7 +8649,7 @@ snapshots: '@databases/pg@5.5.0(typescript@5.8.3)': dependencies: - '@babel/code-frame': 7.25.7 + '@babel/code-frame': 7.26.2 '@databases/escape-identifier': 1.0.3 '@databases/pg-config': 3.2.0(typescript@5.8.3) '@databases/pg-connection-string': 1.0.0 @@ -8597,9 +8659,9 @@ snapshots: '@databases/shared': 3.1.0 '@databases/split-sql-query': 1.0.4(@databases/sql@3.3.0) '@databases/sql': 3.3.0 - assert-never: 1.3.0 + assert-never: 1.4.0 pg: 8.14.1 - pg-cursor: 2.12.0(pg@8.14.1) + pg-cursor: 2.12.3(pg@8.14.1) transitivePeerDependencies: - pg-native - typescript @@ -8636,7 +8698,7 @@ snapshots: '@databases/with-container@2.1.1': dependencies: '@types/cross-spawn': 6.0.6 - cross-spawn: 6.0.5 + cross-spawn: 6.0.6 detect-port: 1.6.1 modern-spawn: 1.0.0 transitivePeerDependencies: @@ -8646,7 +8708,7 @@ snapshots: dependencies: commander: 11.1.0 dotenv: 16.4.7 - eciesjs: 0.4.11 + eciesjs: 0.4.14 execa: 5.1.1 fdir: 6.4.3(picomatch@4.0.2) ignore: 5.3.2 @@ -8654,9 +8716,9 @@ snapshots: picomatch: 4.0.2 which: 4.0.0 - '@ecies/ciphers@0.2.1(@noble/ciphers@1.0.0)': + '@ecies/ciphers@0.2.2(@noble/ciphers@1.2.1)': dependencies: - '@noble/ciphers': 1.0.0 + '@noble/ciphers': 1.2.1 '@emnapi/core@1.3.1': dependencies: @@ -8674,7 +8736,7 @@ snapshots: '@emotion/babel-plugin@11.13.5': dependencies: '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.9 '@emotion/hash': 0.9.2 '@emotion/memoize': 0.9.0 '@emotion/serialize': 1.3.3 @@ -8703,9 +8765,9 @@ snapshots: '@emotion/memoize@0.9.0': {} - '@emotion/react@11.14.0(@types/react@19.0.4)(react@18.3.1)': + '@emotion/react@11.14.0(@types/react@19.0.10)(react@18.3.1)': dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.9 '@emotion/babel-plugin': 11.13.5 '@emotion/cache': 11.14.0 '@emotion/serialize': 1.3.3 @@ -8715,7 +8777,7 @@ snapshots: hoist-non-react-statics: 3.3.2 react: 18.3.1 optionalDependencies: - '@types/react': 19.0.4 + '@types/react': 19.0.10 transitivePeerDependencies: - supports-color @@ -8926,7 +8988,7 @@ snapshots: espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 - import-fresh: 3.3.0 + import-fresh: 3.3.1 js-yaml: 4.1.0 minimatch: 3.1.2 strip-json-comments: 3.1.1 @@ -8940,7 +9002,7 @@ snapshots: espree: 10.3.0 globals: 14.0.0 ignore: 5.3.2 - import-fresh: 3.3.0 + import-fresh: 3.3.1 js-yaml: 4.1.0 minimatch: 3.1.2 strip-json-comments: 3.1.1 @@ -9002,15 +9064,15 @@ snapshots: '@floating-ui/utils@0.2.9': {} - '@hello-pangea/dnd@16.6.0(@types/react@19.0.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@hello-pangea/dnd@16.6.0(@types/react@19.0.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.9 css-box-model: 1.2.1 memoize-one: 6.0.0 raf-schd: 4.0.3 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-redux: 8.1.3(@types/react@19.0.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@4.2.1) + react-redux: 8.1.3(@types/react@19.0.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@4.2.1) redux: 4.2.1 use-memo-one: 1.1.3(react@18.3.1) transitivePeerDependencies: @@ -9050,7 +9112,7 @@ snapshots: react: 18.3.1 warning: 4.0.3 - '@inquirer/figures@1.0.7': {} + '@inquirer/figures@1.0.10': {} '@isaacs/cliui@8.0.2': dependencies: @@ -9171,7 +9233,7 @@ snapshots: '@jest/transform@29.7.0': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 babel-plugin-istanbul: 6.1.1 @@ -9198,7 +9260,7 @@ snapshots: '@types/yargs': 17.0.33 chalk: 4.1.2 - '@jridgewell/gen-mapping@0.3.5': + '@jridgewell/gen-mapping@0.3.8': dependencies: '@jridgewell/set-array': 1.2.1 '@jridgewell/sourcemap-codec': 1.5.0 @@ -9231,7 +9293,7 @@ snapshots: nopt: 5.0.0 npmlog: 5.0.1 rimraf: 3.0.2 - semver: 7.6.3 + semver: 7.7.1 tar: 6.2.1 transitivePeerDependencies: - encoding @@ -9247,6 +9309,26 @@ snapshots: mikro-orm: 6.4.12 reflect-metadata: 0.2.2 + '@mikro-orm/core@6.4.3': + dependencies: + dataloader: 2.2.3 + dotenv: 16.4.7 + esprima: 4.0.1 + fs-extra: 11.2.0 + globby: 11.1.0 + mikro-orm: 6.4.3 + reflect-metadata: 0.2.2 + + '@mikro-orm/core@6.4.7': + dependencies: + dataloader: 2.2.3 + dotenv: 16.4.7 + esprima: 4.0.1 + fs-extra: 11.3.0 + globby: 11.1.0 + mikro-orm: 6.4.7 + reflect-metadata: 0.2.2 + '@mikro-orm/knex@6.4.12(@mikro-orm/core@6.4.12)(pg@8.14.1)': dependencies: '@mikro-orm/core': 6.4.12 @@ -9300,13 +9382,13 @@ snapshots: '@tybys/wasm-util': 0.9.0 optional: true - '@noble/ciphers@1.0.0': {} + '@noble/ciphers@1.2.1': {} - '@noble/curves@1.6.0': + '@noble/curves@1.8.1': dependencies: - '@noble/hashes': 1.5.0 + '@noble/hashes': 1.7.1 - '@noble/hashes@1.5.0': {} + '@noble/hashes@1.7.1': {} '@nodelib/fs.scandir@2.1.5': dependencies: @@ -9318,7 +9400,7 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.17.1 + fastq: 1.19.1 '@nx/devkit@20.7.2(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0))': dependencies: @@ -9332,10 +9414,10 @@ snapshots: tslib: 2.8.1 yargs-parser: 21.1.1 - '@nx/eslint@20.7.2(@babel/traverse@7.26.5)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(@zkochan/js-yaml@0.0.7)(debug@4.4.0)(eslint@9.24.0(jiti@2.4.2))(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0))': + '@nx/eslint@20.7.2(@babel/traverse@7.26.9)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(@zkochan/js-yaml@0.0.7)(debug@4.4.0)(eslint@9.24.0(jiti@2.4.2))(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0))': dependencies: '@nx/devkit': 20.7.2(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)) - '@nx/js': 20.7.2(@babel/traverse@7.26.5)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)) + '@nx/js': 20.7.2(@babel/traverse@7.26.9)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)) eslint: 9.24.0(jiti@2.4.2) semver: 7.6.3 tslib: 2.8.1 @@ -9351,12 +9433,12 @@ snapshots: - supports-color - verdaccio - '@nx/jest@20.7.2(@babel/traverse@7.26.5)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(@types/node@22.14.0)(babel-plugin-macros@3.1.0)(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0))(ts-node@10.9.2(@swc/core@1.11.18(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.3))(typescript@5.8.3)': + '@nx/jest@20.7.2(@babel/traverse@7.26.9)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(@types/node@22.14.0)(babel-plugin-macros@3.1.0)(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0))(ts-node@10.9.2(@swc/core@1.11.18(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.3))(typescript@5.8.3)': dependencies: '@jest/reporters': 29.7.0 '@jest/test-result': 29.7.0 '@nx/devkit': 20.7.2(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)) - '@nx/js': 20.7.2(@babel/traverse@7.26.5)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)) + '@nx/js': 20.7.2(@babel/traverse@7.26.9)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)) '@phenomnomnominal/tsquery': 5.0.1(typescript@5.8.3) identity-obj-proxy: 3.0.0 jest-config: 29.7.0(@types/node@22.14.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.18(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.3)) @@ -9365,7 +9447,7 @@ snapshots: minimatch: 9.0.3 picocolors: 1.1.1 resolve.exports: 2.0.3 - semver: 7.6.3 + semver: 7.7.1 tslib: 2.8.1 yargs-parser: 21.1.1 transitivePeerDependencies: @@ -9382,21 +9464,21 @@ snapshots: - typescript - verdaccio - '@nx/js@20.7.2(@babel/traverse@7.26.5)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0))': + '@nx/js@20.7.2(@babel/traverse@7.26.9)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0))': dependencies: - '@babel/core': 7.26.0 - '@babel/plugin-proposal-decorators': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-runtime': 7.25.9(@babel/core@7.26.0) - '@babel/preset-env': 7.26.0(@babel/core@7.26.0) - '@babel/preset-typescript': 7.26.0(@babel/core@7.26.0) - '@babel/runtime': 7.26.0 + '@babel/core': 7.26.9 + '@babel/plugin-proposal-decorators': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-runtime': 7.26.9(@babel/core@7.26.9) + '@babel/preset-env': 7.26.9(@babel/core@7.26.9) + '@babel/preset-typescript': 7.26.0(@babel/core@7.26.9) + '@babel/runtime': 7.26.9 '@nx/devkit': 20.7.2(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)) '@nx/workspace': 20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0) '@zkochan/js-yaml': 0.0.7 - babel-plugin-const-enum: 1.2.0(@babel/core@7.26.0) + babel-plugin-const-enum: 1.2.0(@babel/core@7.26.9) babel-plugin-macros: 3.1.0 - babel-plugin-transform-typescript-metadata: 0.3.2(@babel/core@7.26.0)(@babel/traverse@7.26.5) + babel-plugin-transform-typescript-metadata: 0.3.2(@babel/core@7.26.9)(@babel/traverse@7.26.9) chalk: 4.1.2 columnify: 1.6.0 detect-port: 1.6.1 @@ -9421,12 +9503,12 @@ snapshots: - nx - supports-color - '@nx/node@20.7.2(@babel/traverse@7.26.5)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(@types/node@22.14.0)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(debug@4.4.0)(eslint@9.24.0(jiti@2.4.2))(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0))(ts-node@10.9.2(@swc/core@1.11.18(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.3))(typescript@5.8.3)': + '@nx/node@20.7.2(@babel/traverse@7.26.9)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(@types/node@22.14.0)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(debug@4.4.0)(eslint@9.24.0(jiti@2.4.2))(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0))(ts-node@10.9.2(@swc/core@1.11.18(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.3))(typescript@5.8.3)': dependencies: '@nx/devkit': 20.7.2(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)) - '@nx/eslint': 20.7.2(@babel/traverse@7.26.5)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(@zkochan/js-yaml@0.0.7)(debug@4.4.0)(eslint@9.24.0(jiti@2.4.2))(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)) - '@nx/jest': 20.7.2(@babel/traverse@7.26.5)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(@types/node@22.14.0)(babel-plugin-macros@3.1.0)(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0))(ts-node@10.9.2(@swc/core@1.11.18(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.3))(typescript@5.8.3) - '@nx/js': 20.7.2(@babel/traverse@7.26.5)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)) + '@nx/eslint': 20.7.2(@babel/traverse@7.26.9)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(@zkochan/js-yaml@0.0.7)(debug@4.4.0)(eslint@9.24.0(jiti@2.4.2))(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)) + '@nx/jest': 20.7.2(@babel/traverse@7.26.9)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(@types/node@22.14.0)(babel-plugin-macros@3.1.0)(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0))(ts-node@10.9.2(@swc/core@1.11.18(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.3))(typescript@5.8.3) + '@nx/js': 20.7.2(@babel/traverse@7.26.9)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)) tslib: 2.8.1 transitivePeerDependencies: - '@babel/traverse' @@ -9474,10 +9556,10 @@ snapshots: '@nx/nx-win32-x64-msvc@20.7.2': optional: true - '@nx/vite@20.7.2(@babel/traverse@7.26.5)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0))(typescript@5.8.3)(vite@6.2.5(@types/node@22.14.0)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))(vitest@3.1.1)': + '@nx/vite@20.7.2(@babel/traverse@7.26.9)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0))(typescript@5.8.3)(vite@6.2.5(@types/node@22.14.0)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))(vitest@3.1.1)': dependencies: '@nx/devkit': 20.7.2(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)) - '@nx/js': 20.7.2(@babel/traverse@7.26.5)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)) + '@nx/js': 20.7.2(@babel/traverse@7.26.9)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)) '@phenomnomnominal/tsquery': 5.0.1(typescript@5.8.3) '@swc/helpers': 0.5.15 enquirer: 2.3.6 @@ -9496,10 +9578,10 @@ snapshots: - typescript - verdaccio - '@nx/web@20.7.2(@babel/traverse@7.26.5)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0))': + '@nx/web@20.7.2(@babel/traverse@7.26.9)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0))': dependencies: '@nx/devkit': 20.7.2(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)) - '@nx/js': 20.7.2(@babel/traverse@7.26.5)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)) + '@nx/js': 20.7.2(@babel/traverse@7.26.9)(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)(nx@20.7.2(@swc-node/register@1.10.10(@swc/core@1.11.18(@swc/helpers@0.5.15))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.18(@swc/helpers@0.5.15))(debug@4.4.0)) detect-port: 1.6.1 http-server: 14.1.1(debug@4.4.0) picocolors: 1.1.1 @@ -9625,7 +9707,7 @@ snapshots: '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.28.0 forwarded-parse: 2.1.2 - semver: 7.6.3 + semver: 7.7.1 transitivePeerDependencies: - supports-color @@ -9749,8 +9831,8 @@ snapshots: '@opentelemetry/api-logs': 0.57.2 '@types/shimmer': 1.2.0 import-in-the-middle: 1.13.0 - require-in-the-middle: 7.4.0 - semver: 7.6.3 + require-in-the-middle: 7.5.2 + semver: 7.7.1 shimmer: 1.2.1 transitivePeerDependencies: - supports-color @@ -9837,184 +9919,136 @@ snapshots: '@redux-devtools/extension@3.3.0(redux@4.2.1)': dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.9 immutable: 4.3.7 redux: 4.2.1 '@remirror/core-constants@2.0.2': {} - '@remix-run/router@1.21.0': {} + '@remix-run/router@1.23.0': {} - '@rollup/plugin-babel@6.0.4(@babel/core@7.26.0)(@types/babel__core@7.20.5)(rollup@4.24.0)': + '@rollup/plugin-babel@6.0.4(@babel/core@7.26.9)(@types/babel__core@7.20.5)(rollup@4.34.8)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) - '@rollup/pluginutils': 5.1.4(rollup@4.24.0) + '@rollup/pluginutils': 5.1.4(rollup@4.34.8) optionalDependencies: '@types/babel__core': 7.20.5 - rollup: 4.24.0 + rollup: 4.34.8 transitivePeerDependencies: - supports-color - '@rollup/plugin-commonjs@25.0.8(rollup@4.24.0)': + '@rollup/plugin-commonjs@25.0.8(rollup@4.34.8)': dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.24.0) + '@rollup/pluginutils': 5.1.4(rollup@4.34.8) commondir: 1.0.1 estree-walker: 2.0.2 glob: 8.1.0 is-reference: 1.2.1 magic-string: 0.30.17 optionalDependencies: - rollup: 4.24.0 + rollup: 4.34.8 - '@rollup/plugin-inject@5.0.5(rollup@4.24.0)': + '@rollup/plugin-inject@5.0.5(rollup@4.34.8)': dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.24.0) + '@rollup/pluginutils': 5.1.4(rollup@4.34.8) estree-walker: 2.0.2 magic-string: 0.30.17 optionalDependencies: - rollup: 4.24.0 + rollup: 4.34.8 - '@rollup/plugin-json@6.1.0(rollup@4.24.0)': + '@rollup/plugin-json@6.1.0(rollup@4.34.8)': dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.24.0) + '@rollup/pluginutils': 5.1.4(rollup@4.34.8) optionalDependencies: - rollup: 4.24.0 + rollup: 4.34.8 - '@rollup/plugin-node-resolve@15.3.1(rollup@4.24.0)': + '@rollup/plugin-node-resolve@15.3.1(rollup@4.34.8)': dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.24.0) + '@rollup/pluginutils': 5.1.4(rollup@4.34.8) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-module: 1.0.0 - resolve: 1.22.8 + resolve: 1.22.10 optionalDependencies: - rollup: 4.24.0 + rollup: 4.34.8 - '@rollup/plugin-replace@5.0.7(rollup@4.24.0)': + '@rollup/plugin-replace@5.0.7(rollup@4.34.8)': dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.24.0) + '@rollup/pluginutils': 5.1.4(rollup@4.34.8) magic-string: 0.30.17 optionalDependencies: - rollup: 4.24.0 + rollup: 4.34.8 - '@rollup/pluginutils@5.1.4(rollup@4.24.0)': + '@rollup/pluginutils@5.1.4(rollup@4.34.8)': dependencies: '@types/estree': 1.0.6 estree-walker: 2.0.2 picomatch: 4.0.2 optionalDependencies: - rollup: 4.24.0 - - '@rollup/rollup-android-arm-eabi@4.24.0': - optional: true - - '@rollup/rollup-android-arm-eabi@4.34.3': - optional: true - - '@rollup/rollup-android-arm64@4.24.0': - optional: true - - '@rollup/rollup-android-arm64@4.34.3': - optional: true + rollup: 4.34.8 - '@rollup/rollup-darwin-arm64@4.24.0': + '@rollup/rollup-android-arm-eabi@4.34.8': optional: true - '@rollup/rollup-darwin-arm64@4.34.3': + '@rollup/rollup-android-arm64@4.34.8': optional: true - '@rollup/rollup-darwin-x64@4.24.0': + '@rollup/rollup-darwin-arm64@4.34.8': optional: true - '@rollup/rollup-darwin-x64@4.34.3': + '@rollup/rollup-darwin-x64@4.34.8': optional: true - '@rollup/rollup-freebsd-arm64@4.34.3': + '@rollup/rollup-freebsd-arm64@4.34.8': optional: true - '@rollup/rollup-freebsd-x64@4.34.3': + '@rollup/rollup-freebsd-x64@4.34.8': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.24.0': + '@rollup/rollup-linux-arm-gnueabihf@4.34.8': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.34.3': + '@rollup/rollup-linux-arm-musleabihf@4.34.8': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.24.0': + '@rollup/rollup-linux-arm64-gnu@4.34.8': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.34.3': + '@rollup/rollup-linux-arm64-musl@4.34.8': optional: true - '@rollup/rollup-linux-arm64-gnu@4.24.0': + '@rollup/rollup-linux-loongarch64-gnu@4.34.8': optional: true - '@rollup/rollup-linux-arm64-gnu@4.34.3': + '@rollup/rollup-linux-powerpc64le-gnu@4.34.8': optional: true - '@rollup/rollup-linux-arm64-musl@4.24.0': + '@rollup/rollup-linux-riscv64-gnu@4.34.8': optional: true - '@rollup/rollup-linux-arm64-musl@4.34.3': + '@rollup/rollup-linux-s390x-gnu@4.34.8': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.34.3': + '@rollup/rollup-linux-x64-gnu@4.34.8': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': + '@rollup/rollup-linux-x64-musl@4.34.8': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.34.3': + '@rollup/rollup-win32-arm64-msvc@4.34.8': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.24.0': + '@rollup/rollup-win32-ia32-msvc@4.34.8': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.34.3': + '@rollup/rollup-win32-x64-msvc@4.34.8': optional: true - '@rollup/rollup-linux-s390x-gnu@4.24.0': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.34.3': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.24.0': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.34.3': - optional: true - - '@rollup/rollup-linux-x64-musl@4.24.0': - optional: true - - '@rollup/rollup-linux-x64-musl@4.34.3': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.24.0': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.34.3': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.24.0': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.34.3': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.24.0': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.34.3': - optional: true - - '@sentry-internal/node-cpu-profiler@2.0.0': + '@sentry-internal/node-cpu-profiler@2.1.0': dependencies: detect-libc: 2.0.3 - node-abi: 3.68.0 + node-abi: 3.74.0 '@sentry/cli-darwin@2.43.0': optional: true @@ -10113,7 +10147,7 @@ snapshots: '@sentry/profiling-node@9.12.0': dependencies: - '@sentry-internal/node-cpu-profiler': 2.0.0 + '@sentry-internal/node-cpu-profiler': 2.1.0 '@sentry/core': 9.12.0 '@sentry/node': 9.12.0 transitivePeerDependencies: @@ -10129,6 +10163,54 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 + '@slonik/driver@46.4.0(zod@3.24.2)': + dependencies: + '@slonik/types': 46.4.0(zod@3.24.2) + '@slonik/utilities': 46.4.0(zod@3.24.2) + roarr: 7.21.1 + serialize-error: 8.1.0 + strict-event-emitter-types: 2.0.0 + zod: 3.24.2 + + '@slonik/errors@46.4.0(zod@3.24.2)': + dependencies: + '@slonik/types': 46.4.0(zod@3.24.2) + zod: 3.24.2 + + '@slonik/pg-driver@46.4.0(zod@3.24.2)': + dependencies: + '@slonik/driver': 46.4.0(zod@3.24.2) + '@slonik/errors': 46.4.0(zod@3.24.2) + '@slonik/sql-tag': 46.4.0(zod@3.24.2) + '@slonik/types': 46.4.0(zod@3.24.2) + '@slonik/utilities': 46.4.0(zod@3.24.2) + pg: 8.14.1 + pg-query-stream: 4.7.3(pg@8.14.1) + pg-types: 4.0.2 + postgres-array: 3.0.2 + zod: 3.24.2 + transitivePeerDependencies: + - pg-native + + '@slonik/sql-tag@46.4.0(zod@3.24.2)': + dependencies: + '@slonik/errors': 46.4.0(zod@3.24.2) + '@slonik/types': 46.4.0(zod@3.24.2) + roarr: 7.21.1 + safe-stable-stringify: 2.5.0 + serialize-error: 8.1.0 + zod: 3.24.2 + + '@slonik/types@46.4.0(zod@3.24.2)': + dependencies: + zod: 3.24.2 + + '@slonik/utilities@46.4.0(zod@3.24.2)': + dependencies: + '@slonik/errors': 46.4.0(zod@3.24.2) + '@slonik/types': 46.4.0(zod@3.24.2) + zod: 3.24.2 + '@styled-system/background@5.1.2': dependencies: '@styled-system/core': 5.1.2 @@ -10273,11 +10355,11 @@ snapshots: dependencies: '@tiptap/pm': 2.1.13 - '@tiptap/extension-blockquote@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))': + '@tiptap/extension-blockquote@2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))': dependencies: '@tiptap/core': 2.1.13(@tiptap/pm@2.1.13) - '@tiptap/extension-bold@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))': + '@tiptap/extension-bold@2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))': dependencies: '@tiptap/core': 2.1.13(@tiptap/pm@2.1.13) @@ -10287,18 +10369,16 @@ snapshots: '@tiptap/pm': 2.1.13 tippy.js: 6.3.7 - '@tiptap/extension-bullet-list@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/extension-list-item@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)))(@tiptap/extension-text-style@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)))': + '@tiptap/extension-bullet-list@2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))': dependencies: '@tiptap/core': 2.1.13(@tiptap/pm@2.1.13) - '@tiptap/extension-list-item': 2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) - '@tiptap/extension-text-style': 2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) '@tiptap/extension-character-count@2.1.13(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/pm@2.1.13)': dependencies: '@tiptap/core': 2.1.13(@tiptap/pm@2.1.13) '@tiptap/pm': 2.1.13 - '@tiptap/extension-code-block@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/pm@2.1.13)': + '@tiptap/extension-code-block@2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/pm@2.1.13)': dependencies: '@tiptap/core': 2.1.13(@tiptap/pm@2.1.13) '@tiptap/pm': 2.1.13 @@ -10311,7 +10391,7 @@ snapshots: dependencies: '@tiptap/core': 2.1.13(@tiptap/pm@2.1.13) - '@tiptap/extension-dropcursor@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/pm@2.1.13)': + '@tiptap/extension-dropcursor@2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/pm@2.1.13)': dependencies: '@tiptap/core': 2.1.13(@tiptap/pm@2.1.13) '@tiptap/pm': 2.1.13 @@ -10322,12 +10402,12 @@ snapshots: '@tiptap/pm': 2.1.13 tippy.js: 6.3.7 - '@tiptap/extension-gapcursor@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/pm@2.1.13)': + '@tiptap/extension-gapcursor@2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/pm@2.1.13)': dependencies: '@tiptap/core': 2.1.13(@tiptap/pm@2.1.13) '@tiptap/pm': 2.1.13 - '@tiptap/extension-hard-break@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))': + '@tiptap/extension-hard-break@2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))': dependencies: '@tiptap/core': 2.1.13(@tiptap/pm@2.1.13) @@ -10335,12 +10415,12 @@ snapshots: dependencies: '@tiptap/core': 2.1.13(@tiptap/pm@2.1.13) - '@tiptap/extension-history@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/pm@2.1.13)': + '@tiptap/extension-history@2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/pm@2.1.13)': dependencies: '@tiptap/core': 2.1.13(@tiptap/pm@2.1.13) '@tiptap/pm': 2.1.13 - '@tiptap/extension-horizontal-rule@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/pm@2.1.13)': + '@tiptap/extension-horizontal-rule@2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/pm@2.1.13)': dependencies: '@tiptap/core': 2.1.13(@tiptap/pm@2.1.13) '@tiptap/pm': 2.1.13 @@ -10349,7 +10429,7 @@ snapshots: dependencies: '@tiptap/core': 2.1.13(@tiptap/pm@2.1.13) - '@tiptap/extension-italic@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))': + '@tiptap/extension-italic@2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))': dependencies: '@tiptap/core': 2.1.13(@tiptap/pm@2.1.13) @@ -10359,21 +10439,19 @@ snapshots: '@tiptap/pm': 2.1.13 linkifyjs: 4.2.0 - '@tiptap/extension-list-item@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))': + '@tiptap/extension-list-item@2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))': dependencies: '@tiptap/core': 2.1.13(@tiptap/pm@2.1.13) - '@tiptap/extension-ordered-list@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/extension-list-item@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)))(@tiptap/extension-text-style@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)))': + '@tiptap/extension-ordered-list@2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))': dependencies: '@tiptap/core': 2.1.13(@tiptap/pm@2.1.13) - '@tiptap/extension-list-item': 2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) - '@tiptap/extension-text-style': 2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) - '@tiptap/extension-paragraph@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))': + '@tiptap/extension-paragraph@2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))': dependencies: '@tiptap/core': 2.1.13(@tiptap/pm@2.1.13) - '@tiptap/extension-strike@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))': + '@tiptap/extension-strike@2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))': dependencies: '@tiptap/core': 2.1.13(@tiptap/pm@2.1.13) @@ -10398,10 +10476,6 @@ snapshots: dependencies: '@tiptap/core': 2.1.13(@tiptap/pm@2.1.13) - '@tiptap/extension-text-style@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))': - dependencies: - '@tiptap/core': 2.1.13(@tiptap/pm@2.1.13) - '@tiptap/extension-text@2.1.13(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))': dependencies: '@tiptap/core': 2.1.13(@tiptap/pm@2.1.13) @@ -10414,7 +10488,7 @@ snapshots: dependencies: prosemirror-changeset: 2.2.1 prosemirror-collab: 1.3.1 - prosemirror-commands: 1.6.1 + prosemirror-commands: 1.7.0 prosemirror-dropcursor: 1.8.1 prosemirror-gapcursor: 1.3.2 prosemirror-history: 1.4.1 @@ -10422,14 +10496,14 @@ snapshots: prosemirror-keymap: 1.2.2 prosemirror-markdown: 1.13.1 prosemirror-menu: 1.2.4 - prosemirror-model: 1.23.0 + prosemirror-model: 1.24.1 prosemirror-schema-basic: 1.2.3 - prosemirror-schema-list: 1.4.1 + prosemirror-schema-list: 1.5.0 prosemirror-state: 1.4.3 - prosemirror-tables: 1.5.0 - prosemirror-trailing-node: 2.0.9(prosemirror-model@1.23.0)(prosemirror-state@1.4.3)(prosemirror-view@1.34.3) + prosemirror-tables: 1.6.4 + prosemirror-trailing-node: 2.0.9(prosemirror-model@1.24.1)(prosemirror-state@1.4.3)(prosemirror-view@1.38.0) prosemirror-transform: 1.10.2 - prosemirror-view: 1.34.3 + prosemirror-view: 1.38.0 '@tiptap/react@2.1.13(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/pm@2.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -10440,34 +10514,33 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@tiptap/starter-kit@2.1.13(@tiptap/extension-text-style@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)))(@tiptap/pm@2.1.13)': + '@tiptap/starter-kit@2.1.13(@tiptap/pm@2.1.13)': dependencies: '@tiptap/core': 2.1.13(@tiptap/pm@2.1.13) - '@tiptap/extension-blockquote': 2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) - '@tiptap/extension-bold': 2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) - '@tiptap/extension-bullet-list': 2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/extension-list-item@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)))(@tiptap/extension-text-style@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))) + '@tiptap/extension-blockquote': 2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) + '@tiptap/extension-bold': 2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) + '@tiptap/extension-bullet-list': 2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) '@tiptap/extension-code': 2.1.13(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) - '@tiptap/extension-code-block': 2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/pm@2.1.13) + '@tiptap/extension-code-block': 2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/pm@2.1.13) '@tiptap/extension-document': 2.1.13(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) - '@tiptap/extension-dropcursor': 2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/pm@2.1.13) - '@tiptap/extension-gapcursor': 2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/pm@2.1.13) - '@tiptap/extension-hard-break': 2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) + '@tiptap/extension-dropcursor': 2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/pm@2.1.13) + '@tiptap/extension-gapcursor': 2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/pm@2.1.13) + '@tiptap/extension-hard-break': 2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) '@tiptap/extension-heading': 2.1.13(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) - '@tiptap/extension-history': 2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/pm@2.1.13) - '@tiptap/extension-horizontal-rule': 2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/pm@2.1.13) - '@tiptap/extension-italic': 2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) - '@tiptap/extension-list-item': 2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) - '@tiptap/extension-ordered-list': 2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/extension-list-item@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)))(@tiptap/extension-text-style@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))) - '@tiptap/extension-paragraph': 2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) - '@tiptap/extension-strike': 2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) + '@tiptap/extension-history': 2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/pm@2.1.13) + '@tiptap/extension-horizontal-rule': 2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13))(@tiptap/pm@2.1.13) + '@tiptap/extension-italic': 2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) + '@tiptap/extension-list-item': 2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) + '@tiptap/extension-ordered-list': 2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) + '@tiptap/extension-paragraph': 2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) + '@tiptap/extension-strike': 2.11.5(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) '@tiptap/extension-text': 2.1.13(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)) transitivePeerDependencies: - - '@tiptap/extension-text-style' - '@tiptap/pm' - '@ts-morph/common@0.26.0': + '@ts-morph/common@0.26.1': dependencies: - fast-glob: 3.3.2 + fast-glob: 3.3.3 minimatch: 9.0.5 path-browserify: 1.0.1 @@ -10489,24 +10562,24 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.26.5 - '@babel/types': 7.26.5 + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.6 '@types/babel__generator@7.6.8': dependencies: - '@babel/types': 7.26.5 + '@babel/types': 7.26.9 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.26.5 - '@babel/types': 7.26.5 + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 '@types/babel__traverse@7.20.6': dependencies: - '@babel/types': 7.26.5 + '@babel/types': 7.26.9 '@types/bcrypt@5.0.2': dependencies: @@ -10520,7 +10593,7 @@ snapshots: dependencies: '@types/node': 22.14.0 - '@types/conventional-commits-parser@5.0.0': + '@types/conventional-commits-parser@5.0.1': dependencies: '@types/node': 22.14.0 @@ -10542,7 +10615,7 @@ snapshots: '@types/hoist-non-react-statics@3.3.6': dependencies: - '@types/react': 19.0.4 + '@types/react': 19.0.10 hoist-non-react-statics: 3.3.2 '@types/istanbul-lib-coverage@2.0.6': {} @@ -10574,7 +10647,7 @@ snapshots: dependencies: '@types/node': 22.14.0 - '@types/node@22.10.10': + '@types/node@22.13.5': dependencies: undici-types: 6.20.0 @@ -10592,8 +10665,8 @@ snapshots: '@types/pg@8.11.11': dependencies: - '@types/node': 22.10.10 - pg-protocol: 1.7.0 + '@types/node': 22.13.5 + pg-protocol: 1.7.1 pg-types: 4.0.2 '@types/pg@8.6.1': @@ -10602,11 +10675,11 @@ snapshots: pg-protocol: 1.8.0 pg-types: 2.2.0 - '@types/react-transition-group@4.4.12(@types/react@19.0.4)': + '@types/react-transition-group@4.4.12(@types/react@19.0.10)': dependencies: - '@types/react': 19.0.4 + '@types/react': 19.0.10 - '@types/react@19.0.4': + '@types/react@19.0.10': dependencies: csstype: 3.1.3 @@ -10716,7 +10789,7 @@ snapshots: globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.6.3 + semver: 7.7.1 ts-api-utils: 1.3.0(typescript@5.8.3) optionalDependencies: typescript: 5.8.3 @@ -10728,7 +10801,7 @@ snapshots: '@typescript-eslint/types': 8.29.1 '@typescript-eslint/visitor-keys': 8.29.1 debug: 4.4.0(supports-color@5.5.0) - fast-glob: 3.3.2 + fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.6.3 @@ -10859,9 +10932,7 @@ snapshots: acorn-walk@8.3.4: dependencies: - acorn: 8.12.1 - - acorn@8.12.1: {} + acorn: 8.14.0 acorn@8.14.0: {} @@ -10869,53 +10940,52 @@ snapshots: address@1.2.2: {} - adminjs@7.8.15(@tiptap/extension-text-style@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)))(@types/babel__core@7.20.5)(@types/react@19.0.4)(debug@4.4.0): - dependencies: - '@adminjs/design-system': 4.1.1(@babel/core@7.26.0)(@tiptap/extension-text-style@2.8.0(@tiptap/core@2.1.13(@tiptap/pm@2.1.13)))(@types/react@19.0.4)(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1) - '@babel/core': 7.26.0 - '@babel/parser': 7.26.5 - '@babel/plugin-syntax-import-assertions': 7.26.0(@babel/core@7.26.0) - '@babel/plugin-transform-runtime': 7.25.9(@babel/core@7.26.0) - '@babel/preset-env': 7.26.0(@babel/core@7.26.0) - '@babel/preset-react': 7.26.3(@babel/core@7.26.0) - '@babel/preset-typescript': 7.26.0(@babel/core@7.26.0) - '@babel/register': 7.25.9(@babel/core@7.26.0) - '@hello-pangea/dnd': 16.6.0(@types/react@19.0.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + adminjs@7.8.15(@types/babel__core@7.20.5)(@types/react@19.0.10)(debug@4.4.0): + dependencies: + '@adminjs/design-system': 4.1.1(@babel/core@7.26.9)(@types/react@19.0.10)(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1) + '@babel/core': 7.26.9 + '@babel/parser': 7.26.9 + '@babel/plugin-syntax-import-assertions': 7.26.0(@babel/core@7.26.9) + '@babel/plugin-transform-runtime': 7.26.9(@babel/core@7.26.9) + '@babel/preset-env': 7.26.9(@babel/core@7.26.9) + '@babel/preset-react': 7.26.3(@babel/core@7.26.9) + '@babel/preset-typescript': 7.26.0(@babel/core@7.26.9) + '@babel/register': 7.25.9(@babel/core@7.26.9) + '@hello-pangea/dnd': 16.6.0(@types/react@19.0.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@redux-devtools/extension': 3.3.0(redux@4.2.1) - '@rollup/plugin-babel': 6.0.4(@babel/core@7.26.0)(@types/babel__core@7.20.5)(rollup@4.24.0) - '@rollup/plugin-commonjs': 25.0.8(rollup@4.24.0) - '@rollup/plugin-json': 6.1.0(rollup@4.24.0) - '@rollup/plugin-node-resolve': 15.3.1(rollup@4.24.0) - '@rollup/plugin-replace': 5.0.7(rollup@4.24.0) - axios: 1.7.9(debug@4.4.0) + '@rollup/plugin-babel': 6.0.4(@babel/core@7.26.9)(@types/babel__core@7.20.5)(rollup@4.34.8) + '@rollup/plugin-commonjs': 25.0.8(rollup@4.34.8) + '@rollup/plugin-json': 6.1.0(rollup@4.34.8) + '@rollup/plugin-node-resolve': 15.3.1(rollup@4.34.8) + '@rollup/plugin-replace': 5.0.7(rollup@4.34.8) + axios: 1.8.1(debug@4.4.0) commander: 10.0.1 flat: 5.0.2 i18next: 22.5.1 i18next-browser-languagedetector: 7.2.2 - i18next-http-backend: 2.7.1 + i18next-http-backend: 2.7.3 lodash: 4.17.21 ora: 6.3.1 prop-types: 15.8.1 punycode: 2.3.1 - qs: 6.13.1 + qs: 6.14.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-feather: 2.0.10(react@18.3.1) react-i18next: 12.3.1(i18next@22.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-is: 18.3.1 - react-redux: 8.1.3(@types/react@19.0.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@4.2.1) - react-router: 6.28.1(react@18.3.1) - react-router-dom: 6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-redux: 8.1.3(@types/react@19.0.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@4.2.1) + react-router: 6.30.0(react@18.3.1) + react-router-dom: 6.30.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) redux: 4.2.1 regenerator-runtime: 0.14.1 - rollup: 4.24.0 - rollup-plugin-esbuild-minify: 1.2.0(rollup@4.24.0) - rollup-plugin-polyfill-node: 0.13.0(rollup@4.24.0) + rollup: 4.34.8 + rollup-plugin-esbuild-minify: 1.2.0(rollup@4.34.8) + rollup-plugin-polyfill-node: 0.13.0(rollup@4.34.8) slash: 5.1.0 uuid: 9.0.1 xss: 1.0.15 transitivePeerDependencies: - - '@tiptap/extension-text-style' - '@types/babel__core' - '@types/react' - '@types/react-dom' @@ -11000,10 +11070,10 @@ snapshots: argparse@2.0.1: {} - array-buffer-byte-length@1.0.1: + array-buffer-byte-length@1.0.2: dependencies: - call-bind: 1.0.7 - is-array-buffer: 3.0.4 + call-bound: 1.0.3 + is-array-buffer: 3.0.5 array-ify@1.0.0: {} @@ -11011,7 +11081,7 @@ snapshots: arrify@1.0.1: {} - assert-never@1.3.0: {} + assert-never@1.4.0: {} assertion-error@2.0.1: {} @@ -11027,17 +11097,17 @@ snapshots: available-typed-arrays@1.0.7: dependencies: - possible-typed-array-names: 1.0.0 + possible-typed-array-names: 1.1.0 avvio@9.0.0: dependencies: '@fastify/error': 4.0.0 fastq: 1.17.1 - axios@1.7.9(debug@4.4.0): + axios@1.8.1(debug@4.4.0): dependencies: follow-redirects: 1.15.9(debug@4.4.0) - form-data: 4.0.1 + form-data: 4.0.2 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug @@ -11045,30 +11115,30 @@ snapshots: axios@1.8.3(debug@4.4.0): dependencies: follow-redirects: 1.15.9(debug@4.4.0) - form-data: 4.0.1 + form-data: 4.0.2 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug - babel-jest@29.7.0(@babel/core@7.26.0): + babel-jest@29.7.0(@babel/core@7.26.9): dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@jest/transform': 29.7.0 '@types/babel__core': 7.20.5 babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.26.0) + babel-preset-jest: 29.6.3(@babel/core@7.26.9) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 transitivePeerDependencies: - supports-color - babel-plugin-const-enum@1.2.0(@babel/core@7.26.0): + babel-plugin-const-enum@1.2.0(@babel/core@7.26.9): dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0) - '@babel/traverse': 7.26.5(supports-color@5.5.0) + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.9) + '@babel/traverse': 7.26.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -11084,84 +11154,92 @@ snapshots: babel-plugin-jest-hoist@29.6.3: dependencies: - '@babel/template': 7.25.9 - '@babel/types': 7.26.5 + '@babel/template': 7.26.9 + '@babel/types': 7.26.9 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.6 babel-plugin-macros@3.1.0: dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.9 cosmiconfig: 7.1.0 - resolve: 1.22.8 + resolve: 1.22.10 - babel-plugin-polyfill-corejs2@0.4.12(@babel/core@7.26.0): + babel-plugin-polyfill-corejs2@0.4.12(@babel/core@7.26.9): dependencies: - '@babel/compat-data': 7.26.5 - '@babel/core': 7.26.0 - '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.0) + '@babel/compat-data': 7.26.8 + '@babel/core': 7.26.9 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.9) semver: 6.3.1 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.10.6(@babel/core@7.26.0): + babel-plugin-polyfill-corejs3@0.10.6(@babel/core@7.26.9): dependencies: - '@babel/core': 7.26.0 - '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.0) + '@babel/core': 7.26.9 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.9) core-js-compat: 3.40.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.3(@babel/core@7.26.0): + babel-plugin-polyfill-corejs3@0.11.1(@babel/core@7.26.9): dependencies: - '@babel/core': 7.26.0 - '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.0) + '@babel/core': 7.26.9 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.9) + core-js-compat: 3.40.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.3(@babel/core@7.26.9): + dependencies: + '@babel/core': 7.26.9 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.9) transitivePeerDependencies: - supports-color - babel-plugin-styled-components@2.1.4(@babel/core@7.26.0)(styled-components@5.3.9(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1))(supports-color@5.5.0): + babel-plugin-styled-components@2.1.4(@babel/core@7.26.9)(styled-components@5.3.9(@babel/core@7.26.9)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1))(supports-color@5.5.0): dependencies: '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) - '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.9) lodash: 4.17.21 picomatch: 2.3.1 - styled-components: 5.3.9(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1) + styled-components: 5.3.9(@babel/core@7.26.9)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1) transitivePeerDependencies: - '@babel/core' - supports-color - babel-plugin-transform-typescript-metadata@0.3.2(@babel/core@7.26.0)(@babel/traverse@7.26.5): + babel-plugin-transform-typescript-metadata@0.3.2(@babel/core@7.26.9)(@babel/traverse@7.26.9): dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 optionalDependencies: - '@babel/traverse': 7.26.5(supports-color@5.5.0) - - babel-preset-current-node-syntax@1.1.0(@babel/core@7.26.0): - dependencies: - '@babel/core': 7.26.0 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.26.0) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.26.0) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.26.0) - '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.0) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.26.0) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.26.0) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.26.0) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.0) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.26.0) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.26.0) - - babel-preset-jest@29.6.3(@babel/core@7.26.0): - dependencies: - '@babel/core': 7.26.0 + '@babel/traverse': 7.26.9(supports-color@5.5.0) + + babel-preset-current-node-syntax@1.1.0(@babel/core@7.26.9): + dependencies: + '@babel/core': 7.26.9 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.26.9) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.26.9) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.26.9) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.26.9) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.9) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.26.9) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.26.9) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.26.9) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.26.9) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.26.9) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.26.9) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.26.9) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.9) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.26.9) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.26.9) + + babel-preset-jest@29.6.3(@babel/core@7.26.9): + dependencies: + '@babel/core': 7.26.9 babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.0) + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.9) balanced-match@1.0.2: {} @@ -11206,10 +11284,10 @@ snapshots: browserslist@4.24.4: dependencies: - caniuse-lite: 1.0.30001692 - electron-to-chromium: 1.5.80 + caniuse-lite: 1.0.30001701 + electron-to-chromium: 1.5.108 node-releases: 2.0.19 - update-browserslist-db: 1.1.2(browserslist@4.24.4) + update-browserslist-db: 1.1.3(browserslist@4.24.4) bser@2.1.1: dependencies: @@ -11229,14 +11307,23 @@ snapshots: cac@6.7.14: {} - call-bind@1.0.7: + call-bind-apply-helpers@1.0.2: dependencies: - es-define-property: 1.0.0 es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.4 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 set-function-length: 1.2.2 + call-bound@1.0.3: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + callsites@3.1.0: {} camelcase-keys@6.2.2: @@ -11253,7 +11340,7 @@ snapshots: camelize@1.0.1: {} - caniuse-lite@1.0.30001692: {} + caniuse-lite@1.0.30001701: {} chai@5.2.0: dependencies: @@ -11294,7 +11381,7 @@ snapshots: ci-info@3.9.0: {} - cjs-module-lexer@1.4.1: {} + cjs-module-lexer@1.4.3: {} classnames@2.5.1: {} @@ -11555,14 +11642,14 @@ snapshots: cosmiconfig@7.1.0: dependencies: '@types/parse-json': 4.0.2 - import-fresh: 3.3.0 + import-fresh: 3.3.1 parse-json: 5.2.0 path-type: 4.0.0 yaml: 1.10.2 cosmiconfig@8.3.6(typescript@5.8.3): dependencies: - import-fresh: 3.3.0 + import-fresh: 3.3.1 js-yaml: 4.1.0 parse-json: 5.2.0 path-type: 4.0.0 @@ -11572,7 +11659,7 @@ snapshots: cosmiconfig@9.0.0(typescript@5.8.3): dependencies: env-paths: 2.2.1 - import-fresh: 3.3.0 + import-fresh: 3.3.1 js-yaml: 4.1.0 parse-json: 5.2.0 optionalDependencies: @@ -11588,7 +11675,7 @@ snapshots: transitivePeerDependencies: - encoding - cross-spawn@6.0.5: + cross-spawn@6.0.6: dependencies: nice-try: 1.0.5 path-key: 2.0.1 @@ -11596,12 +11683,6 @@ snapshots: shebang-command: 1.2.0 which: 1.3.1 - cross-spawn@7.0.3: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -11632,7 +11713,7 @@ snapshots: date-fns@2.30.0: dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.9 dateformat@3.0.3: {} @@ -11667,24 +11748,24 @@ snapshots: deep-equal@2.2.3: dependencies: - array-buffer-byte-length: 1.0.1 - call-bind: 1.0.7 + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 es-get-iterator: 1.1.3 - get-intrinsic: 1.2.4 - is-arguments: 1.1.1 - is-array-buffer: 3.0.4 - is-date-object: 1.0.5 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.3 + get-intrinsic: 1.3.0 + is-arguments: 1.2.0 + is-array-buffer: 3.0.5 + is-date-object: 1.1.0 + is-regex: 1.2.1 + is-shared-array-buffer: 1.0.4 isarray: 2.0.5 object-is: 1.1.6 object-keys: 1.1.1 - object.assign: 4.1.5 - regexp.prototype.flags: 1.5.3 - side-channel: 1.0.6 - which-boxed-primitive: 1.0.2 + object.assign: 4.1.7 + regexp.prototype.flags: 1.5.4 + side-channel: 1.1.0 + which-boxed-primitive: 1.1.1 which-collection: 1.0.2 - which-typed-array: 1.1.15 + which-typed-array: 1.1.18 deep-is@0.1.4: {} @@ -11696,9 +11777,9 @@ snapshots: define-data-property@1.1.4: dependencies: - es-define-property: 1.0.0 + es-define-property: 1.0.1 es-errors: 1.3.0 - gopd: 1.0.1 + gopd: 1.2.0 define-lazy-prop@2.0.0: {} @@ -11745,7 +11826,7 @@ snapshots: dom-helpers@5.2.1: dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.9 csstype: 3.1.3 dot-prop@5.3.0: @@ -11765,20 +11846,26 @@ snapshots: dottie@2.0.6: {} + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + eastasianwidth@0.2.0: {} - eciesjs@0.4.11: + eciesjs@0.4.14: dependencies: - '@ecies/ciphers': 0.2.1(@noble/ciphers@1.0.0) - '@noble/ciphers': 1.0.0 - '@noble/curves': 1.6.0 - '@noble/hashes': 1.5.0 + '@ecies/ciphers': 0.2.2(@noble/ciphers@1.2.1) + '@noble/ciphers': 1.2.1 + '@noble/curves': 1.8.1 + '@noble/hashes': 1.7.1 ejs@3.1.10: dependencies: jake: 10.9.2 - electron-to-chromium@1.5.80: {} + electron-to-chromium@1.5.108: {} emittery@0.13.1: {} @@ -11806,26 +11893,35 @@ snapshots: dependencies: is-arrayish: 0.2.1 - es-define-property@1.0.0: - dependencies: - get-intrinsic: 1.2.4 + es-define-property@1.0.1: {} es-errors@1.3.0: {} es-get-iterator@1.1.3: dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - has-symbols: 1.0.3 - is-arguments: 1.1.1 + call-bind: 1.0.8 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + is-arguments: 1.2.0 is-map: 2.0.3 is-set: 2.0.3 - is-string: 1.0.7 + is-string: 1.1.1 isarray: 2.0.5 - stop-iteration-iterator: 1.0.0 + stop-iteration-iterator: 1.1.0 es-module-lexer@1.6.0: {} + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + esbuild@0.24.2: optionalDependencies: '@esbuild/aix-ppc64': 0.24.2 @@ -12090,7 +12186,7 @@ snapshots: fast-diff@1.3.0: {} - fast-glob@3.3.2: + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 '@nodelib/fs.walk': 1.2.8 @@ -12112,6 +12208,8 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-printf@1.6.10: {} + fast-querystring@1.1.2: dependencies: fast-decode-uri-component: 1.0.1 @@ -12148,6 +12246,10 @@ snapshots: dependencies: reusify: 1.0.4 + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + fb-watchman@2.0.2: dependencies: bser: 2.1.1 @@ -12235,19 +12337,20 @@ snapshots: optionalDependencies: debug: 4.4.0(supports-color@5.5.0) - for-each@0.3.3: + for-each@0.3.5: dependencies: is-callable: 1.2.7 - foreground-child@3.3.0: + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 signal-exit: 4.1.0 - form-data@4.0.1: + form-data@4.0.2: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 mime-types: 2.1.35 forwarded-parse@2.1.2: {} @@ -12260,6 +12363,12 @@ snapshots: fs-constants@1.0.0: {} + fs-extra@11.2.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + fs-extra@11.3.0: dependencies: graceful-fs: 4.2.11 @@ -12297,15 +12406,20 @@ snapshots: get-caller-file@2.0.5: {} - get-east-asian-width@1.2.0: {} + get-east-asian-width@1.3.0: {} - get-intrinsic@1.2.4: + get-intrinsic@1.3.0: dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 es-errors: 1.3.0 + es-object-atoms: 1.1.1 function-bind: 1.1.2 - has-proto: 1.0.3 - has-symbols: 1.0.3 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 hasown: 2.0.2 + math-intrinsics: 1.1.0 get-package-type@0.1.0: {} @@ -12316,11 +12430,20 @@ snapshots: through2: 2.0.5 yargs: 16.2.0 + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stack-trace@3.1.1: + dependencies: + stacktrace-parser: 0.1.11 + get-stream@6.0.1: {} get-stream@8.0.1: {} - get-tsconfig@4.8.1: + get-tsconfig@4.10.0: dependencies: resolve-pkg-maps: 1.0.0 @@ -12364,17 +12487,17 @@ snapshots: glob@10.4.5: dependencies: - foreground-child: 3.3.0 + foreground-child: 3.3.1 jackspeak: 3.4.3 minimatch: 9.0.5 minipass: 7.1.2 package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - glob@11.0.0: + glob@11.0.1: dependencies: - foreground-child: 3.3.0 - jackspeak: 4.0.2 + foreground-child: 3.3.1 + jackspeak: 4.1.0 minimatch: 10.0.1 minipass: 7.1.2 package-json-from-dist: 1.0.1 @@ -12415,14 +12538,12 @@ snapshots: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 - fast-glob: 3.3.2 + fast-glob: 3.3.3 ignore: 5.3.2 merge2: 1.4.1 slash: 3.0.0 - gopd@1.0.1: - dependencies: - get-intrinsic: 1.2.4 + gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -12447,7 +12568,7 @@ snapshots: dependencies: ansi-regex: 2.1.1 - has-bigints@1.0.2: {} + has-bigints@1.1.0: {} has-flag@3.0.0: {} @@ -12455,15 +12576,13 @@ snapshots: has-property-descriptors@1.0.2: dependencies: - es-define-property: 1.0.0 + es-define-property: 1.0.1 - has-proto@1.0.3: {} - - has-symbols@1.0.3: {} + has-symbols@1.1.0: {} has-tostringtag@1.0.2: dependencies: - has-symbols: 1.0.3 + has-symbols: 1.1.0 has-unicode@2.0.1: {} @@ -12526,7 +12645,7 @@ snapshots: mime: 1.6.0 minimist: 1.2.8 opener: 1.5.2 - portfinder: 1.0.32 + portfinder: 1.0.33 secure-compare: 3.0.1 union: 0.5.0 url-join: 4.0.1 @@ -12545,13 +12664,11 @@ snapshots: human-signals@5.0.0: {} - husky@9.1.7: {} - i18next-browser-languagedetector@7.2.2: dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.9 - i18next-http-backend@2.7.1: + i18next-http-backend@2.7.3: dependencies: cross-fetch: 4.0.0 transitivePeerDependencies: @@ -12559,7 +12676,7 @@ snapshots: i18next@22.5.1: dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.9 iconv-lite@0.4.24: dependencies: @@ -12579,7 +12696,7 @@ snapshots: immutable@4.3.7: {} - import-fresh@3.3.0: + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 @@ -12588,7 +12705,7 @@ snapshots: dependencies: acorn: 8.14.0 acorn-import-attributes: 1.9.5(acorn@8.14.0) - cjs-module-lexer: 1.4.1 + cjs-module-lexer: 1.4.3 module-details-from-path: 1.0.3 import-meta-resolve@4.1.0: {} @@ -12612,24 +12729,24 @@ snapshots: inquirer@9.3.7: dependencies: - '@inquirer/figures': 1.0.7 + '@inquirer/figures': 1.0.10 ansi-escapes: 4.3.2 cli-width: 4.1.0 external-editor: 3.1.0 mute-stream: 1.0.0 ora: 5.4.1 run-async: 3.0.0 - rxjs: 7.8.1 + rxjs: 7.8.2 string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.2 - internal-slot@1.0.7: + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 hasown: 2.0.2 - side-channel: 1.0.6 + side-channel: 1.1.0 interpret@2.2.0: {} @@ -12639,35 +12756,37 @@ snapshots: ipaddr.js@2.2.0: {} - is-arguments@1.1.1: + is-arguments@1.2.0: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 has-tostringtag: 1.0.2 - is-array-buffer@3.0.4: + is-array-buffer@3.0.5: dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 + call-bind: 1.0.8 + call-bound: 1.0.3 + get-intrinsic: 1.3.0 is-arrayish@0.2.1: {} - is-bigint@1.0.4: + is-bigint@1.1.0: dependencies: - has-bigints: 1.0.2 + has-bigints: 1.1.0 - is-boolean-object@1.1.2: + is-boolean-object@1.2.2: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 has-tostringtag: 1.0.2 is-callable@1.2.7: {} - is-core-module@2.15.1: + is-core-module@2.16.1: dependencies: hasown: 2.0.2 - is-date-object@1.0.5: + is-date-object@1.1.0: dependencies: + call-bound: 1.0.3 has-tostringtag: 1.0.2 is-docker@2.2.1: {} @@ -12680,7 +12799,7 @@ snapshots: is-fullwidth-code-point@5.0.0: dependencies: - get-east-asian-width: 1.2.0 + get-east-asian-width: 1.3.0 is-generator-fn@2.1.0: {} @@ -12696,8 +12815,9 @@ snapshots: is-module@1.0.0: {} - is-number-object@1.0.7: + is-number-object@1.1.1: dependencies: + call-bound: 1.0.3 has-tostringtag: 1.0.2 is-number@7.0.0: {} @@ -12718,28 +12838,33 @@ snapshots: dependencies: '@types/estree': 1.0.6 - is-regex@1.1.4: + is-regex@1.2.1: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 + gopd: 1.2.0 has-tostringtag: 1.0.2 + hasown: 2.0.2 is-set@2.0.3: {} - is-shared-array-buffer@1.0.3: + is-shared-array-buffer@1.0.4: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 is-stream@2.0.1: {} is-stream@3.0.0: {} - is-string@1.0.7: + is-string@1.1.1: dependencies: + call-bound: 1.0.3 has-tostringtag: 1.0.2 - is-symbol@1.0.4: + is-symbol@1.1.1: dependencies: - has-symbols: 1.0.3 + call-bound: 1.0.3 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 is-text-path@1.0.1: dependencies: @@ -12755,10 +12880,10 @@ snapshots: is-weakmap@2.0.2: {} - is-weakset@2.0.3: + is-weakset@2.0.4: dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 + call-bound: 1.0.3 + get-intrinsic: 1.3.0 is-wsl@2.2.0: dependencies: @@ -12772,14 +12897,16 @@ snapshots: isexe@3.1.1: {} + iso8601-duration@1.3.0: {} + isobject@3.0.1: {} istanbul-lib-coverage@3.2.2: {} istanbul-lib-instrument@5.2.1: dependencies: - '@babel/core': 7.26.0 - '@babel/parser': 7.26.5 + '@babel/core': 7.26.9 + '@babel/parser': 7.26.9 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 @@ -12788,11 +12915,11 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.26.0 - '@babel/parser': 7.26.5 + '@babel/core': 7.26.9 + '@babel/parser': 7.26.9 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.6.3 + semver: 7.7.1 transitivePeerDependencies: - supports-color @@ -12829,7 +12956,7 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jackspeak@4.0.2: + jackspeak@4.1.0: dependencies: '@isaacs/cliui': 8.0.2 @@ -12868,10 +12995,10 @@ snapshots: jest-config@29.7.0(@types/node@22.14.0)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.11.18(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.3)): dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.26.9 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.0) + babel-jest: 29.7.0(@babel/core@7.26.9) chalk: 4.1.2 ci-info: 3.9.0 deepmerge: 4.3.1 @@ -12987,7 +13114,7 @@ snapshots: jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) jest-util: 29.7.0 jest-validate: 29.7.0 - resolve: 1.22.8 + resolve: 1.22.10 resolve.exports: 2.0.3 slash: 3.0.0 @@ -13028,7 +13155,7 @@ snapshots: '@jest/types': 29.6.3 '@types/node': 22.14.0 chalk: 4.1.2 - cjs-module-lexer: 1.4.1 + cjs-module-lexer: 1.4.3 collect-v8-coverage: 1.0.2 glob: 7.2.3 graceful-fs: 4.2.11 @@ -13046,15 +13173,15 @@ snapshots: jest-snapshot@29.7.0: dependencies: - '@babel/core': 7.26.0 - '@babel/generator': 7.26.5 - '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0) - '@babel/types': 7.26.5 + '@babel/core': 7.26.9 + '@babel/generator': 7.26.9 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.9) + '@babel/types': 7.26.9 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.0) + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.9) chalk: 4.1.2 expect: 29.7.0 graceful-fs: 4.2.11 @@ -13065,7 +13192,7 @@ snapshots: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.6.3 + semver: 7.7.1 transitivePeerDependencies: - supports-color @@ -13324,7 +13451,7 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.0.1: {} + lru-cache@11.0.2: {} lru-cache@5.1.1: dependencies: @@ -13340,8 +13467,8 @@ snapshots: magicast@0.3.5: dependencies: - '@babel/parser': 7.26.5 - '@babel/types': 7.26.5 + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 source-map-js: 1.2.1 make-dir@2.1.0: @@ -13355,7 +13482,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.6.3 + semver: 7.7.1 make-error@1.3.6: {} @@ -13376,6 +13503,8 @@ snapshots: punycode.js: 2.3.1 uc.micro: 2.1.0 + math-intrinsics@1.1.0: {} + mdurl@2.0.0: {} media-typer@0.3.0: {} @@ -13409,6 +13538,10 @@ snapshots: mikro-orm@6.4.12: {} + mikro-orm@6.4.3: {} + + mikro-orm@6.4.7: {} + mime-db@1.52.0: {} mime-types@2.1.35: @@ -13474,7 +13607,7 @@ snapshots: modern-spawn@1.0.0: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 modify-values@1.0.1: {} @@ -13486,7 +13619,7 @@ snapshots: moment@2.30.1: {} - mrmime@2.0.0: {} + mrmime@2.0.1: {} ms@2.1.2: {} @@ -13508,9 +13641,9 @@ snapshots: nice-try@1.0.5: {} - node-abi@3.68.0: + node-abi@3.74.0: dependencies: - semver: 7.6.3 + semver: 7.7.1 node-addon-api@5.1.0: {} @@ -13531,15 +13664,15 @@ snapshots: normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.8 + resolve: 1.22.10 semver: 5.7.2 validate-npm-package-license: 3.0.4 normalize-package-data@3.0.3: dependencies: hosted-git-info: 4.1.0 - is-core-module: 2.15.1 - semver: 7.6.3 + is-core-module: 2.16.1 + semver: 7.7.1 validate-npm-package-license: 3.0.4 normalize-path@3.0.0: {} @@ -13548,7 +13681,7 @@ snapshots: dependencies: hosted-git-info: 7.0.2 proc-log: 3.0.0 - semver: 7.6.3 + semver: 7.7.1 validate-npm-package-name: 5.0.1 npm-run-path@4.0.1: @@ -13593,7 +13726,7 @@ snapshots: open: 8.4.2 ora: 5.3.0 resolve.exports: 2.0.3 - semver: 7.6.3 + semver: 7.7.1 string-width: 4.2.3 tar-stream: 2.2.0 tmp: 0.2.3 @@ -13620,22 +13753,24 @@ snapshots: object-assign@4.1.1: {} - object-inspect@1.13.2: {} + object-inspect@1.13.4: {} object-is@1.1.6: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 object-keys@1.1.1: {} object-treeify@1.1.33: {} - object.assign@4.1.5: + object.assign@4.1.7: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.3 define-properties: 1.2.1 - has-symbols: 1.0.3 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 object-keys: 1.1.1 obuf@1.1.2: {} @@ -13813,7 +13948,7 @@ snapshots: path-scurry@2.0.0: dependencies: - lru-cache: 11.0.1 + lru-cache: 11.0.2 minipass: 7.1.2 path-type@3.0.0: @@ -13833,7 +13968,7 @@ snapshots: pg-connection-string@2.7.0: {} - pg-cursor@2.12.0(pg@8.14.1): + pg-cursor@2.12.3(pg@8.14.1): dependencies: pg: 8.14.1 @@ -13849,10 +13984,15 @@ snapshots: dependencies: pg: 8.14.1 - pg-protocol@1.7.0: {} + pg-protocol@1.7.1: {} pg-protocol@1.8.0: {} + pg-query-stream@4.7.3(pg@8.14.1): + dependencies: + pg: 8.14.1 + pg-cursor: 2.12.3(pg@8.14.1) + pg-types@2.2.0: dependencies: pg-int8: 1.0.1 @@ -13950,9 +14090,9 @@ snapshots: polished@4.3.1: dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.9 - portfinder@1.0.32: + portfinder@1.0.33: dependencies: async: 2.6.4 debug: 3.2.7 @@ -13960,7 +14100,7 @@ snapshots: transitivePeerDependencies: - supports-color - possible-typed-array-names@1.0.0: {} + possible-typed-array-names@1.1.0: {} postcss-value-parser@4.2.0: {} @@ -14049,9 +14189,9 @@ snapshots: dependencies: prosemirror-state: 1.4.3 - prosemirror-commands@1.6.1: + prosemirror-commands@1.7.0: dependencies: - prosemirror-model: 1.23.0 + prosemirror-model: 1.24.1 prosemirror-state: 1.4.3 prosemirror-transform: 1.10.2 @@ -14059,20 +14199,20 @@ snapshots: dependencies: prosemirror-state: 1.4.3 prosemirror-transform: 1.10.2 - prosemirror-view: 1.34.3 + prosemirror-view: 1.38.0 prosemirror-gapcursor@1.3.2: dependencies: prosemirror-keymap: 1.2.2 - prosemirror-model: 1.23.0 + prosemirror-model: 1.24.1 prosemirror-state: 1.4.3 - prosemirror-view: 1.34.3 + prosemirror-view: 1.38.0 prosemirror-history@1.4.1: dependencies: prosemirror-state: 1.4.3 prosemirror-transform: 1.10.2 - prosemirror-view: 1.34.3 + prosemirror-view: 1.38.0 rope-sequence: 1.3.4 prosemirror-inputrules@1.4.0: @@ -14089,58 +14229,58 @@ snapshots: dependencies: '@types/markdown-it': 14.1.2 markdown-it: 14.1.0 - prosemirror-model: 1.23.0 + prosemirror-model: 1.24.1 prosemirror-menu@1.2.4: dependencies: crelt: 1.0.6 - prosemirror-commands: 1.6.1 + prosemirror-commands: 1.7.0 prosemirror-history: 1.4.1 prosemirror-state: 1.4.3 - prosemirror-model@1.23.0: + prosemirror-model@1.24.1: dependencies: orderedmap: 2.1.1 prosemirror-schema-basic@1.2.3: dependencies: - prosemirror-model: 1.23.0 + prosemirror-model: 1.24.1 - prosemirror-schema-list@1.4.1: + prosemirror-schema-list@1.5.0: dependencies: - prosemirror-model: 1.23.0 + prosemirror-model: 1.24.1 prosemirror-state: 1.4.3 prosemirror-transform: 1.10.2 prosemirror-state@1.4.3: dependencies: - prosemirror-model: 1.23.0 + prosemirror-model: 1.24.1 prosemirror-transform: 1.10.2 - prosemirror-view: 1.34.3 + prosemirror-view: 1.38.0 - prosemirror-tables@1.5.0: + prosemirror-tables@1.6.4: dependencies: prosemirror-keymap: 1.2.2 - prosemirror-model: 1.23.0 + prosemirror-model: 1.24.1 prosemirror-state: 1.4.3 prosemirror-transform: 1.10.2 - prosemirror-view: 1.34.3 + prosemirror-view: 1.38.0 - prosemirror-trailing-node@2.0.9(prosemirror-model@1.23.0)(prosemirror-state@1.4.3)(prosemirror-view@1.34.3): + prosemirror-trailing-node@2.0.9(prosemirror-model@1.24.1)(prosemirror-state@1.4.3)(prosemirror-view@1.38.0): dependencies: '@remirror/core-constants': 2.0.2 escape-string-regexp: 4.0.0 - prosemirror-model: 1.23.0 + prosemirror-model: 1.24.1 prosemirror-state: 1.4.3 - prosemirror-view: 1.34.3 + prosemirror-view: 1.38.0 prosemirror-transform@1.10.2: dependencies: - prosemirror-model: 1.23.0 + prosemirror-model: 1.24.1 - prosemirror-view@1.34.3: + prosemirror-view@1.38.0: dependencies: - prosemirror-model: 1.23.0 + prosemirror-model: 1.24.1 prosemirror-state: 1.4.3 prosemirror-transform: 1.10.2 @@ -14159,9 +14299,9 @@ snapshots: q@1.5.1: {} - qs@6.13.1: + qs@6.14.0: dependencies: - side-channel: 1.0.6 + side-channel: 1.1.0 queue-microtask@1.2.3: {} @@ -14171,7 +14311,7 @@ snapshots: raf-schd@4.0.3: {} - react-currency-input-field@3.9.0(react@18.3.1): + react-currency-input-field@3.10.0(react@18.3.1): dependencies: react: 18.3.1 @@ -14201,7 +14341,7 @@ snapshots: react-i18next@12.3.1(i18next@22.5.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.9 html-parse-stringify: 3.0.1 i18next: 22.5.1 react: 18.3.1 @@ -14236,9 +14376,9 @@ snapshots: react-fast-compare: 3.2.2 warning: 4.0.3 - react-redux@8.1.3(@types/react@19.0.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@4.2.1): + react-redux@8.1.3(@types/react@19.0.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@4.2.1): dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.9 '@types/hoist-non-react-statics': 3.3.6 '@types/use-sync-external-store': 0.0.3 hoist-non-react-statics: 3.3.2 @@ -14246,35 +14386,35 @@ snapshots: react-is: 18.3.1 use-sync-external-store: 1.4.0(react@18.3.1) optionalDependencies: - '@types/react': 19.0.4 + '@types/react': 19.0.10 react-dom: 18.3.1(react@18.3.1) redux: 4.2.1 - react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-router-dom@6.30.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@remix-run/router': 1.21.0 + '@remix-run/router': 1.23.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-router: 6.28.1(react@18.3.1) + react-router: 6.30.0(react@18.3.1) - react-router@6.28.1(react@18.3.1): + react-router@6.30.0(react@18.3.1): dependencies: - '@remix-run/router': 1.21.0 + '@remix-run/router': 1.23.0 react: 18.3.1 - react-select@5.9.0(@types/react@19.0.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-select@5.10.0(@types/react@19.0.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.9 '@emotion/cache': 11.14.0 - '@emotion/react': 11.14.0(@types/react@19.0.4)(react@18.3.1) + '@emotion/react': 11.14.0(@types/react@19.0.10)(react@18.3.1) '@floating-ui/dom': 1.6.13 - '@types/react-transition-group': 4.4.12(@types/react@19.0.4) + '@types/react-transition-group': 4.4.12(@types/react@19.0.10) memoize-one: 6.0.0 prop-types: 15.8.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - use-isomorphic-layout-effect: 1.2.0(@types/react@19.0.4)(react@18.3.1) + use-isomorphic-layout-effect: 1.2.0(@types/react@19.0.10)(react@18.3.1) transitivePeerDependencies: - '@types/react' - supports-color @@ -14286,7 +14426,7 @@ snapshots: react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.9 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 @@ -14341,7 +14481,7 @@ snapshots: rechoir@0.8.0: dependencies: - resolve: 1.22.8 + resolve: 1.22.10 redent@3.0.0: dependencies: @@ -14350,7 +14490,7 @@ snapshots: redux@4.2.1: dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.9 reflect-metadata@0.2.2: {} @@ -14364,13 +14504,15 @@ snapshots: regenerator-transform@0.15.2: dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.26.9 - regexp.prototype.flags@1.5.3: + regexp.prototype.flags@1.5.4: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 set-function-name: 2.0.2 regexpu-core@6.2.0: @@ -14392,11 +14534,11 @@ snapshots: require-from-string@2.0.2: {} - require-in-the-middle@7.4.0: + require-in-the-middle@7.5.2: dependencies: debug: 4.4.0(supports-color@5.5.0) module-details-from-path: 1.0.3 - resolve: 1.22.8 + resolve: 1.22.10 transitivePeerDependencies: - supports-color @@ -14412,9 +14554,9 @@ snapshots: resolve.exports@2.0.3: {} - resolve@1.22.8: + resolve@1.22.10: dependencies: - is-core-module: 2.15.1 + is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -14439,6 +14581,8 @@ snapshots: reusify@1.0.4: {} + reusify@1.1.0: {} + rfdc@1.4.1: {} rimraf@3.0.2: @@ -14447,64 +14591,48 @@ snapshots: rimraf@6.0.1: dependencies: - glob: 11.0.0 + glob: 11.0.1 package-json-from-dist: 1.0.1 - rollup-plugin-esbuild-minify@1.2.0(rollup@4.24.0): + roarr@7.21.1: dependencies: - esbuild: 0.24.2 - rollup: 4.24.0 + fast-printf: 1.6.10 + safe-stable-stringify: 2.5.0 + semver-compare: 1.0.0 - rollup-plugin-polyfill-node@0.13.0(rollup@4.24.0): + rollup-plugin-esbuild-minify@1.2.0(rollup@4.34.8): dependencies: - '@rollup/plugin-inject': 5.0.5(rollup@4.24.0) - rollup: 4.24.0 + esbuild: 0.24.2 + rollup: 4.34.8 - rollup@4.24.0: + rollup-plugin-polyfill-node@0.13.0(rollup@4.34.8): dependencies: - '@types/estree': 1.0.6 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.24.0 - '@rollup/rollup-android-arm64': 4.24.0 - '@rollup/rollup-darwin-arm64': 4.24.0 - '@rollup/rollup-darwin-x64': 4.24.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.24.0 - '@rollup/rollup-linux-arm-musleabihf': 4.24.0 - '@rollup/rollup-linux-arm64-gnu': 4.24.0 - '@rollup/rollup-linux-arm64-musl': 4.24.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.24.0 - '@rollup/rollup-linux-riscv64-gnu': 4.24.0 - '@rollup/rollup-linux-s390x-gnu': 4.24.0 - '@rollup/rollup-linux-x64-gnu': 4.24.0 - '@rollup/rollup-linux-x64-musl': 4.24.0 - '@rollup/rollup-win32-arm64-msvc': 4.24.0 - '@rollup/rollup-win32-ia32-msvc': 4.24.0 - '@rollup/rollup-win32-x64-msvc': 4.24.0 - fsevents: 2.3.3 + '@rollup/plugin-inject': 5.0.5(rollup@4.34.8) + rollup: 4.34.8 - rollup@4.34.3: + rollup@4.34.8: dependencies: '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.34.3 - '@rollup/rollup-android-arm64': 4.34.3 - '@rollup/rollup-darwin-arm64': 4.34.3 - '@rollup/rollup-darwin-x64': 4.34.3 - '@rollup/rollup-freebsd-arm64': 4.34.3 - '@rollup/rollup-freebsd-x64': 4.34.3 - '@rollup/rollup-linux-arm-gnueabihf': 4.34.3 - '@rollup/rollup-linux-arm-musleabihf': 4.34.3 - '@rollup/rollup-linux-arm64-gnu': 4.34.3 - '@rollup/rollup-linux-arm64-musl': 4.34.3 - '@rollup/rollup-linux-loongarch64-gnu': 4.34.3 - '@rollup/rollup-linux-powerpc64le-gnu': 4.34.3 - '@rollup/rollup-linux-riscv64-gnu': 4.34.3 - '@rollup/rollup-linux-s390x-gnu': 4.34.3 - '@rollup/rollup-linux-x64-gnu': 4.34.3 - '@rollup/rollup-linux-x64-musl': 4.34.3 - '@rollup/rollup-win32-arm64-msvc': 4.34.3 - '@rollup/rollup-win32-ia32-msvc': 4.34.3 - '@rollup/rollup-win32-x64-msvc': 4.34.3 + '@rollup/rollup-android-arm-eabi': 4.34.8 + '@rollup/rollup-android-arm64': 4.34.8 + '@rollup/rollup-darwin-arm64': 4.34.8 + '@rollup/rollup-darwin-x64': 4.34.8 + '@rollup/rollup-freebsd-arm64': 4.34.8 + '@rollup/rollup-freebsd-x64': 4.34.8 + '@rollup/rollup-linux-arm-gnueabihf': 4.34.8 + '@rollup/rollup-linux-arm-musleabihf': 4.34.8 + '@rollup/rollup-linux-arm64-gnu': 4.34.8 + '@rollup/rollup-linux-arm64-musl': 4.34.8 + '@rollup/rollup-linux-loongarch64-gnu': 4.34.8 + '@rollup/rollup-linux-powerpc64le-gnu': 4.34.8 + '@rollup/rollup-linux-riscv64-gnu': 4.34.8 + '@rollup/rollup-linux-s390x-gnu': 4.34.8 + '@rollup/rollup-linux-x64-gnu': 4.34.8 + '@rollup/rollup-linux-x64-musl': 4.34.8 + '@rollup/rollup-win32-arm64-msvc': 4.34.8 + '@rollup/rollup-win32-ia32-msvc': 4.34.8 + '@rollup/rollup-win32-x64-msvc': 4.34.8 fsevents: 2.3.3 rope-sequence@1.3.4: {} @@ -14515,7 +14643,7 @@ snapshots: dependencies: queue-microtask: 1.2.3 - rxjs@7.8.1: + rxjs@7.8.2: dependencies: tslib: 2.8.1 @@ -14523,6 +14651,12 @@ snapshots: safe-buffer@5.2.1: {} + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + is-regex: 1.2.1 + safe-regex2@4.0.0: dependencies: ret: 0.5.0 @@ -14541,12 +14675,16 @@ snapshots: secure-json-parse@3.0.1: {} + semver-compare@1.0.0: {} + semver@5.7.2: {} semver@6.3.1: {} semver@7.6.3: {} + semver@7.7.1: {} + sequelize-pool@7.1.0: {} sequelize@6.37.7(pg-hstore@2.3.4)(pg@8.14.1): @@ -14561,7 +14699,7 @@ snapshots: moment-timezone: 0.5.46 pg-connection-string: 2.7.0 retry-as-promised: 7.0.4 - semver: 7.6.3 + semver: 7.7.1 sequelize-pool: 7.1.0 toposort-class: 1.0.1 uuid: 8.3.2 @@ -14573,6 +14711,10 @@ snapshots: transitivePeerDependencies: - supports-color + serialize-error@8.1.0: + dependencies: + type-fest: 0.20.2 + set-blocking@2.0.0: {} set-cookie-parser@2.7.0: {} @@ -14582,8 +14724,8 @@ snapshots: define-data-property: 1.1.4 es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.4 - gopd: 1.0.1 + get-intrinsic: 1.3.0 + gopd: 1.2.0 has-property-descriptors: 1.0.2 set-function-name@2.0.2: @@ -14617,12 +14759,33 @@ snapshots: short-unique-id@5.2.2: {} - side-channel@1.0.6: + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: dependencies: - call-bind: 1.0.7 es-errors: 1.3.0 - get-intrinsic: 1.2.4 - object-inspect: 1.13.2 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 siginfo@2.0.0: {} @@ -14633,7 +14796,7 @@ snapshots: sirv@3.0.1: dependencies: '@polka/url': 1.0.0-next.28 - mrmime: 2.0.0 + mrmime: 2.0.1 totalist: 3.0.1 slash@3.0.0: {} @@ -14650,6 +14813,23 @@ snapshots: ansi-styles: 6.2.1 is-fullwidth-code-point: 5.0.0 + slonik@46.4.0(zod@3.24.2): + dependencies: + '@slonik/driver': 46.4.0(zod@3.24.2) + '@slonik/errors': 46.4.0(zod@3.24.2) + '@slonik/pg-driver': 46.4.0(zod@3.24.2) + '@slonik/sql-tag': 46.4.0(zod@3.24.2) + '@slonik/utilities': 46.4.0(zod@3.24.2) + get-stack-trace: 3.1.1 + iso8601-duration: 1.3.0 + postgres-interval: 4.0.2 + roarr: 7.21.1 + serialize-error: 8.1.0 + strict-event-emitter-types: 2.0.0 + zod: 3.24.2 + transitivePeerDependencies: + - pg-native + sonic-boom@4.1.0: dependencies: atomic-sleep: 1.0.0 @@ -14678,16 +14858,16 @@ snapshots: spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.20 + spdx-license-ids: 3.0.21 spdx-exceptions@2.5.0: {} spdx-expression-parse@3.0.1: dependencies: spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.20 + spdx-license-ids: 3.0.21 - spdx-license-ids@3.0.20: {} + spdx-license-ids@3.0.21: {} split2@3.2.2: dependencies: @@ -14709,6 +14889,10 @@ snapshots: stackback@0.0.2: {} + stacktrace-parser@0.1.11: + dependencies: + type-fest: 0.7.1 + standard-version@9.5.0: dependencies: chalk: 2.4.2 @@ -14722,7 +14906,7 @@ snapshots: figures: 3.2.0 find-up: 5.0.0 git-semver-tags: 4.1.1 - semver: 7.6.3 + semver: 7.7.1 stringify-package: 1.0.1 yargs: 16.2.0 @@ -14734,9 +14918,12 @@ snapshots: dependencies: bl: 5.1.0 - stop-iteration-iterator@1.0.0: + stop-iteration-iterator@1.1.0: dependencies: - internal-slot: 1.0.7 + es-errors: 1.3.0 + internal-slot: 1.1.0 + + strict-event-emitter-types@2.0.0: {} string-argv@0.3.2: {} @@ -14760,7 +14947,7 @@ snapshots: string-width@7.2.0: dependencies: emoji-regex: 10.4.0 - get-east-asian-width: 1.2.0 + get-east-asian-width: 1.3.0 strip-ansi: 7.1.0 string_decoder@1.1.1: @@ -14799,14 +14986,14 @@ snapshots: strip-json-comments@3.1.1: {} - styled-components@5.3.9(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1): + styled-components@5.3.9(@babel/core@7.26.9)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1): dependencies: '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) - '@babel/traverse': 7.26.5(supports-color@5.5.0) + '@babel/traverse': 7.26.9(supports-color@5.5.0) '@emotion/is-prop-valid': 1.3.1 '@emotion/stylis': 0.8.5 '@emotion/unitless': 0.7.5 - babel-plugin-styled-components: 2.1.4(@babel/core@7.26.0)(styled-components@5.3.9(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1))(supports-color@5.5.0) + babel-plugin-styled-components: 2.1.4(@babel/core@7.26.9)(styled-components@5.3.9(@babel/core@7.26.9)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1))(supports-color@5.5.0) css-to-react-native: 3.2.0 hoist-non-react-statics: 3.3.2 react: 18.3.1 @@ -14837,7 +15024,7 @@ snapshots: sucrase@3.35.0: dependencies: - '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/gen-mapping': 0.3.8 commander: 4.1.1 glob: 10.4.5 lines-and-columns: 1.2.4 @@ -14989,7 +15176,7 @@ snapshots: ts-morph@25.0.1: dependencies: - '@ts-morph/common': 0.26.0 + '@ts-morph/common': 0.26.1 code-block-writer: 13.0.3 ts-node@10.9.2(@swc/core@1.11.18(@swc/helpers@0.5.15))(@types/node@22.14.0)(typescript@5.8.3): @@ -15000,7 +15187,7 @@ snapshots: '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 '@types/node': 22.14.0 - acorn: 8.12.1 + acorn: 8.14.0 acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 @@ -15023,7 +15210,7 @@ snapshots: tsx@4.19.3: dependencies: esbuild: 0.25.0 - get-tsconfig: 4.8.1 + get-tsconfig: 4.10.0 optionalDependencies: fsevents: 2.3.3 @@ -15043,6 +15230,8 @@ snapshots: type-fest@0.6.0: {} + type-fest@0.7.1: {} + type-fest@0.8.1: {} type-is@1.6.18: @@ -15088,11 +15277,11 @@ snapshots: union@0.5.0: dependencies: - qs: 6.13.1 + qs: 6.14.0 universalify@2.0.1: {} - update-browserslist-db@1.1.2(browserslist@4.24.4): + update-browserslist-db@1.1.3(browserslist@4.24.4): dependencies: browserslist: 4.24.4 escalade: 3.2.0 @@ -15108,11 +15297,11 @@ snapshots: url-join@4.0.1: {} - use-isomorphic-layout-effect@1.2.0(@types/react@19.0.4)(react@18.3.1): + use-isomorphic-layout-effect@1.2.0(@types/react@19.0.10)(react@18.3.1): dependencies: react: 18.3.1 optionalDependencies: - '@types/react': 19.0.4 + '@types/react': 19.0.10 use-memo-one@1.1.3(react@18.3.1): dependencies: @@ -15172,7 +15361,7 @@ snapshots: dependencies: esbuild: 0.25.0 postcss: 8.5.3 - rollup: 4.34.3 + rollup: 4.34.8 optionalDependencies: '@types/node': 22.14.0 fsevents: 2.3.3 @@ -15184,7 +15373,7 @@ snapshots: dependencies: esbuild: 0.25.0 postcss: 8.5.3 - rollup: 4.34.3 + rollup: 4.34.8 optionalDependencies: '@types/node': 22.14.0 fsevents: 2.3.3 @@ -15243,7 +15432,7 @@ snapshots: espree: 9.6.1 esquery: 1.6.0 lodash: 4.17.21 - semver: 7.6.3 + semver: 7.7.1 transitivePeerDependencies: - supports-color @@ -15272,27 +15461,28 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 - which-boxed-primitive@1.0.2: + which-boxed-primitive@1.1.1: dependencies: - is-bigint: 1.0.4 - is-boolean-object: 1.1.2 - is-number-object: 1.0.7 - is-string: 1.0.7 - is-symbol: 1.0.4 + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 which-collection@1.0.2: dependencies: is-map: 2.0.3 is-set: 2.0.3 is-weakmap: 2.0.2 - is-weakset: 2.0.3 + is-weakset: 2.0.4 - which-typed-array@1.1.15: + which-typed-array@1.1.18: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.7 - for-each: 0.3.3 - gopd: 1.0.1 + call-bind: 1.0.8 + call-bound: 1.0.3 + for-each: 0.3.5 + gopd: 1.2.0 has-tostringtag: 1.0.2 which@1.3.1: @@ -15403,3 +15593,5 @@ snapshots: yocto-queue@1.1.1: {} yoctocolors-cjs@2.1.2: {} + + zod@3.24.2: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ebb11ddd7..b4642b11a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,4 +1,9 @@ packages: - - 'packages/*' - - "src/*" - - "libs/@rustymotors/*" + - packages/* + - src/* + - libs/@rustymotors/* +onlyBuiltDependencies: + - '@sentry/cli' + - '@sentry/profiling-node' + - '@swc/core' + - nx diff --git a/services/haproxy/Dockerfile b/services/haproxy/Dockerfile new file mode 100644 index 000000000..49460039b --- /dev/null +++ b/services/haproxy/Dockerfile @@ -0,0 +1 @@ +FROM haproxy:3.1.6 \ No newline at end of file diff --git a/services/haproxy/haproxy.cfg b/services/haproxy/haproxy.cfg new file mode 100644 index 000000000..eed480a2a --- /dev/null +++ b/services/haproxy/haproxy.cfg @@ -0,0 +1,54 @@ +# +# demo config for Proxy mode +# + +global + maxconn 20000 + ulimit-n 16384 + log stderr local0 + ssl-security-level 0 + ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 + ssl-default-server-options ssl-min-ver TLSv1.0 no-tls-tickets + daemon + +frontend authlogin + bind *:80 + bind *:443 ssl crt /usr/local/etc/haproxy/ssl/haproxy.pem + mode http + log global + option logasap + option tcplog + option httplog + option dontlognull + option nolinger + maxconn 8000 + timeout client 30s + + # # layer3: Valid users + # acl allow_host src 192.168.200.150/32 + # http-request deny if !allow_host + + # # layer7: prevent private network relaying + # acl forbidden_dst url_ip 192.168.0.0/24 + # acl forbidden_dst url_ip 172.16.0.0/12 + # acl forbidden_dst url_ip 10.0.0.0/8 + # http-request deny if forbidden_dst + + default_backend test-proxy-srv + + +backend test-proxy-srv + mode http + timeout connect 5s + timeout server 5s + retries 2 + option nolinger + + # layer7: Only GET method is valid + acl valid_method method GET + http-request deny if !valid_method + + # layer7: protect bad reply + http-response deny if { res.hdr(content-type) audio/mp3 } + + server server1 host.docker.internal:3000 diff --git a/services/haproxy/ssl/haproxy.pem b/services/haproxy/ssl/haproxy.pem new file mode 100644 index 000000000..96f08209f --- /dev/null +++ b/services/haproxy/ssl/haproxy.pem @@ -0,0 +1,33 @@ +-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMTb313megZROpjP +jC8ujd4Hq+Ic647YPneWwFnEglBymgN+eK0chlhl5nlDenuyFayqrZxeOQzmcsVN +q1U1ZlDHGFD2M2xLi5NjgEkmiSpvjiX/km8XVQuB02HKK86bzEUoPHgFUupW1204 +V0L+nvudLUe92A8jxZ2e5MgGm865AgMBAAECgYEAn/wEqp5dOvcWxQufZNTTzMa1 +RLy2H0/UbIIibpHKSjDow8AruJw+/mZKCPDzAMX44WuQTk0w1jAOxBRcEpKkQ8JF +Tt/XAppo5b2BOpNQNDad+mQwhwXEHPwLaM+INo5BJkwzN+rw5FwARqyd2gKyweHk +4btT8pTNfCWh/VZiQoECQQDr3IMPvqB1/HLhCpDCPSM5682cWu+FnUxz55Ob2wR8 +ESbLeYk+Oj0FHHx8RLI/GIjSGHzAHxaK/JxdLfs8CGZpAkEA1arWyC43FblFE18p +s+61AmBj4kq7pPyHXr7iZb1L2YJU7Jz/xdaHPNt2fHXk2FyRAQG2fYPXGy+K6PdB +ajY70QJAN8s3Ga9fZ+mrBz8nlTs/LQFx5w2/4VyfbD+YtGKwAeVQxwteAuFl8+KU +NBVQEQrtur1eLVhtTb2KA6TP7JiUGQJARrEuPK7ltL9GdmFO1+RLaQzhHzagnLac +RyGaoBSgKsevN1W+XgNbXSwzKHyNCXSe2Rlq+l4LZkg1ilx7/RsScQJBAMAQhb94 +CUPD1a2t9Xp6FqFEqgCLgnQb/xqCR4Cjnms/EGEjBG7GRbeV7tO18/SGHotCPdMh +l2448aVdXShjbJ8= +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIICvTCCAiagAwIBAgIUR8C7hExU+43EnLwJlyAdlZv1GqIwDQYJKoZIhvcNAQEL +BQAwdjEYMBYGA1UEAwwPbWNvdW5pdmVyc2UuY29tMSQwIgYJKoZIhvcNAQkBFhVh +ZG1pbkBtY291bml2ZXJzZS5jb20xFDASBgNVBAoMC01DT1VuaXZlcnNlMREwDwYD +VQQHDAhOZXcgWW9yazELMAkGA1UEBhMCVVMwHhcNMjEwNjI0MTQzNjIwWhcNMjIw +NjI0MTQzNjIwWjB2MRgwFgYDVQQDDA9tY291bml2ZXJzZS5jb20xJDAiBgkqhkiG +9w0BCQEWFWFkbWluQG1jb3VuaXZlcnNlLmNvbTEUMBIGA1UECgwLTUNPVW5pdmVy +c2UxETAPBgNVBAcMCE5ldyBZb3JrMQswCQYDVQQGEwJVUzCBnzANBgkqhkiG9w0B +AQEFAAOBjQAwgYkCgYEAxNvfXeZ6BlE6mM+MLy6N3ger4hzrjtg+d5bAWcSCUHKa +A354rRyGWGXmeUN6e7IVrKqtnF45DOZyxU2rVTVmUMcYUPYzbEuLk2OASSaJKm+O +Jf+SbxdVC4HTYcorzpvMRSg8eAVS6lbXbThXQv6e+50tR73YDyPFnZ7kyAabzrkC +AwEAAaNIMEYwDAYDVR0TBAUwAwEB/zAXBgNVHREBAf8EDTALgglsb2NhbGhvc3Qw +HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4GB +ALk8ZaOFZBZbDBxlXTynf5vzt30mpDIlJ8RLUQ/F2GzRU9O7bVAQb5utwUDegQst +c08JsrN5XHcEQf87AdM4e/Q/MlQjNu1TClUNXjdPYQfmWMzz1QXwTt3GjJA0n3I7 +ZdYLWVNyu9cZfoDJCqZm2VYPHMmCMgIZpHjvetFaXb3G +-----END CERTIFICATE----- diff --git a/src/chat/ChatMessage.ts b/src/chat/ChatMessage.ts index d474fe33a..d26cbb28d 100644 --- a/src/chat/ChatMessage.ts +++ b/src/chat/ChatMessage.ts @@ -1,10 +1,9 @@ -import { assertLength } from "./assertLength.js"; import { bufferToHexString } from "./toHexString.js"; export class ChatMessage { - messageId: number; - messageLength: number; - payload: Buffer; + messageId = 0; + messageLength = 0; + payload: Buffer = Buffer.alloc(0); constructor(messageId: number, messageLength: number, payload: Buffer) { this.messageId = messageId; @@ -12,15 +11,15 @@ export class ChatMessage { this.payload = payload; } - static fromBuffer(buffer: Buffer): ChatMessage { + deserialize(buffer: Buffer): ChatMessage { const messageId = buffer.readUInt16BE(0); const messageLength = buffer.readUInt16BE(2); + const payload = buffer.subarray(4); - assertLength(buffer.byteLength, messageLength); - - const payload = buffer.subarray(4, 4 + messageLength); - - return new ChatMessage(messageId, messageLength, payload); + this.messageId = messageId; + this.messageLength = messageLength; + this.payload = payload; + return this; } toBuffer(): Buffer { diff --git a/src/chat/ListInGameEmailsMessage.ts b/src/chat/ListInGameEmailsMessage.ts index 46d1dd062..b43195aa5 100644 --- a/src/chat/ListInGameEmailsMessage.ts +++ b/src/chat/ListInGameEmailsMessage.ts @@ -1,26 +1,16 @@ -import { assertLength } from "./assertLength.js"; import { ChatMessage } from "./ChatMessage.js"; export class ListInGameEmailsMessage extends ChatMessage { - userId: number; - lastEmailId: number; + userId = 0; + lastEmailId = 0; - constructor(messageId: number, messageLength: number, payload: Buffer) { - super(messageId, messageLength, payload); - this.userId = payload.readUInt32BE(0); - this.lastEmailId = payload.readUInt32BE(4); - } - - static override fromBuffer(buffer: Buffer): ListInGameEmailsMessage { - const messageId = buffer.readUInt16BE(0); - const messageLength = buffer.readUInt16BE(2); - - assertLength(buffer.byteLength, messageLength); - - const payload = buffer.subarray(4, 4 + messageLength); + override deserialize(buffer: Buffer): ChatMessage { + this.userId = buffer.readUInt16BE(0); + this.lastEmailId = buffer.readUInt16BE(4); + this.payload = buffer.subarray(8); - return new ListInGameEmailsMessage(messageId, messageLength, payload); + return this; } override toString(): string { diff --git a/src/chat/ListInGameEmailsResponseMessage.ts b/src/chat/ListInGameEmailsResponseMessage.ts index 39cabbac7..fb08b7822 100644 --- a/src/chat/ListInGameEmailsResponseMessage.ts +++ b/src/chat/ListInGameEmailsResponseMessage.ts @@ -12,15 +12,19 @@ export class ListInGameEmailsResponseMessage extends ChatMessage { } override toBuffer(): Buffer { - const buffer = Buffer.alloc(this.messageLength); - - buffer.writeUInt16BE(this.messageId, 0); - buffer.writeUInt16BE(this.messageLength, 2); - - buffer.writeUInt16BE(this.totalEmails, 4); - buffer.writeUInt32BE(this.firstEmailId, 6); - - return buffer; +try { + const buffer = Buffer.alloc(this.messageLength); + + buffer.writeUInt16BE(this.messageId, 0); + buffer.writeUInt16BE(this.messageLength, 2); + + buffer.writeUInt16BE(this.totalEmails, 4); + buffer.writeUInt32BE(this.firstEmailId, 6); + + return buffer; +} catch (error) { + throw new Error(`Error in ListInGameEmailsResponseMessage.toBuffer: ${error}`); +} } override toString(): string { diff --git a/src/chat/inGameEmails.ts b/src/chat/inGameEmails.ts index a22a8d4a0..4eded9929 100644 --- a/src/chat/inGameEmails.ts +++ b/src/chat/inGameEmails.ts @@ -1,5 +1,4 @@ import { ChatMessage } from "./ChatMessage.js"; -import { assertLength } from "./assertLength.js"; import { ListInGameEmailsMessage } from "./ListInGameEmailsMessage.js"; import { ListInGameEmailsResponseMessage } from "./ListInGameEmailsResponseMessage.js"; import { InGameEmailMessage } from "./InGameEmailMessage.js"; @@ -25,27 +24,17 @@ unseenMail.set( ); export class ReceiveEmailMessage extends ChatMessage { - gameUserId: number; - mailId: number; - headerOnly: boolean; + gameUserId = 0; + mailId = 0; + headerOnly = false; - static override fromBuffer(buffer: Buffer): ReceiveEmailMessage { - const messageId = buffer.readUInt16BE(0); - const messageLength = buffer.readUInt16BE(2); - assertLength(buffer.byteLength, messageLength); + override deserialize(buffer: Buffer): ChatMessage { + this.gameUserId = buffer.readUInt32BE(0); + this.mailId = buffer.readUInt16BE(4); + this.headerOnly = buffer.readUInt16BE(8) === 1; - const payload = buffer.subarray(4, 4 + messageLength); - - return new ReceiveEmailMessage(messageId, messageLength, payload); - } - - constructor(messageId: number, messageLength: number, payload: Buffer) { - super(messageId, messageLength, payload); - - this.gameUserId = payload.readUInt32BE(0); - this.mailId = payload.readUInt16BE(4); - this.headerOnly = payload.readUInt16BE(8) === 1; + return this; } override toString(): string { @@ -56,7 +45,8 @@ export class ReceiveEmailMessage extends ChatMessage { export function handleListInGameEmailsMessage(message: ChatMessage): Buffer[] { defaultLogger.debug(`Handling ListInGameEmailsMessage: ${message.toString()}`); - const parsedMessage = ListInGameEmailsMessage.fromBuffer(message.toBuffer()); + const parsedMessage = new ListInGameEmailsMessage(0, 0, Buffer.alloc(0)); + parsedMessage.deserialize(message.toBuffer()); defaultLogger.debug(`Parsed message: ${parsedMessage.toString()}`); @@ -73,7 +63,8 @@ export function handleListInGameEmailsMessage(message: ChatMessage): Buffer[] { export function handleReceiveEmailMessage(message: ChatMessage): Buffer[] { defaultLogger.debug(`Handling ReceiveEmailMessage: ${message.toString()}`); - const parsedMessage = ReceiveEmailMessage.fromBuffer(message.toBuffer()); + const parsedMessage = new ReceiveEmailMessage(0, 0, Buffer.alloc(0)); + parsedMessage.deserialize(message.toBuffer()); defaultLogger.debug(`Parsed message: ${parsedMessage.toString()}`); diff --git a/src/chat/index.ts b/src/chat/index.ts index 5c5f8c811..170bddb63 100644 --- a/src/chat/index.ts +++ b/src/chat/index.ts @@ -39,7 +39,8 @@ async function receiveChatData({ let inboundMessage: ChatMessage; try { - inboundMessage = ChatMessage.fromBuffer(message.serialize()); + inboundMessage = new ChatMessage(); + inboundMessage.deserialize(message.serialize()); } catch (error) { const err = new Error(`[${connectionId}] Error deserializing message`, { cause: error, @@ -67,7 +68,7 @@ async function receiveChatData({ ); const messages = responses.map((response) => { const responseBuffer = new SerializedBufferOld(); - responseBuffer._doDeserialize(response); + responseBuffer.deserialize(response); return responseBuffer; }); diff --git a/src/server.ts b/src/server.ts index fef62f675..b9af603b4 100755 --- a/src/server.ts +++ b/src/server.ts @@ -50,12 +50,7 @@ function main() { `Pre-flight checks passed. Starting server with config: ${JSON.stringify(sanitizedConfig)}`, ); - const appLog = coreLogger.child({ - name: "app", - level: config.logLevel, - }); - - const listeningPortList = [ + const listeningPortList: number[] = [ 6660, 7003, 8228, 8226, 8227, 9000, 9001, 9002, 9003, 9004, 9005, 9006, 9007, 9008, 9009, 9010, 9011, 9012, 9013, 9014, 43200, 43300, 43400, 53303, @@ -63,7 +58,6 @@ function main() { const gatewayServer = new Gateway({ config, - log: appLog, listeningPortList, }); diff --git a/tsconfig.base.json b/tsconfig.base.json index a1587c950..63801c984 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1,7 +1,7 @@ { "compilerOptions": { "lib": ["es2022"], - "module": "CommonJS", + "module": "NodeNext", "target": "es2022", "allowUnusedLabels": false, "allowUnreachableCode": false,