From 306f627cef317a34850f0275b4704251e8a2c6f0 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Sat, 25 Nov 2023 22:45:27 +0700 Subject: [PATCH] chore: format and remove unneccessary code --- .eslintrc | 3 +- apis/websocket/README.md | 114 +++---- .../docs/0_development_environment.md | 78 ++--- apis/websocket/docs/1_configuration.md | 78 ++--- apis/websocket/docs/2_running.md | 83 +++-- apis/websocket/docs/3_packets.md | 66 ++-- apis/websocket/docs/README.md | 32 +- apis/websocket/package.json | 86 ++--- apis/websocket/src/classes/Client.ts | 4 +- apis/websocket/src/events/index.ts | 7 +- apis/websocket/src/index.ts | 307 +++++++++--------- apis/websocket/src/utils/checkEnv.ts | 9 +- apis/websocket/src/utils/getConfig.ts | 33 +- apis/websocket/src/utils/index.ts | 2 +- apis/websocket/src/utils/logger.ts | 19 +- apis/websocket/tsconfig.json | 4 +- bun.lockb | Bin 330156 -> 335700 bytes package.json | 114 +++---- packages/api/package.json | 80 ++--- packages/api/src/classes/Client.ts | 8 +- packages/api/src/classes/ClientGateway.ts | 10 +- packages/api/src/classes/index.ts | 2 +- packages/api/tsconfig.json | 4 +- packages/api/utility-types.d.ts | 2 +- packages/shared/package.json | 74 ++--- .../shared/src/constants/DisconnectReason.ts | 6 +- .../constants/HumanizedDisconnectReason.ts | 7 +- packages/shared/src/constants/Operation.ts | 4 +- packages/shared/src/constants/index.ts | 2 +- packages/shared/src/index.ts | 2 +- packages/shared/src/schemas/index.ts | 2 +- packages/shared/src/utils/guard.ts | 21 +- packages/shared/src/utils/serialization.ts | 2 +- packages/shared/src/utils/string.ts | 4 +- packages/shared/tsconfig.json | 4 +- tsconfig.apis.json | 2 +- tsconfig.base.json | 6 +- tsconfig.packages.json | 4 +- turbo.json | 52 +-- 39 files changed, 690 insertions(+), 647 deletions(-) diff --git a/.eslintrc b/.eslintrc index 9146069..475489e 100755 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,7 @@ { "root": true, - "extends": ["prettier"], + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"], + "parser": "@typescript-eslint/parser", "parserOptions": { "sourceType": "module", "ecmaVersion": "latest" diff --git a/apis/websocket/README.md b/apis/websocket/README.md index b13d97a..e38c83c 100755 --- a/apis/websocket/README.md +++ b/apis/websocket/README.md @@ -1,57 +1,57 @@ -

- - - - -
- - -     - - - - - -     - - -     - - -     - - -     - - - - - -     - - - -
-
- Continuing the legacy of Vanced -

- -# 🚙 ReVanced Bot WebSocket API - -![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-yellow.svg) - -The WebSocket API for ReVanced bots utilizing BSON for packet transmission with a heartbeating system to monitor the statuses of clients. - -## 📚 Documentation - -Documentation are provided [here](./docs/README.md). - -## 📄 License - -**ReVanced Bot WebSocket API** adopts the [GNU General Public License 3.0](./LICENSE), tl;dr: You may copy, distribute and modify the software as long as you track changes/dates in source files. Any modifications to or software including (via compiler) GPL-licensed code must also be made available under the GPL along with build & install instructions. \ No newline at end of file +

+ + + + +
+ + +     + + + + + +     + + +     + + +     + + +     + + + + + +     + + + +
+
+ Continuing the legacy of Vanced +

+ +# 🚙 ReVanced Bot WebSocket API + +![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-yellow.svg) + +The WebSocket API for ReVanced bots utilizing BSON for packet transmission with a heartbeating system to monitor the statuses of clients. + +## 📚 Documentation + +Documentation are provided [here](./docs/README.md). + +## 📄 License + +**ReVanced Bot WebSocket API** adopts the [GNU General Public License 3.0](./LICENSE), tl;dr: You may copy, distribute and modify the software as long as you track changes/dates in source files. Any modifications to or software including (via compiler) GPL-licensed code must also be made available under the GPL along with build & install instructions. diff --git a/apis/websocket/docs/0_development_environment.md b/apis/websocket/docs/0_development_environment.md index 9e523f1..85960a4 100644 --- a/apis/websocket/docs/0_development_environment.md +++ b/apis/websocket/docs/0_development_environment.md @@ -1,39 +1,39 @@ -# 🏗️ Setting up the development environment - -> [!IMPORTANT] -> **This project uses [Bun](https://bun.sh) to run and bundle the code.** -> Compatibility with other runtimes (Node.js, Deno, ...) are not guaranteed and most package scripts won't work. - -To start developing, you'll need to set up the development environment first. - -1. Install [Bun](https://bun.sh) - -2. Clone the mono-repository - - ```sh - git clone https://github.com/ReVanced/revanced-helper.git && - cd revanced-helper - ``` - -3. Install dependencies - - ```sh - bun install - ``` - -4. Build packages/libraries - - ```sh - bun build:deps - ``` - -5. Change your directory to this project's root - ```sh - cd apis/websocket - ``` - -## ⏭️ What's next - -The next page will tell you about server configurations. - -Continue: [⚙️ Configuration](./1_configuration.md) +# 🏗️ Setting up the development environment + +> [!IMPORTANT] +> **This project uses [Bun](https://bun.sh) to run and bundle the code.** +> Compatibility with other runtimes (Node.js, Deno, ...) are not guaranteed and most package scripts won't work. + +To start developing, you'll need to set up the development environment first. + +1. Install [Bun](https://bun.sh) + +2. Clone the mono-repository + + ```sh + git clone https://github.com/ReVanced/revanced-helper.git && + cd revanced-helper + ``` + +3. Install dependencies + + ```sh + bun install + ``` + +4. Build packages/libraries + + ```sh + bun build:deps + ``` + +5. Change your directory to this project's root + ```sh + cd apis/websocket + ``` + +## ⏭️ What's next + +The next page will tell you about server configurations. + +Continue: [⚙️ Configuration](./1_configuration.md) diff --git a/apis/websocket/docs/1_configuration.md b/apis/websocket/docs/1_configuration.md index dd134be..5f2454b 100644 --- a/apis/websocket/docs/1_configuration.md +++ b/apis/websocket/docs/1_configuration.md @@ -1,39 +1,39 @@ -# ⚙️ Configuration - -This is the default configuration: - -```json -{ - "address": "127.0.0.1", - "port": 3000, - "ocrConcurrentQueues": 1, - "clientHeartbeatInterval": 60000, - "debugLogsInProduction": false -} -``` - ---- - -### `config.address` & `config.port` - -The address and port for the server to listen on. - -### `config.ocrConcurrentQueues` - -Amount of concurrent queues that can be run at a time. - -> Setting this too high may cause performance issues. - -### `config.clientHeartbeatInterval` - -Heartbeat interval for clients. See [**💓 Heartbeating**](./packets.md#💓-heartbeating). - -### `config.debugLogsInProduction` - -Whether to print debug logs at all in production mode (when `NODE_ENV` is `production`). - -## ⏭️ What's next - -The next page will tell you how to run and bundle the server. - -Continue: [🏃🏻‍♂️ Running the server](./2_running.md) +# ⚙️ Configuration + +This is the default configuration: + +```json +{ + "address": "127.0.0.1", + "port": 3000, + "ocrConcurrentQueues": 1, + "clientHeartbeatInterval": 60000, + "debugLogsInProduction": false +} +``` + +--- + +### `config.address` & `config.port` + +The address and port for the server to listen on. + +### `config.ocrConcurrentQueues` + +Amount of concurrent queues that can be run at a time. + +> Setting this too high may cause performance issues. + +### `config.clientHeartbeatInterval` + +Heartbeat interval for clients. See [**💓 Heartbeating**](./packets.md#💓-heartbeating). + +### `config.debugLogsInProduction` + +Whether to print debug logs at all in production mode (when `NODE_ENV` is `production`). + +## ⏭️ What's next + +The next page will tell you how to run and bundle the server. + +Continue: [🏃🏻‍♂️ Running the server](./2_running.md) diff --git a/apis/websocket/docs/2_running.md b/apis/websocket/docs/2_running.md index 6734a27..3ffd782 100644 --- a/apis/websocket/docs/2_running.md +++ b/apis/websocket/docs/2_running.md @@ -1,42 +1,41 @@ -# 🏃🏻‍♂️ Running the server - -There are many methods to run the server. Choose one that suits best for the situation. - -> [!IMPORTANT] -> Make sure you've followed the [**🏗️ Setting up the environment**](./0_development_environment.md) steps. - -## 👷🏻 Development mode (recommended) - -There will be no compilation step, and Bun will automatically watch changes and restart the server for you. - -You can quickly start the server by running: - -```sh -bun dev -``` - -## 🌐 Production mode - -Production mode runs no different from the development server, it simply has less debugging information printed to console by default. However, more production-specific features may come. - -To start the server in production mode, you'll have to: - -1. Set the `NODE_ENV` environment variable to `production` - - > It is very possible to set the value in the `.env` file and let Bun load it, **but it is recommended to set the variable before Bun even starts**. - -2. Start the server - ```sh - bun dev - ``` - -## 📦 Building - -If you're looking to build and host the server somewhere else, you can run: - -```sh -bun bundle -``` - -The files will be placed in the `dist` directory. **Configurations and `.env` files will NOT be copied automatically.** - +# 🏃🏻‍♂️ Running the server + +There are many methods to run the server. Choose one that suits best for the situation. + +> [!IMPORTANT] +> Make sure you've followed the [**🏗️ Setting up the environment**](./0_development_environment.md) steps. + +## 👷🏻 Development mode (recommended) + +There will be no compilation step, and Bun will automatically watch changes and restart the server for you. + +You can quickly start the server by running: + +```sh +bun dev +``` + +## 🌐 Production mode + +Production mode runs no different from the development server, it simply has less debugging information printed to console by default. However, more production-specific features may come. + +To start the server in production mode, you'll have to: + +1. Set the `NODE_ENV` environment variable to `production` + + > It is very possible to set the value in the `.env` file and let Bun load it, **but it is recommended to set the variable before Bun even starts**. + +2. Start the server + ```sh + bun dev + ``` + +## 📦 Building + +If you're looking to build and host the server somewhere else, you can run: + +```sh +bun bundle +``` + +The files will be placed in the `dist` directory. **Configurations and `.env` files will NOT be copied automatically.** diff --git a/apis/websocket/docs/3_packets.md b/apis/websocket/docs/3_packets.md index fe2ea75..96d5c7f 100644 --- a/apis/websocket/docs/3_packets.md +++ b/apis/websocket/docs/3_packets.md @@ -1,33 +1,33 @@ -# 📨 Packets - -Packets are BSON messages sent to the server. They're structured like the following when decoded: - -```json -{ - "op": 12345, - "d": { - "some_field": "some data" - } -} -``` - -### `packet.op` - -Operation codes are numbers that communicate an action. - -### `packet.d` - -Data fields include additional information for the server to process. They are **either an object with specific fields or just `null`**. - -#### 📦 Schemas and constants - -Schemas for packets and their respective data[^1], and the list of possible operation codes[^2] can be found in the `@revanced/bot-shared` package, with typings as well. - -[^1]: [`@revanced/bot-shared/src/schemas/Packet.ts`](../../packages/shared/src/schemas/Packet.ts) -[^2]: [`@revanced/bot-shared/src/constants/Operation`](../../packages/shared/src/constants/Operation.ts) - -## 💓 Heartbeating - -Heartbeating is a process where the client regularly send each other signals to confirm that they are still connected and functioning. If the server doesn't receive a heartbeat from the client within a specified timeframe, it assume the client has disconnected and closes the socket. - -You can configure the interval in the configuration file. See [**📝 Configuration > `config.clientHeartbeatInterval`**](./1_configuration.md#configclientheartbeatinterval). +# 📨 Packets + +Packets are BSON messages sent to the server. They're structured like the following when decoded: + +```json +{ + "op": 12345, + "d": { + "some_field": "some data" + } +} +``` + +### `packet.op` + +Operation codes are numbers that communicate an action. + +### `packet.d` + +Data fields include additional information for the server to process. They are **either an object with specific fields or just `null`**. + +#### 📦 Schemas and constants + +Schemas for packets and their respective data[^1], and the list of possible operation codes[^2] can be found in the `@revanced/bot-shared` package, with typings as well. + +[^1]: [`@revanced/bot-shared/src/schemas/Packet.ts`](../../packages/shared/src/schemas/Packet.ts) +[^2]: [`@revanced/bot-shared/src/constants/Operation`](../../packages/shared/src/constants/Operation.ts) + +## 💓 Heartbeating + +Heartbeating is a process where the client regularly send each other signals to confirm that they are still connected and functioning. If the server doesn't receive a heartbeat from the client within a specified timeframe, it assume the client has disconnected and closes the socket. + +You can configure the interval in the configuration file. See [**📝 Configuration > `config.clientHeartbeatInterval`**](./1_configuration.md#configclientheartbeatinterval). diff --git a/apis/websocket/docs/README.md b/apis/websocket/docs/README.md index 1ce65ca..5dc3ad7 100755 --- a/apis/websocket/docs/README.md +++ b/apis/websocket/docs/README.md @@ -1,16 +1,16 @@ -# 🚙 ReVanced Bot WebSocket API - -This documentation explains how the server works, how to start developing, and how to configure the server. - -# 📖 Table of contents - -0. [🏗️ Setting up the development environment](./0_development_environment.md) -1. [⚙️ Configuration](./1_configuration.md) -2. [🏃🏻‍♂️ Running the server](./2_running.md) -3. [📨 Packets](./3_packets.md) - -## ⏭️ Start here - -The next page will tell you how to set up the development environment. - -Continue: [🏗️ Setting up the development environment](./0_development_environment.md) +# 🚙 ReVanced Bot WebSocket API + +This documentation explains how the server works, how to start developing, and how to configure the server. + +# 📖 Table of contents + +0. [🏗️ Setting up the development environment](./0_development_environment.md) +1. [⚙️ Configuration](./1_configuration.md) +2. [🏃🏻‍♂️ Running the server](./2_running.md) +3. [📨 Packets](./3_packets.md) + +## ⏭️ Start here + +The next page will tell you how to set up the development environment. + +Continue: [🏗️ Setting up the development environment](./0_development_environment.md) diff --git a/apis/websocket/package.json b/apis/websocket/package.json index 99fd315..e95c042 100755 --- a/apis/websocket/package.json +++ b/apis/websocket/package.json @@ -1,43 +1,43 @@ -{ - "name": "@revanced/bot-websocket-api", - "type": "module", - "private": true, - "version": "0.1.0", - "description": "🧦 WebSocket API server for bots assisting ReVanced", - "main": "dist/index.js", - "scripts": { - "bundle": "bun build src/index.ts --outdir=dist --target=node --minify --sourcemap=external", - "dev": "bun run src/index.ts --watch", - "build": "bun bundle", - "watch": "bun dev" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/revanced/revanced-helper.git", - "directory": "apis/websocket" - }, - "author": "Palm (https://github.com/PalmDevs)", - "contributors": [ - "Palm (https://github.com/PalmDevs)", - "ReVanced (https://github.com/revanced)" - ], - "license": "GPL-3.0-or-later", - "bugs": { - "url": "https://github.com/revanced/revanced-helper/issues" - }, - "homepage": "https://github.com/revanced/revanced-helper#readme", - "dependencies": { - "@fastify/websocket": "^8.2.0", - "@revanced/bot-shared": "workspace:*", - "@sapphire/async-queue": "^1.5.0", - "chalk": "^5.3.0", - "fastify": "^4.24.3", - "node-wit": "^6.6.0", - "tesseract.js": "^5.0.3" - }, - "devDependencies": { - "@types/node-wit": "^6.0.3", - "@types/ws": "^8.5.10", - "typed-emitter": "^2.1.0" - } -} +{ + "name": "@revanced/bot-websocket-api", + "type": "module", + "private": true, + "version": "0.1.0", + "description": "🧦 WebSocket API server for bots assisting ReVanced", + "main": "dist/index.js", + "scripts": { + "bundle": "bun build src/index.ts --outdir=dist --target=node --minify --sourcemap=external", + "dev": "bun run src/index.ts --watch", + "build": "bun bundle", + "watch": "bun dev" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/revanced/revanced-helper.git", + "directory": "apis/websocket" + }, + "author": "Palm (https://github.com/PalmDevs)", + "contributors": [ + "Palm (https://github.com/PalmDevs)", + "ReVanced (https://github.com/revanced)" + ], + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/revanced/revanced-helper/issues" + }, + "homepage": "https://github.com/revanced/revanced-helper#readme", + "dependencies": { + "@fastify/websocket": "^8.2.0", + "@revanced/bot-shared": "workspace:*", + "@sapphire/async-queue": "^1.5.0", + "chalk": "^5.3.0", + "fastify": "^4.24.3", + "node-wit": "^6.6.0", + "tesseract.js": "^5.0.3" + }, + "devDependencies": { + "@types/node-wit": "^6.0.3", + "@types/ws": "^8.5.10", + "typed-emitter": "^2.1.0" + } +} diff --git a/apis/websocket/src/classes/Client.ts b/apis/websocket/src/classes/Client.ts index 5c96086..09bf3d3 100755 --- a/apis/websocket/src/classes/Client.ts +++ b/apis/websocket/src/classes/Client.ts @@ -141,7 +141,7 @@ export default class Client { this.#emitter.emit('packet', packet) this.#emitter.emit( uncapitalize(ClientOperation[packet.op] as ClientEventName), - // @ts-expect-error + // @ts-expect-error TypeScript doesn't know that the above line will negate the type enough packet ) } catch (e) { @@ -181,7 +181,7 @@ export default class Client { this.once('heartbeat', () => clearTimeout(interval)) // This should never happen but it did in my testing so I'm adding this just in case this.once('disconnect', () => clearTimeout(interval)) - // Technically we don't have to do this, but JUST IN CASE! + // Technically we don't have to do this, but JUST IN CASE! } else this.#hbTimeout.refresh() }, this.heartbeatInterval) } diff --git a/apis/websocket/src/events/index.ts b/apis/websocket/src/events/index.ts index 9724ec5..53d9d03 100755 --- a/apis/websocket/src/events/index.ts +++ b/apis/websocket/src/events/index.ts @@ -8,10 +8,13 @@ import type { Worker as TesseractWorker } from 'tesseract.js' export { default as parseTextEventHandler } from './parseText.js' export { default as parseImageEventHandler } from './parseImage.js' -export type EventHandler = (packet: ClientPacketObject, context: EventContext) => void | Promise +export type EventHandler = ( + packet: ClientPacketObject, + context: EventContext +) => void | Promise export type EventContext = { witClient: Wit tesseractWorker: TesseractWorker logger: Logger config: Config -} \ No newline at end of file +} diff --git a/apis/websocket/src/index.ts b/apis/websocket/src/index.ts index fab0d4e..6281ab1 100755 --- a/apis/websocket/src/index.ts +++ b/apis/websocket/src/index.ts @@ -1,150 +1,157 @@ -import { fastify } from 'fastify' -import fastifyWebsocket from '@fastify/websocket' - -import { createWorker as createTesseractWorker } from 'tesseract.js' -import witPkg from 'node-wit' -const { Wit } = witPkg - -import { inspect as inspectObject } from 'node:util' - -import Client from './classes/Client.js' - -import { - EventContext, - parseImageEventHandler, - parseTextEventHandler, -} from './events/index.js' - -import { getConfig, checkEnv, logger } from './utils/index.js' -import { WebSocket } from 'ws' -import { DisconnectReason, HumanizedDisconnectReason } from '@revanced/bot-shared' - -// Load environment variables and config - -(async () => { - -const environment = checkEnv(logger) -const config = getConfig() - -if (!config.debugLogsInProduction && environment === 'production') logger.debug = () => {} - -// Workers and API clients - -const tesseractWorker = await createTesseractWorker('eng') -const witClient = new Wit({ - accessToken: process.env['WIT_AI_TOKEN']!, -}) - -process.on('beforeExit', () => tesseractWorker.terminate()) - -// Server logic - -const clients = new Set() -const clientSocketMap = new WeakMap() -const eventContext: EventContext = { - tesseractWorker, - logger, - witClient, - config, -} - -const server = fastify() - .register(fastifyWebsocket, { - options: { - // 16 KiB max payload - // A Discord message can not be longer than 4000 characters - // OCR should not be longer than 16000 characters - maxPayload: 16 * 1024, - }, - }) - .register(async instance => { - instance.get('/', { websocket: true }, async (connection, request) => { - try { - const client = new Client({ - socket: connection.socket, - id: request.hostname, - heartbeatInterval: config.clientHeartbeatInterval, - }) - - clientSocketMap.set(connection.socket, client) - clients.add(client) - - logger.debug(`Client ${client.id}'s instance has been added`) - logger.info( - `New client connected (now ${clients.size} clients) with ID:`, - client.id - ) - - client.on('disconnect', reason => { - clients.delete(client) - logger.info( - `Client ${client.id} disconnected because client ${HumanizedDisconnectReason[reason]}` - ) - }) - - client.on('parseText', async packet => - parseTextEventHandler(packet, eventContext) - ) - - client.on('parseImage', async packet => - parseImageEventHandler(packet, eventContext) - ) - - if (environment === 'development' && !config.debugLogsInProduction) { - logger.debug('Running development mode or debug logs in production is enabled, attaching debug events...') - client.on('packet', ({ client: _, ...rawPacket }) => - logger.debug( - `Packet received from client ${client.id}:`, - inspectObject(rawPacket) - ) - ) - - client.on('heartbeat', () => - logger.debug('Heartbeat received from client', client.id) - ) - } - } catch (e) { - if (e instanceof Error) logger.error(e.stack ?? e.message) - else logger.error(inspectObject(e)) - - const client = clientSocketMap.get(connection.socket) - - if (!client) { - logger.error( - 'Missing client instance when encountering an error. If the instance still exists in memory, it will NOT be removed!' - ) - return connection.socket.terminate() - } - - if (client.disconnected === false) - client.disconnect(DisconnectReason.ServerError) - else client.forceDisconnect() - - clients.delete(client) - - logger.debug( - `Client ${client.id} disconnected because of an internal error` - ) - } - }) - }) - -// Start the server - -logger.debug('Starting with these configurations:', inspectObject(config)) - -await server.listen({ - host: config.address ?? '0.0.0.0', - port: config.port ?? 80, -}) - -const addressInfo = server.server.address() -if (!addressInfo || typeof addressInfo !== 'object') - logger.debug('Server started, but cannot determine address information') -else - logger.info( - 'Server started at:', - `${addressInfo.address}:${addressInfo.port}` - ) - -})() +import { fastify } from 'fastify' +import fastifyWebsocket from '@fastify/websocket' + +import { createWorker as createTesseractWorker } from 'tesseract.js' +import witPkg from 'node-wit' +const { Wit } = witPkg + +import { inspect as inspectObject } from 'node:util' + +import Client from './classes/Client.js' + +import { + EventContext, + parseImageEventHandler, + parseTextEventHandler, +} from './events/index.js' + +import { getConfig, checkEnv, logger } from './utils/index.js' +import { WebSocket } from 'ws' +import { + DisconnectReason, + HumanizedDisconnectReason, +} from '@revanced/bot-shared' + +// Check environment variables and load config +const environment = checkEnv(logger) +const config = getConfig() + +if (!config.debugLogsInProduction && environment === 'production') + logger.debug = () => {} + +// Workers and API clients + +const tesseractWorker = await createTesseractWorker('eng') +const witClient = new Wit({ + accessToken: process.env['WIT_AI_TOKEN']!, +}) + +process.on('beforeExit', () => tesseractWorker.terminate()) + +// Server logic + +const clients = new Set() +const clientSocketMap = new WeakMap() +const eventContext: EventContext = { + tesseractWorker, + logger, + witClient, + config, +} + +const server = fastify() + .register(fastifyWebsocket, { + options: { + // 16 KiB max payload + // A Discord message can not be longer than 4000 characters + // OCR should not be longer than 16000 characters + maxPayload: 16 * 1024, + }, + }) + .register(async instance => { + instance.get('/', { websocket: true }, async (connection, request) => { + try { + const client = new Client({ + socket: connection.socket, + id: request.hostname, + heartbeatInterval: config.clientHeartbeatInterval, + }) + + clientSocketMap.set(connection.socket, client) + clients.add(client) + + logger.debug(`Client ${client.id}'s instance has been added`) + logger.info( + `New client connected (now ${clients.size} clients) with ID:`, + client.id + ) + + client.on('disconnect', reason => { + clients.delete(client) + logger.info( + `Client ${client.id} disconnected because client ${HumanizedDisconnectReason[reason]}` + ) + }) + + client.on('parseText', async packet => + parseTextEventHandler(packet, eventContext) + ) + + client.on('parseImage', async packet => + parseImageEventHandler(packet, eventContext) + ) + + if ( + environment === 'development' && + !config.debugLogsInProduction + ) { + logger.debug( + 'Running development mode or debug logs in production is enabled, attaching debug events...' + ) + client.on('packet', ({ client, ...rawPacket }) => + logger.debug( + `Packet received from client ${client.id}:`, + inspectObject(rawPacket) + ) + ) + + client.on('heartbeat', () => + logger.debug( + 'Heartbeat received from client', + client.id + ) + ) + } + } catch (e) { + if (e instanceof Error) logger.error(e.stack ?? e.message) + else logger.error(inspectObject(e)) + + const client = clientSocketMap.get(connection.socket) + + if (!client) { + logger.error( + 'Missing client instance when encountering an error. If the instance still exists in memory, it will NOT be removed!' + ) + return connection.socket.terminate() + } + + if (client.disconnected === false) + client.disconnect(DisconnectReason.ServerError) + else client.forceDisconnect() + + clients.delete(client) + + logger.debug( + `Client ${client.id} disconnected because of an internal error` + ) + } + }) + }) + +// Start the server + +logger.debug('Starting with these configurations:', inspectObject(config)) + +await server.listen({ + host: config.address ?? '0.0.0.0', + port: config.port ?? 80, +}) + +const addressInfo = server.server.address() +if (!addressInfo || typeof addressInfo !== 'object') + logger.debug('Server started, but cannot determine address information') +else + logger.info( + 'Server started at:', + `${addressInfo.address}:${addressInfo.port}` + ) diff --git a/apis/websocket/src/utils/checkEnv.ts b/apis/websocket/src/utils/checkEnv.ts index 6bbe68b..00cee6d 100755 --- a/apis/websocket/src/utils/checkEnv.ts +++ b/apis/websocket/src/utils/checkEnv.ts @@ -1,9 +1,10 @@ import type { Logger } from './logger.js' export default function checkEnv(logger: Logger) { - if (!process.env['NODE_ENV']) logger.warn('NODE_ENV not set, defaulting to `development`') + if (!process.env['NODE_ENV']) + logger.warn('NODE_ENV not set, defaulting to `development`') const environment = (process.env['NODE_ENV'] ?? - 'development') as NodeEnvironment + 'development') as NodeEnvironment if (!['development', 'production'].includes(environment)) { logger.error( @@ -16,7 +17,9 @@ export default function checkEnv(logger: Logger) { logger.info(`Running in ${environment} mode...`) if (environment === 'production' && process.env['IS_USING_DOT_ENV']) { - logger.warn('You seem to be using .env files, this is generally not a good idea in production...') + logger.warn( + 'You seem to be using .env files, this is generally not a good idea in production...' + ) } if (!process.env['WIT_AI_TOKEN']) { diff --git a/apis/websocket/src/utils/getConfig.ts b/apis/websocket/src/utils/getConfig.ts index 2b39b31..384b695 100755 --- a/apis/websocket/src/utils/getConfig.ts +++ b/apis/websocket/src/utils/getConfig.ts @@ -2,28 +2,31 @@ import { existsSync } from 'node:fs' import { resolve as resolvePath } from 'node:path' import { pathToFileURL } from 'node:url' -const configPath = resolvePath( - process.cwd(), - 'config.json' -) +const configPath = resolvePath(process.cwd(), 'config.json') const userConfig: Partial = existsSync(configPath) - ? (await import(pathToFileURL(configPath).href, { - assert: { - type: 'json', - }, - })).default + ? ( + await import(pathToFileURL(configPath).href, { + assert: { + type: 'json', + }, + }) + ).default : {} type BaseTypeOf = T extends (infer U)[] ? U[] - : T extends (...args: any[]) => infer U - ? (...args: any[]) => U - : T extends object - ? { [K in keyof T]: T[K] } - : T + : T extends (...args: unknown[]) => infer U + ? (...args: unknown[]) => U + : T extends object + ? { [K in keyof T]: T[K] } + : T + +export type Config = Omit< + BaseTypeOf, + '$schema' +> -export type Config = Omit, '$schema'> & {} export const defaultConfig: Config = { address: '127.0.0.1', port: 80, diff --git a/apis/websocket/src/utils/index.ts b/apis/websocket/src/utils/index.ts index 9fe7e87..913395a 100755 --- a/apis/websocket/src/utils/index.ts +++ b/apis/websocket/src/utils/index.ts @@ -1,3 +1,3 @@ export { default as getConfig } from './getConfig.js' export { default as checkEnv } from './checkEnv.js' -export { default as logger } from './logger.js' \ No newline at end of file +export { default as logger } from './logger.js' diff --git a/apis/websocket/src/utils/logger.ts b/apis/websocket/src/utils/logger.ts index 2a71a75..96ef157 100755 --- a/apis/websocket/src/utils/logger.ts +++ b/apis/websocket/src/utils/logger.ts @@ -3,14 +3,23 @@ import { Chalk } from 'chalk' const chalk = new Chalk() const logger = { debug: (...args) => console.debug(chalk.gray('DEBUG:', ...args)), - info: (...args) => console.info(chalk.bgBlue.whiteBright(' INFO '), ...args), - warn: (...args) => console.warn(chalk.bgYellow.blackBright.bold(' WARN '), chalk.yellowBright(...args)), - error: (...args) => console.error(chalk.bgRed.whiteBright.bold(' ERROR '), chalk.redBright(...args)), + info: (...args) => + console.info(chalk.bgBlue.whiteBright(' INFO '), ...args), + warn: (...args) => + console.warn( + chalk.bgYellow.blackBright.bold(' WARN '), + chalk.yellowBright(...args) + ), + error: (...args) => + console.error( + chalk.bgRed.whiteBright.bold(' ERROR '), + chalk.redBright(...args) + ), log: console.log, } satisfies Logger export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'log' -export type LogFunction = (...x: any[]) => void +export type LogFunction = (...x: unknown[]) => void export type Logger = Record -export default logger \ No newline at end of file +export default logger diff --git a/apis/websocket/tsconfig.json b/apis/websocket/tsconfig.json index f69c640..fd79f29 100755 --- a/apis/websocket/tsconfig.json +++ b/apis/websocket/tsconfig.json @@ -4,8 +4,8 @@ "baseUrl": ".", "outDir": "dist", "module": "ESNext", - "composite": false, + "composite": false }, "exclude": ["node_modules", "dist"], "include": ["./*.json", "src/**/*.ts"] -} \ No newline at end of file +} diff --git a/bun.lockb b/bun.lockb index 36573204465ab4fbc11e79d09d77c78be2c49c57..78b21eb9de591caa16b365c0cdfca07ea44cf545 100755 GIT binary patch delta 57150 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©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�tp+ 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 CXrxmB diff --git a/package.json b/package.json index 6ae41cf..102c106 100755 --- a/package.json +++ b/package.json @@ -1,57 +1,57 @@ -{ - "name": "revanced-helper", - "version": "0.0.0", - "description": "🤖 Bots assisting ReVanced on multiple platforms", - "private": true, - "workspaces": [ - "apis/*", - "bots/*", - "packages/*" - ], - "scripts": { - "build": "bun task build", - "commitlint": "commitlint --edit", - "format": "turbo run format", - "format:check": "turbo run format:check", - "lint": "turbo run lint", - "lint:apply": "turbo run lint:apply", - "watch": "bun task watch", - "task": "turbo run", - "t": "bun task", - "prepare": "lefthook install" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/revanced/revanced-helper.git" - }, - "author": "Palm (https://github.com/PalmDevs)", - "contributors": [ - "Palm (https://github.com/PalmDevs)", - "ReVanced (https://github.com/revanced)" - ], - "license": "GPL-3.0-or-later", - "bugs": { - "url": "https://github.com/revanced/revanced-helper/issues" - }, - "homepage": "https://github.com/revanced/revanced-helper#readme", - "devDependencies": { - "@commitlint/cli": "^18.4.3", - "@commitlint/config-conventional": "^18.4.3", - "@tsconfig/strictest": "^2.0.2", - "conventional-changelog-conventionalcommits": "^7.0.2", - "eslint": "^8.54.0", - "eslint-config-prettier": "^9.0.0", - "eslint-import-resolver-typescript": "^3.6.1", - "eslint-plugin-import": "^2.29.0", - "eslint-plugin-prettier": "^5.0.1", - "lefthook": "^1.5.3", - "prettier": "^3.1.0", - "semantic-release": "^22.0.8", - "turbo": "^1.10.16", - "typescript": "^5.3.2" - }, - "overrides": { - "uuid": ">=9.0.0", - "isomorphic-fetch": ">=3.0.0" - } -} +{ + "name": "revanced-helper", + "version": "0.0.0", + "description": "🤖 Bots assisting ReVanced on multiple platforms", + "private": true, + "workspaces": [ + "apis/*", + "bots/*", + "packages/*" + ], + "scripts": { + "build": "bun task build", + "commitlint": "commitlint --edit", + "format": "turbo run format", + "format:check": "turbo run format:check", + "lint": "turbo run lint", + "lint:apply": "turbo run lint:apply", + "watch": "bun task watch", + "task": "turbo run", + "t": "bun task", + "prepare": "lefthook install" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/revanced/revanced-helper.git" + }, + "author": "Palm (https://github.com/PalmDevs)", + "contributors": [ + "Palm (https://github.com/PalmDevs)", + "ReVanced (https://github.com/revanced)" + ], + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/revanced/revanced-helper/issues" + }, + "homepage": "https://github.com/revanced/revanced-helper#readme", + "devDependencies": { + "@commitlint/cli": "^18.4.3", + "@commitlint/config-conventional": "^18.4.3", + "@tsconfig/strictest": "^2.0.2", + "conventional-changelog-conventionalcommits": "^7.0.2", + "eslint": "^8.54.0", + "eslint-config-prettier": "^9.0.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-prettier": "^5.0.1", + "lefthook": "^1.5.3", + "prettier": "^3.1.0", + "semantic-release": "^22.0.8", + "turbo": "^1.10.16", + "typescript": "^5.3.2" + }, + "overrides": { + "uuid": ">=9.0.0", + "isomorphic-fetch": ">=3.0.0" + } +} diff --git a/packages/api/package.json b/packages/api/package.json index 84bcd7f..9604004 100755 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,40 +1,40 @@ -{ - "name": "@revanced/bot-api", - "type": "module", - "version": "0.1.0", - "description": "🙌🏻 Programmatic API for bots assisting ReVanced to communicate to its API server", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "bun bundle && bun types", - "watch": "conc --raw \"bun bundle:watch\" \"bun types:watch\"", - "bundle": "bun build src/index.ts --outdir=dist --sourcemap=external --target=node --minify", - "bundle:watch": "bun run bundle --watch", - "types": "tsc --declaration --emitDeclarationOnly", - "types:watch": "bun types --watch --preserveWatchOutput", - "types:clean": "bun types --build --clean" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/revanced/revanced-helper.git", - "directory": "packages/api" - }, - "author": "Palm (https://github.com/PalmDevs)", - "contributors": [ - "Palm (https://github.com/PalmDevs)", - "ReVanced (https://github.com/revanced)" - ], - "license": "GPL-3.0-or-later", - "bugs": { - "url": "https://github.com/revanced/revanced-helper/issues" - }, - "homepage": "https://github.com/revanced/revanced-helper#readme", - "dependencies": { - "@revanced/bot-shared": "workspace:*", - "ws": "^8.14.2" - }, - "devDependencies": { - "@types/ws": "^8.5.10", - "typed-emitter": "^2.1.0" - } -} +{ + "name": "@revanced/bot-api", + "type": "module", + "version": "0.1.0", + "description": "🙌🏻 Programmatic API for bots assisting ReVanced to communicate to its API server", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "bun bundle && bun types", + "watch": "conc --raw \"bun bundle:watch\" \"bun types:watch\"", + "bundle": "bun build src/index.ts --outdir=dist --sourcemap=external --target=node --minify", + "bundle:watch": "bun run bundle --watch", + "types": "tsc --declaration --emitDeclarationOnly", + "types:watch": "bun types --watch --preserveWatchOutput", + "types:clean": "bun types --build --clean" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/revanced/revanced-helper.git", + "directory": "packages/api" + }, + "author": "Palm (https://github.com/PalmDevs)", + "contributors": [ + "Palm (https://github.com/PalmDevs)", + "ReVanced (https://github.com/revanced)" + ], + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/revanced/revanced-helper/issues" + }, + "homepage": "https://github.com/revanced/revanced-helper#readme", + "dependencies": { + "@revanced/bot-shared": "workspace:*", + "ws": "^8.14.2" + }, + "devDependencies": { + "@types/ws": "^8.5.10", + "typed-emitter": "^2.1.0" + } +} diff --git a/packages/api/src/classes/Client.ts b/packages/api/src/classes/Client.ts index 9daab24..725a811 100755 --- a/packages/api/src/classes/Client.ts +++ b/packages/api/src/classes/Client.ts @@ -49,7 +49,9 @@ export default class Client { rs(packet) } - const parseTextFailedListener = (packet: Packet) => { + const parseTextFailedListener = ( + packet: Packet + ) => { if (packet.d.id !== currentId) return this.gateway.off('parseTextFailed', parseTextFailedListener) rj(packet) @@ -84,7 +86,9 @@ export default class Client { rs(packet) } - const parseImageFailedListener = (packet: Packet) => { + const parseImageFailedListener = ( + packet: Packet + ) => { if (packet.d.id !== currentId) return this.gateway.off('parseImageFailed', parseImageFailedListener) rj(packet) diff --git a/packages/api/src/classes/ClientGateway.ts b/packages/api/src/classes/ClientGateway.ts index 8ff72e1..eb119b4 100755 --- a/packages/api/src/classes/ClientGateway.ts +++ b/packages/api/src/classes/ClientGateway.ts @@ -24,8 +24,7 @@ export default class ClientGateway { #hbTimeout: NodeJS.Timeout = null! #socket: WebSocket = null! - #emitter = - new EventEmitter() as TypedEmitter + #emitter = new EventEmitter() as TypedEmitter constructor(options: ClientGatewayOptions) { this.url = options.url @@ -110,6 +109,7 @@ export default class ClientGateway { switch (packet.op) { case ServerOperation.Hello: + // eslint-disable-next-line no-case-declarations const data = Object.freeze( (packet as Packet).d ) @@ -124,9 +124,11 @@ export default class ClientGateway { default: return this.#emitter.emit( uncapitalize( - ServerOperation[packet.op] as ClientGatewayServerEventName + ServerOperation[ + packet.op + ] as ClientGatewayServerEventName ), - // @ts-expect-error + // @ts-expect-error TypeScript doesn't know that the lines above negate the type enough packet ) } diff --git a/packages/api/src/classes/index.ts b/packages/api/src/classes/index.ts index 2ad36c8..e02f863 100755 --- a/packages/api/src/classes/index.ts +++ b/packages/api/src/classes/index.ts @@ -1,4 +1,4 @@ export { default as Client } from './Client.js' export * from './Client.js' export { default as ClientGateway } from './ClientGateway.js' -export * from './ClientGateway.js' \ No newline at end of file +export * from './ClientGateway.js' diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 9342b09..f4b850d 100755 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -5,7 +5,7 @@ "rootDir": "./src", "outDir": "dist", "module": "ESNext", - "composite": true, + "composite": true }, "exclude": ["node_modules", "dist"] -} \ No newline at end of file +} diff --git a/packages/api/utility-types.d.ts b/packages/api/utility-types.d.ts index 7fc44c5..1710e17 100755 --- a/packages/api/utility-types.d.ts +++ b/packages/api/utility-types.d.ts @@ -1 +1 @@ -type RequiredProperty = { [P in keyof T]: Required>; }; \ No newline at end of file +type RequiredProperty = { [P in keyof T]: Required> } diff --git a/packages/shared/package.json b/packages/shared/package.json index ac2f589..f877e75 100755 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,37 +1,37 @@ -{ - "name": "@revanced/bot-shared", - "type": "module", - "version": "0.1.0", - "description": "🙌🏻 Shared components for bots assisting ReVanced", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "bun bundle && bun types", - "watch": "conc --raw \"bun bundle:watch\" \"bun types:watch\"", - "bundle": "bun build src/index.ts --outdir=dist --sourcemap=external --target=node --minify", - "bundle:watch": "bun run bundle --watch", - "types": "tsc --declaration --emitDeclarationOnly", - "types:watch": "bun types --watch --preserveWatchOutput", - "types:clean": "bun types --build --clean" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/revanced/revanced-helper.git", - "directory": "packages/shared" - }, - "author": "Palm (https://github.com/PalmDevs)", - "contributors": [ - "Palm (https://github.com/PalmDevs)", - "ReVanced (https://github.com/revanced)" - ], - "license": "GPL-3.0-or-later", - "bugs": { - "url": "https://github.com/revanced/revanced-helper/issues" - }, - "homepage": "https://github.com/revanced/revanced-helper#readme", - "dependencies": { - "bson": "^6.2.0", - "valibot": "^0.21.0", - "zod": "^3.22.4" - } -} +{ + "name": "@revanced/bot-shared", + "type": "module", + "version": "0.1.0", + "description": "🙌🏻 Shared components for bots assisting ReVanced", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "bun bundle && bun types", + "watch": "conc --raw \"bun bundle:watch\" \"bun types:watch\"", + "bundle": "bun build src/index.ts --outdir=dist --sourcemap=external --target=node --minify", + "bundle:watch": "bun run bundle --watch", + "types": "tsc --declaration --emitDeclarationOnly", + "types:watch": "bun types --watch --preserveWatchOutput", + "types:clean": "bun types --build --clean" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/revanced/revanced-helper.git", + "directory": "packages/shared" + }, + "author": "Palm (https://github.com/PalmDevs)", + "contributors": [ + "Palm (https://github.com/PalmDevs)", + "ReVanced (https://github.com/revanced)" + ], + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/revanced/revanced-helper/issues" + }, + "homepage": "https://github.com/revanced/revanced-helper#readme", + "dependencies": { + "bson": "^6.2.0", + "valibot": "^0.21.0", + "zod": "^3.22.4" + } +} diff --git a/packages/shared/src/constants/DisconnectReason.ts b/packages/shared/src/constants/DisconnectReason.ts index cd017f8..051d5e3 100755 --- a/packages/shared/src/constants/DisconnectReason.ts +++ b/packages/shared/src/constants/DisconnectReason.ts @@ -19,9 +19,9 @@ enum DisconnectReason { */ ServerError, /** - * The client had never connected to the server (**CLIENT-ONLY**) + * The client had never connected to the server (**CLIENT-ONLY**) */ - NeverConnected + NeverConnected, } -export default DisconnectReason \ No newline at end of file +export default DisconnectReason diff --git a/packages/shared/src/constants/HumanizedDisconnectReason.ts b/packages/shared/src/constants/HumanizedDisconnectReason.ts index 95f510a..d9b4022 100755 --- a/packages/shared/src/constants/HumanizedDisconnectReason.ts +++ b/packages/shared/src/constants/HumanizedDisconnectReason.ts @@ -4,8 +4,9 @@ const HumanizedDisconnectReason = { [DisconnectReason.InvalidPacket]: 'has sent invalid packet', [DisconnectReason.Generic]: 'has been disconnected for unknown reasons', [DisconnectReason.TimedOut]: 'has timed out', - [DisconnectReason.ServerError]: 'has been disconnected due to an internal server error', - [DisconnectReason.NeverConnected]: 'had never connected to the server' + [DisconnectReason.ServerError]: + 'has been disconnected due to an internal server error', + [DisconnectReason.NeverConnected]: 'had never connected to the server', } as const satisfies Record -export default HumanizedDisconnectReason \ No newline at end of file +export default HumanizedDisconnectReason diff --git a/packages/shared/src/constants/Operation.ts b/packages/shared/src/constants/Operation.ts index 5a0a7cf..dcd0b96 100755 --- a/packages/shared/src/constants/Operation.ts +++ b/packages/shared/src/constants/Operation.ts @@ -50,8 +50,8 @@ export enum ServerOperation { /** * Server's disconnect message */ - Disconnect = 20 + Disconnect = 20, } export const Operation = { ...ClientOperation, ...ServerOperation } as const -export type Operation = (ClientOperation | ServerOperation) \ No newline at end of file +export type Operation = ClientOperation | ServerOperation diff --git a/packages/shared/src/constants/index.ts b/packages/shared/src/constants/index.ts index aa16eda..097859d 100755 --- a/packages/shared/src/constants/index.ts +++ b/packages/shared/src/constants/index.ts @@ -1,3 +1,3 @@ export { default as DisconnectReason } from './DisconnectReason.js' export { default as HumanizedDisconnectReason } from './HumanizedDisconnectReason.js' -export * from './Operation.js' \ No newline at end of file +export * from './Operation.js' diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 4cc2cfc..e5ef662 100755 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -1,3 +1,3 @@ export * from './constants/index.js' export * from './schemas/index.js' -export * from './utils/index.js' \ No newline at end of file +export * from './utils/index.js' diff --git a/packages/shared/src/schemas/index.ts b/packages/shared/src/schemas/index.ts index bbcb9e0..b593970 100755 --- a/packages/shared/src/schemas/index.ts +++ b/packages/shared/src/schemas/index.ts @@ -1 +1 @@ -export * from './Packet.js' \ No newline at end of file +export * from './Packet.js' diff --git a/packages/shared/src/utils/guard.ts b/packages/shared/src/utils/guard.ts index b8fd1ad..24a98e2 100755 --- a/packages/shared/src/utils/guard.ts +++ b/packages/shared/src/utils/guard.ts @@ -1,5 +1,9 @@ import { Packet } from '../schemas/Packet.js' -import { ClientOperation, Operation, ServerOperation } from '../constants/Operation.js' +import { + ClientOperation, + Operation, + ServerOperation, +} from '../constants/Operation.js' /** * Checks whether a packet is trying to do the given operation @@ -7,7 +11,10 @@ import { ClientOperation, Operation, ServerOperation } from '../constants/Operat * @param packet A packet * @returns Whether this packet is trying to do the operation given */ -export function packetMatchesOperation(op: TOp, packet: Packet): packet is Packet { +export function packetMatchesOperation( + op: TOp, + packet: Packet +): packet is Packet { return packet.op === op } @@ -16,7 +23,9 @@ export function packetMatchesOperation(op: TOp, packet: P * @param packet A packet * @returns Whether this packet is a client packet */ -export function isClientPacket(packet: Packet): packet is Packet { +export function isClientPacket( + packet: Packet +): packet is Packet { return packet.op in ClientOperation } @@ -25,6 +34,8 @@ export function isClientPacket(packet: Packet): packet is Packet { +export function isServerPacket( + packet: Packet +): packet is Packet { return packet.op in ServerOperation -} \ No newline at end of file +} diff --git a/packages/shared/src/utils/serialization.ts b/packages/shared/src/utils/serialization.ts index 807ddc5..764af14 100755 --- a/packages/shared/src/utils/serialization.ts +++ b/packages/shared/src/utils/serialization.ts @@ -20,4 +20,4 @@ export function serializePacket(packet: Packet) { export function deserializePacket(buffer: Buffer) { const data = BSON.deserialize(buffer) return parse(PacketSchema, data) as Packet -} \ No newline at end of file +} diff --git a/packages/shared/src/utils/string.ts b/packages/shared/src/utils/string.ts index 1c63acf..589bfa5 100755 --- a/packages/shared/src/utils/string.ts +++ b/packages/shared/src/utils/string.ts @@ -1,3 +1,3 @@ export function uncapitalize(str: T): Uncapitalize { - return str.charAt(0).toLowerCase() + str.slice(1) as Uncapitalize -} \ No newline at end of file + return (str.charAt(0).toLowerCase() + str.slice(1)) as Uncapitalize +} diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json index 9342b09..f4b850d 100755 --- a/packages/shared/tsconfig.json +++ b/packages/shared/tsconfig.json @@ -5,7 +5,7 @@ "rootDir": "./src", "outDir": "dist", "module": "ESNext", - "composite": true, + "composite": true }, "exclude": ["node_modules", "dist"] -} \ No newline at end of file +} diff --git a/tsconfig.apis.json b/tsconfig.apis.json index 80c29da..89b3d2b 100755 --- a/tsconfig.apis.json +++ b/tsconfig.apis.json @@ -1,3 +1,3 @@ { - "extends": "./tsconfig.base.json", + "extends": "./tsconfig.base.json" } diff --git a/tsconfig.base.json b/tsconfig.base.json index f11e436..a176582 100755 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1,6 +1,6 @@ { // `bun-types` will not be added until https://github.com/oven-sh/bun/issues/7247 is fixed - "extends": ["@tsconfig/strictest", /* "bun-types" */], + "extends": ["@tsconfig/strictest" /* "bun-types" */], "compilerOptions": { "lib": ["ESNext"], "module": "NodeNext", @@ -12,6 +12,6 @@ "esModuleInterop": true, "declaration": false, "allowSyntheticDefaultImports": true, - "isolatedModules": true, - }, + "isolatedModules": true + } } diff --git a/tsconfig.packages.json b/tsconfig.packages.json index 831dee6..5b6cb51 100755 --- a/tsconfig.packages.json +++ b/tsconfig.packages.json @@ -2,7 +2,7 @@ "extends": "./tsconfig.base.json", "compilerOptions": { "declaration": true, - "declarationMap": true, + "declarationMap": true }, "references": [ { @@ -10,6 +10,6 @@ }, { "path": "./packages/api" - }, + } ] } diff --git a/turbo.json b/turbo.json index 00f8c5a..263271e 100755 --- a/turbo.json +++ b/turbo.json @@ -1,26 +1,26 @@ -{ - "$schema": "https://turbo.build/schema.json", - "pipeline": { - "build": { - "dependsOn": ["^build"], - "outputs": ["dist/**"], - "outputMode": "errors-only" - }, - "watch": { - "dependsOn": ["^watch"], - "outputMode": "errors-only" - }, - "format": { - "dependsOn": ["^format"], - "outputMode": "errors-only" - }, - "lint": { - "dependsOn": ["^lint"], - "outputMode": "errors-only" - }, - "lint:apply": { - "dependsOn": ["^lint:apply"], - "outputMode": "errors-only" - } - } -} \ No newline at end of file +{ + "$schema": "https://turbo.build/schema.json", + "pipeline": { + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**"], + "outputMode": "errors-only" + }, + "watch": { + "dependsOn": ["^watch"], + "outputMode": "errors-only" + }, + "format": { + "dependsOn": ["^format"], + "outputMode": "errors-only" + }, + "lint": { + "dependsOn": ["^lint"], + "outputMode": "errors-only" + }, + "lint:apply": { + "dependsOn": ["^lint:apply"], + "outputMode": "errors-only" + } + } +}