From dc7e02477995c756040d6a212e91ecf153459769 Mon Sep 17 00:00:00 2001 From: Andrea Salvatore Date: Mon, 30 Oct 2023 12:42:32 +0100 Subject: [PATCH] Socket.io server code to TS and adds unit testing (#418) * feat: initial typescript setup * feat: converts to ts `api-config` file * feat: converts to ts `socket-config` file * feat: converts to ts `rate-limiter` file * feat: converts to ts `utils` file * feat: updates the `Dockerfile` to support prebuilding from typescript * fix: fixes docker image running * feat: adds package.json start:docker script, setup unit testing and adds it to `api-config` file * feat: adds unit tests for `rate-limiter` and `socket-config` files * chore: add jest config files * fix: fixes socket-config tests and refactors utils.ts for testability --------- Co-authored-by: Omri --- packages/sdk-socket-server/Dockerfile | 26 +- .../__mocks__/analytics-node.ts | 14 + packages/sdk-socket-server/api-config.test.ts | 38 ++ .../{api-config.js => api-config.ts} | 47 +- packages/sdk-socket-server/index.js | 23 - packages/sdk-socket-server/index.ts | 27 + packages/sdk-socket-server/jest.config.ts | 19 + packages/sdk-socket-server/nodemon.json | 5 + packages/sdk-socket-server/package.json | 36 +- .../sdk-socket-server/rate-limiter.test.ts | 43 ++ .../{rate-limiter.js => rate-limiter.ts} | 28 +- packages/sdk-socket-server/socket-config.js | 220 ------- .../sdk-socket-server/socket-config.test.ts | 64 ++ packages/sdk-socket-server/socket-config.ts | 253 ++++++++ packages/sdk-socket-server/tsconfig.json | 19 + packages/sdk-socket-server/tsconfig.test.json | 14 + packages/sdk-socket-server/utils.js | 47 -- packages/sdk-socket-server/utils.ts | 69 +++ yarn.lock | 585 +++++++++++++++++- 19 files changed, 1218 insertions(+), 359 deletions(-) create mode 100644 packages/sdk-socket-server/__mocks__/analytics-node.ts create mode 100644 packages/sdk-socket-server/api-config.test.ts rename packages/sdk-socket-server/{api-config.js => api-config.ts} (62%) delete mode 100644 packages/sdk-socket-server/index.js create mode 100644 packages/sdk-socket-server/index.ts create mode 100644 packages/sdk-socket-server/jest.config.ts create mode 100644 packages/sdk-socket-server/nodemon.json create mode 100644 packages/sdk-socket-server/rate-limiter.test.ts rename packages/sdk-socket-server/{rate-limiter.js => rate-limiter.ts} (82%) delete mode 100644 packages/sdk-socket-server/socket-config.js create mode 100644 packages/sdk-socket-server/socket-config.test.ts create mode 100644 packages/sdk-socket-server/socket-config.ts create mode 100644 packages/sdk-socket-server/tsconfig.json create mode 100644 packages/sdk-socket-server/tsconfig.test.json delete mode 100644 packages/sdk-socket-server/utils.js create mode 100644 packages/sdk-socket-server/utils.ts diff --git a/packages/sdk-socket-server/Dockerfile b/packages/sdk-socket-server/Dockerfile index 521142896..2561b64de 100644 --- a/packages/sdk-socket-server/Dockerfile +++ b/packages/sdk-socket-server/Dockerfile @@ -1,15 +1,27 @@ -FROM node:18-alpine - -RUN apk update && apk upgrade && apk add --no-cache zlib +# Build stage +FROM node:18-alpine AS builder +# Install build dependencies and build the project WORKDIR /app +COPY package.json ./ +RUN yarn install +COPY . . +RUN yarn build -COPY package.json . +# Runtime stage +FROM node:18-alpine -RUN yarn install +# Install runtime dependencies +WORKDIR /app +COPY --from=builder /app/package.json ./ +RUN yarn install --production -COPY . . +# Copy built project and .env file from the build stage +COPY --from=builder /app/dist ./dist +COPY .env ./ +# Expose the server port EXPOSE 4000 -CMD ["yarn","start"] +# Start the server +CMD ["node", "dist/index.js"] diff --git a/packages/sdk-socket-server/__mocks__/analytics-node.ts b/packages/sdk-socket-server/__mocks__/analytics-node.ts new file mode 100644 index 000000000..fbc8fdb58 --- /dev/null +++ b/packages/sdk-socket-server/__mocks__/analytics-node.ts @@ -0,0 +1,14 @@ +class Analytics { + constructor(writeKey: string, options?: object) { + // Mock constructor + } + + track(event: object, callback?: (err: Error | null) => void): void { + // Mock track method + if (callback) { + callback(null); + } + } +} + +export default Analytics; diff --git a/packages/sdk-socket-server/api-config.test.ts b/packages/sdk-socket-server/api-config.test.ts new file mode 100644 index 000000000..c56f79078 --- /dev/null +++ b/packages/sdk-socket-server/api-config.test.ts @@ -0,0 +1,38 @@ +import request from 'supertest'; +import { app } from './api-config'; + +jest.mock('analytics-node'); + +describe('API Config', () => { + describe('GET /', () => { + it('should respond with success', async () => { + const response = await request(app).get('/'); + expect(response.status).toBe(200); + expect(response.body).toStrictEqual({ success: true }); + }); + }); + + describe('POST /debug', () => { + it('should respond with error when event is missing', async () => { + const response = await request(app).post('/debug').send({}); + expect(response.status).toBe(400); + expect(response.body).toStrictEqual({ error: 'event is required' }); + }); + + it('should respond with error when event name is wrong', async () => { + const response = await request(app) + .post('/debug') + .send({ event: 'wrong_event' }); + expect(response.status).toBe(400); + expect(response.body).toStrictEqual({ error: 'wrong event name' }); + }); + + it('should respond with success when event is correct', async () => { + const response = await request(app) + .post('/debug') + .send({ event: 'sdk_test' }); + expect(response.status).toBe(200); + expect(response.body).toStrictEqual({ success: true }); + }); + }); +}); diff --git a/packages/sdk-socket-server/api-config.js b/packages/sdk-socket-server/api-config.ts similarity index 62% rename from packages/sdk-socket-server/api-config.js rename to packages/sdk-socket-server/api-config.ts index 471b3c925..0d291d92a 100644 --- a/packages/sdk-socket-server/api-config.js +++ b/packages/sdk-socket-server/api-config.ts @@ -1,17 +1,17 @@ /* eslint-disable node/no-process-env */ -const crypto = require('crypto'); -const express = require('express'); -const bodyParser = require('body-parser'); -const cors = require('cors'); -const helmet = require('helmet'); -const { LRUCache } = require('lru-cache'); -const Analytics = require('analytics-node'); +import crypto from 'crypto'; +import express from 'express'; +import bodyParser from 'body-parser'; +import cors from 'cors'; +import helmet from 'helmet'; +import { LRUCache } from 'lru-cache'; +import Analytics from 'analytics-node'; -const isDevelopment = process.env.NODE_ENV === 'development'; +const isDevelopment: boolean = process.env.NODE_ENV === 'development'; -const userIdHashCache = new LRUCache({ +const userIdHashCache = new LRUCache({ max: 5000, - maxAge: 1000 * 60 * 60 * 24, + ttl: 1000 * 60 * 60 * 24, }); const app = express(); @@ -25,11 +25,11 @@ app.disable('x-powered-by'); const analytics = new Analytics( isDevelopment - ? process.env.SEGMENT_API_KEY_DEBUG - : process.env.SEGMENT_API_KEY_PRODUCTION, + ? process.env.SEGMENT_API_KEY_DEBUG || '' + : process.env.SEGMENT_API_KEY_PRODUCTION || '', { flushInterval: isDevelopment ? 1000 : 10000, - errorHandler: (err) => { + errorHandler: (err: Error) => { console.error(`ERROR> Analytics-node flush failed: ${err}`); }, }, @@ -51,15 +51,22 @@ app.post('/debug', (_req, res) => { return res.status(400).json({ error: 'wrong event name' }); } - const id = body.id || 'socket.io-server'; - let userIdHash = userIdHashCache.get(id); + const id: string = body.id || 'socket.io-server'; + let userIdHash: string | undefined = userIdHashCache.get(id); if (!userIdHash) { userIdHash = crypto.createHash('sha1').update(id).digest('hex'); userIdHashCache.set(id, userIdHash); } - const event = { + const event: { + userId: string; + event: string; + properties: { + userId: string; + [key: string]: string | undefined; // This line adds an index signature + }; + } = { userId: userIdHash, event: body.event, properties: { @@ -68,7 +75,7 @@ app.post('/debug', (_req, res) => { }; // Define properties to be excluded - const propertiesToExclude = ['icon']; + const propertiesToExclude: string[] = ['icon']; for (const property in body) { if ( @@ -84,9 +91,9 @@ app.post('/debug', (_req, res) => { console.log('DEBUG> Event object:', event); } - analytics.track(event, function (err, batch) { + analytics.track(event, function (err: Error) { if (isDevelopment) { - console.log('DEBUG> Segment batch', batch); + console.log('DEBUG> Segment batch'); } if (err) { @@ -100,4 +107,4 @@ app.post('/debug', (_req, res) => { } }); -module.exports = { app, analytics }; +export { app, analytics }; diff --git a/packages/sdk-socket-server/index.js b/packages/sdk-socket-server/index.js deleted file mode 100644 index 215211c4a..000000000 --- a/packages/sdk-socket-server/index.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable node/no-process-env */ -require('dotenv').config(); -const http = require('http'); - -const { app, analytics } = require('./api-config'); -const configureSocketIO = require('./socket-config'); -const { cleanupAndExit } = require('./utils'); - -const isDevelopment = process.env.NODE_ENV === 'development'; - -const server = http.createServer(app); -configureSocketIO(server); // configure socket.io server - -console.log('INFO> isDevelopment?', isDevelopment); - -// Register event listeners for process termination events -process.on('SIGINT', () => cleanupAndExit(server, analytics)); -process.on('SIGTERM', () => cleanupAndExit(server, analytics)); - -const port = process.env.port || 4000; -server.listen(port, () => { - console.log(`INFO> listening on *:${port}`); -}); diff --git a/packages/sdk-socket-server/index.ts b/packages/sdk-socket-server/index.ts new file mode 100644 index 000000000..6cd4b556c --- /dev/null +++ b/packages/sdk-socket-server/index.ts @@ -0,0 +1,27 @@ +import dotenv from 'dotenv'; +dotenv.config(); + +import http from 'http'; +import { app, analytics } from './api-config'; +import configureSocketIO from './socket-config'; +import { cleanupAndExit } from './utils'; + +const isDevelopment: boolean = process.env.NODE_ENV === 'development'; + +const server = http.createServer(app); +configureSocketIO(server); // configure socket.io server + +console.log('INFO> isDevelopment?', isDevelopment); + +// Register event listeners for process termination events +process.on('SIGINT', async () => { + await cleanupAndExit(server, analytics); +}); +process.on('SIGTERM', async () => { + await cleanupAndExit(server, analytics); +}); + +const port: number = Number(process.env.PORT) || 4000; +server.listen(port, () => { + console.log(`INFO> listening on *:${port}`); +}); diff --git a/packages/sdk-socket-server/jest.config.ts b/packages/sdk-socket-server/jest.config.ts new file mode 100644 index 000000000..e48363f92 --- /dev/null +++ b/packages/sdk-socket-server/jest.config.ts @@ -0,0 +1,19 @@ +import baseConfig from '../../jest.config.base'; + +module.exports = { + ...baseConfig, + testEnvironment: 'node', + coveragePathIgnorePatterns: ['./types', './index.ts'], + collectCoverageFrom: ['*.ts'], + coverageThreshold: { + global: { + branches: 28, + functions: 44, + lines: 49, + statements: 49, + }, + }, + clearMocks: true, + resetMocks: false, + restoreMocks: false, +}; diff --git a/packages/sdk-socket-server/nodemon.json b/packages/sdk-socket-server/nodemon.json new file mode 100644 index 000000000..952bc288c --- /dev/null +++ b/packages/sdk-socket-server/nodemon.json @@ -0,0 +1,5 @@ +{ + "watch": ["."], + "ext": "ts", + "exec": "ts-node" +} diff --git a/packages/sdk-socket-server/package.json b/packages/sdk-socket-server/package.json index ce67c623d..30ce95f12 100644 --- a/packages/sdk-socket-server/package.json +++ b/packages/sdk-socket-server/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/sdk-socket-server", - "version": "1.0.0", + "version": "2.0.0", "private": true, "description": "", "homepage": "https://github.com/MetaMask/metamask-sdk#readme", @@ -12,21 +12,22 @@ "url": "https://github.com/MetaMask/metamask-sdk.git" }, "author": "", - "main": "index.js", + "main": "dist/index.js", "scripts": { - "build": "echo 'N/A'", + "build": "tsc", "build:post-tsc": "echo 'N/A'", "build:pre-tsc": "echo 'N/A'", - "clean": "echo 'N/A'", - "debug": "NODE_ENV=development nodemon index.js --trace-warnings", + "clean": "rimraf dist", + "debug": "NODE_ENV=development ts-node-dev --respawn --transpile-only --inspect -- index.ts", "lint": "yarn lint:eslint && yarn lint:misc --check", "lint:changelog": "yarn auto-changelog validate", "lint:eslint": "eslint . --cache --ext js,ts", "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write", "lint:misc": "prettier '**/*.json' '**/*.md' '!CHANGELOG.md' --ignore-path ../../.gitignore", "reset": "yarn clean && rimraf ./node_modules/", - "start": "node index.js", - "test": "echo 'N/A'", + "start": "yarn build && node dist/index.js", + "start:docker": "docker run -p 4000:4000 -d socket-test", + "test": "jest", "test:ci": "jest --coverage --passWithNoTests" }, "dependencies": { @@ -45,6 +46,19 @@ "devDependencies": { "@lavamoat/allow-scripts": "^2.3.1", "@metamask/auto-changelog": "^2.3.0", + "@types/analytics-node": "^3.1.13", + "@types/body-parser": "^1.19.4", + "@types/cors": "^2.8.15", + "@types/dotenv": "^8.2.0", + "@types/express": "^4.17.20", + "@types/helmet": "^4.0.0", + "@types/jest": "^29.5.6", + "@types/node": "^20.8.7", + "@types/socket.io": "^3.0.2", + "@types/supertest": "^2.0.15", + "@types/uuid": "^8.3.1", + "@typescript-eslint/eslint-plugin": "^6.8.0", + "@typescript-eslint/parser": "^6.8.0", "eslint": "^7.30.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.23.4", @@ -54,7 +68,13 @@ "eslint-plugin-prettier": "^3.4.0", "jest": "^29.6.4", "nodemon": "^2.0.20", - "prettier": "^2.8.8" + "prettier": "^2.8.8", + "socket.io-client": "^4.7.2", + "supertest": "^6.3.3", + "ts-jest": "^29.1.1", + "ts-node": "^10.9.1", + "ts-node-dev": "^2.0.0", + "typescript": "^5.2.2" }, "lavamoat": { "allowScripts": { diff --git a/packages/sdk-socket-server/rate-limiter.test.ts b/packages/sdk-socket-server/rate-limiter.test.ts new file mode 100644 index 000000000..810274731 --- /dev/null +++ b/packages/sdk-socket-server/rate-limiter.test.ts @@ -0,0 +1,43 @@ +import { + resetRateLimits, + increaseRateLimits, + setLastConnectionErrorTimestamp, +} from './rate-limiter'; + +import os from 'os'; + +jest.mock('os'); + +describe('rate-limiter', () => { + let consoleLogSpy: jest.SpyInstance; + + beforeEach(() => { + jest.resetModules(); // Clear any cached modules, which includes the rateLimiter instances. + consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(); + }); + + afterEach(() => { + consoleLogSpy.mockRestore(); + }); + + it('resetRateLimits should reset rate limits', () => { + setLastConnectionErrorTimestamp(Date.now() - 10001); + resetRateLimits(); + + expect(consoleLogSpy).toHaveBeenCalledWith( + expect.stringContaining('INFO> RL points:'), + ); + }); + + it('increaseRateLimits should adjust rate limits based on system load', () => { + (os.loadavg as jest.Mock).mockReturnValue([0.5, 0.5, 0.5]); + (os.cpus as jest.Mock).mockReturnValue(new Array(4)); + (os.totalmem as jest.Mock).mockReturnValue(10000000); + (os.freemem as jest.Mock).mockReturnValue(5000000); + increaseRateLimits(50); + + expect(consoleLogSpy).toHaveBeenCalledWith( + expect.stringContaining('INFO> RL points:'), + ); + }); +}); diff --git a/packages/sdk-socket-server/rate-limiter.js b/packages/sdk-socket-server/rate-limiter.ts similarity index 82% rename from packages/sdk-socket-server/rate-limiter.js rename to packages/sdk-socket-server/rate-limiter.ts index a316399da..672da60d5 100644 --- a/packages/sdk-socket-server/rate-limiter.js +++ b/packages/sdk-socket-server/rate-limiter.ts @@ -1,11 +1,11 @@ -const os = require('os'); -const { RateLimiterMemory } = require('rate-limiter-flexible'); +import os from 'os'; +import { RateLimiterMemory } from 'rate-limiter-flexible'; let rateLimitPoints = 10; let rateLimitMessagePoints = 100; const rateLimitPointsMax = 40; const rateLimitMessagePointsMax = 400; -let lastConnectionErrorTimestamp; +let lastConnectionErrorTimestamp: number | null = null; // Store the initial values const initialRateLimitPoints = rateLimitPoints; @@ -17,16 +17,16 @@ let rateLimiter = new RateLimiterMemory({ duration: 1, }); -let rateLimiterMesssage = new RateLimiterMemory({ +let rateLimiterMessage = new RateLimiterMemory({ points: rateLimitMessagePoints, duration: 1, }); -const setLastConnectionErrorTimestamp = (timestamp) => { +const setLastConnectionErrorTimestamp = (timestamp: number): void => { lastConnectionErrorTimestamp = timestamp; }; -const resetRateLimits = () => { +const resetRateLimits = (): void => { const tenSecondsPassedSinceLastError = lastConnectionErrorTimestamp && Date.now() - lastConnectionErrorTimestamp >= 10000; @@ -42,10 +42,7 @@ const resetRateLimits = () => { ); }; -const increaseRateLimits = ( - cpuUsagePercentMin, - // freeMemoryPercentMin -) => { +const increaseRateLimits = (cpuUsagePercentMin: number): void => { // Check the CPU usage const cpuLoad = os.loadavg()[0]; // 1 minute load average const numCpus = os.cpus().length; @@ -62,10 +59,7 @@ const increaseRateLimits = ( ); // If CPU is not at 100% and there is at least 10% of free memory - if ( - cpuUsagePercent <= cpuUsagePercentMin - // && freeMemoryPercent >= freeMemoryPercentMin - ) { + if (cpuUsagePercent <= cpuUsagePercentMin) { // Increase the rate limits by steps of 5 and 10, up to a max of 50 and 500 rateLimitPoints = Math.min(rateLimitPoints + 5, rateLimitPointsMax); rateLimitMessagePoints = Math.min( @@ -87,7 +81,7 @@ const increaseRateLimits = ( duration: 1, }); - rateLimiterMesssage = new RateLimiterMemory({ + rateLimiterMessage = new RateLimiterMemory({ points: rateLimitMessagePoints, duration: 1, }); @@ -97,9 +91,9 @@ const increaseRateLimits = ( ); }; -module.exports = { +export { rateLimiter, - rateLimiterMesssage, + rateLimiterMessage, resetRateLimits, increaseRateLimits, setLastConnectionErrorTimestamp, diff --git a/packages/sdk-socket-server/socket-config.js b/packages/sdk-socket-server/socket-config.js deleted file mode 100644 index bf1238a8c..000000000 --- a/packages/sdk-socket-server/socket-config.js +++ /dev/null @@ -1,220 +0,0 @@ -/* eslint-disable node/no-process-env */ -const { Server } = require('socket.io'); -const uuid = require('uuid'); -const { - rateLimiter, - rateLimiterMesssage, - resetRateLimits, - increaseRateLimits, - setLastConnectionErrorTimestamp, -} = require('./rate-limiter'); - -const isDevelopment = process.env.NODE_ENV === 'development'; - -module.exports = (server) => { - const io = new Server(server, { - cors: { - origin: '*', - }, - }); - - io.on('connection', (socket) => { - console.log('INFO> connection'); - - const socketId = socket.id; - const clientIp = socket.request.socket.remoteAddress; - - if (isDevelopment) { - console.log(`DEBUG> socketId=${socketId} clientIp=${clientIp}`); - } - - socket.on('create_channel', async (id) => { - console.log('INFO> create_channel'); - - try { - await rateLimiter.consume(socket.handshake.address); - - if (isDevelopment) { - console.log('DEBUG> create channel', id); - } - - if (!uuid.validate(id)) { - return socket.emit(`message-${id}`, { - error: 'must specify a valid id', - }); - } - - const room = io.sockets.adapter.rooms.get(id); - if (!id) { - return socket.emit(`message-${id}`, { error: 'must specify an id' }); - } - - if (room) { - return socket.emit(`message-${id}`, { - error: 'room already created', - }); - } - - resetRateLimits(); - - socket.join(id); - return socket.emit(`channel_created-${id}`, id); - } catch (error) { - setLastConnectionErrorTimestamp(Date.now()); - increaseRateLimits(90, 0); - console.error('ERROR> Error on create_channel:', error); - // emit an error message back to the client, if appropriate - return socket.emit(`error`, { error: error.message }); - } - }); - - socket.on('message', async ({ id, message, context, plaintext }) => { - const isMobile = context === 'mm-mobile'; - console.log(`INFO> message' ${isMobile ? ' mobile' : ''}`); - - try { - await rateLimiterMesssage.consume(socket.handshake.address); - - if (isDevelopment) { - // Minify encrypted message for easier readibility - let displayMessage = message; - if (plaintext) { - displayMessage = 'AAAAAA_ENCRYPTED_AAAAAA'; - } - - if (isMobile) { - console.log(`DEBUG> \x1b[33m message-${id} -> \x1b[0m`, { - id, - context, - displayMessage, - plaintext, - }); - } else { - console.log(`DEBUG> message-${id} -> `, { - id, - context, - displayMessage, - plaintext, - }); - } - } - - resetRateLimits(); - - return socket.to(id).emit(`message-${id}`, { id, message }); - } catch (error) { - setLastConnectionErrorTimestamp(Date.now()); - increaseRateLimits(90, 0); - console.error(`ERROR> Error on message: ${error}`); - // emit an error message back to the client, if appropriate - return socket.emit(`message-${id}`, { error: error.message }); - } - }); - - socket.on('ping', async ({ id, message, context }) => { - console.log('INFO> ping'); - - try { - await rateLimiterMesssage.consume(socket.handshake.address); - - if (isDevelopment) { - console.log(`DEBUG> ping-${id} -> `, { id, context, message }); - } - socket.to(id).emit(`ping-${id}`, { id, message }); - } catch (error) { - console.error('ERROR> Error on ping:', error); - // emit an error message back to the client, if appropriate - socket.emit(`ping-${id}`, { error: error.message }); - } - }); - - socket.on('join_channel', async (id, test) => { - console.log('INFO> join_channel'); - - try { - await rateLimiter.consume(socket.handshake.address); - } catch (e) { - return; - } - - if (isDevelopment) { - console.log(`DEBUG> join_channel ${id} ${test}`); - } - - if (!uuid.validate(id)) { - socket.emit(`message-${id}`, { error: 'must specify a valid id' }); - return; - } - - const room = io.sockets.adapter.rooms.get(id); - if (isDevelopment) { - console.log(`DEBUG> join_channel ${id} room.size=${room && room.size}`); - } - - if (room && room.size > 2) { - if (isDevelopment) { - console.log(`DEBUG> join_channel ${id} room already full`); - } - socket.emit(`message-${id}`, { error: 'room already full' }); - io.sockets.in(id).socketsLeave(id); - return; - } - - socket.join(id); - - if (!room || room.size < 2) { - socket.emit(`clients_waiting_to_join-${id}`, room ? room.size : 1); - } - - socket.on('disconnect', function (error) { - console.log('INFO> disconnect'); - - if (isDevelopment) { - console.log(`DEBUG> disconnect event channel=${id}: `, error); - } - - io.sockets.in(id).emit(`clients_disconnected-${id}`, error); - // io.sockets.in(id).socketsLeave(id); - }); - - if (room && room.size === 2) { - io.sockets.in(id).emit(`clients_connected-${id}`, id); - } - }); - - socket.on('leave_channel', (id) => { - console.log('INFO> leave_channel'); - - if (isDevelopment) { - console.log(`DEBUG> leave_channel id=${id}`); - } - - socket.leave(id); - io.sockets.in(id).emit(`clients_disconnected-${id}`); - }); - - socket.on('check_room', (id, callback) => { - console.log('INFO> check_room'); - - if (isDevelopment) { - console.log(`DEBUG> check_room id=${id}`); - } - - if (!uuid.validate(id)) { - return callback(new Error('must specify a valid id'), null); - } - - const room = io.sockets.adapter.rooms.get(id); - const occupancy = room ? room.size : 0; - - if (isDevelopment) { - console.log(`DEBUG> check_room id=${id} occupancy=${occupancy}`); - } - - // Callback with null as the first argument, meaning "no error" - return callback(null, { occupancy }); - }); - }); - - return io; -}; diff --git a/packages/sdk-socket-server/socket-config.test.ts b/packages/sdk-socket-server/socket-config.test.ts new file mode 100644 index 000000000..47138fa96 --- /dev/null +++ b/packages/sdk-socket-server/socket-config.test.ts @@ -0,0 +1,64 @@ +import { Server as HTTPServer } from 'http'; +import { Server as IoServer } from 'socket.io'; +import { io as clientIo, Socket } from 'socket.io-client'; +import createIoServer from './socket-config'; + +describe('Socket Config', () => { + let httpServer: HTTPServer; + let ioServer: IoServer; + let clientSocket: Socket; + let logSpy: jest.SpyInstance; + let errorSpy: jest.SpyInstance; + + beforeEach(async () => { + logSpy = jest.spyOn(console, 'log').mockImplementation(); + errorSpy = jest.spyOn(console, 'error').mockImplementation(); + httpServer = new HTTPServer(); + + await new Promise((resolve) => { + httpServer.listen(() => { + const { port } = httpServer.address() as { port: number }; + ioServer = createIoServer(httpServer); + clientSocket = clientIo(`http://localhost:${port}`); + resolve(); + }); + }); + }); + + afterEach(() => { + ioServer.close(); + clientSocket.close(); + httpServer.close(); + logSpy.mockRestore(); + errorSpy.mockRestore(); + }); + + it('should log "INFO> connection" on connection', (done) => { + clientSocket.on('connect', () => { + expect(logSpy).toHaveBeenCalledWith('INFO> connection'); + done(); + }); + clientSocket.connect(); + }); + + it('should log "INFO> create_channel" on create_channel', (done) => { + clientSocket.emit('create_channel', 'some-id'); + setTimeout(() => { + expect(logSpy).toHaveBeenCalledWith('INFO> create_channel'); + done(); + }, 100); + }); + + it('should log "INFO> ping" on ping', (done) => { + clientSocket.emit('ping', { + id: 'some-id', + message: 'some-message', + context: '', + }); + + setTimeout(() => { + expect(logSpy).toHaveBeenCalledWith('INFO> ping'); + done(); + }, 100); + }); +}); diff --git a/packages/sdk-socket-server/socket-config.ts b/packages/sdk-socket-server/socket-config.ts new file mode 100644 index 000000000..c4e31a0bb --- /dev/null +++ b/packages/sdk-socket-server/socket-config.ts @@ -0,0 +1,253 @@ +/* eslint-disable node/no-process-env */ +import { Server as HTTPServer } from 'http'; +import { Server, Socket } from 'socket.io'; +import { validate } from 'uuid'; +import { + rateLimiter, + rateLimiterMessage, + resetRateLimits, + increaseRateLimits, + setLastConnectionErrorTimestamp, +} from './rate-limiter'; + +const isDevelopment: boolean = process.env.NODE_ENV === 'development'; + +export default (server: HTTPServer): Server => { + const io = new Server(server, { + cors: { + origin: '*', + }, + }); + + io.on('connection', (socket: Socket) => { + console.log('INFO> connection'); + + const socketId = socket.id; + const clientIp = socket.request.socket.remoteAddress; + + if (isDevelopment) { + console.log(`DEBUG> socketId=${socketId} clientIp=${clientIp}`); + } + + socket.on('create_channel', async (id: string) => { + console.log('INFO> create_channel'); + + try { + await rateLimiter.consume(socket.handshake.address); + + if (isDevelopment) { + console.log('DEBUG> create channel', id); + } + + if (!validate(id)) { + return socket.emit(`message-${id}`, { + error: 'must specify a valid id', + }); + } + + const room = io.sockets.adapter.rooms.get(id); + if (!id) { + return socket.emit(`message-${id}`, { error: 'must specify an id' }); + } + + if (room) { + return socket.emit(`message-${id}`, { + error: 'room already created', + }); + } + + resetRateLimits(); + + socket.join(id); + return socket.emit(`channel_created-${id}`, id); + } catch (error) { + setLastConnectionErrorTimestamp(Date.now()); + increaseRateLimits(90); + console.error('ERROR> Error on create_channel:', error); + // emit an error message back to the client, if appropriate + return socket.emit(`error`, { error: (error as Error).message }); + } + }); + + socket.on( + 'message', + async ({ + id, + message, + context, + plaintext, + }: { + id: string; + message: string; + context: string; + plaintext: string; + }) => { + const isMobile = context === 'mm-mobile'; + console.log(`INFO> message${isMobile ? ' mobile' : ''}`); + + try { + await rateLimiterMessage.consume(socket.handshake.address); + + if (isDevelopment) { + // Minify encrypted message for easier readibility + let displayMessage = message; + if (plaintext) { + displayMessage = 'AAAAAA_ENCRYPTED_AAAAAA'; + } + + if (isMobile) { + console.log(`DEBUG> \x1b[33m message-${id} -> \x1b[0m`, { + id, + context, + displayMessage, + plaintext, + }); + } else { + console.log(`DEBUG> message-${id} -> `, { + id, + context, + displayMessage, + plaintext, + }); + } + } + + resetRateLimits(); + + return socket.to(id).emit(`message-${id}`, { id, message }); + } catch (error) { + setLastConnectionErrorTimestamp(Date.now()); + increaseRateLimits(90); + console.error(`ERROR> Error on message: ${error}`); + // emit an error message back to the client, if appropriate + return socket.emit(`message-${id}`, { + error: (error as Error).message, + }); + } + }, + ); + + socket.on( + 'ping', + async ({ + id, + message, + context, + }: { + id: string; + message: string; + context: string; + }) => { + console.log('INFO> ping'); + + try { + await rateLimiterMessage.consume(socket.handshake.address); + + if (isDevelopment) { + console.log(`DEBUG> ping-${id} -> `, { id, context, message }); + } + socket.to(id).emit(`ping-${id}`, { id, message }); + } catch (error) { + console.error('ERROR> Error on ping:', error); + // emit an error message back to the client, if appropriate + socket.emit(`ping-${id}`, { error: (error as Error).message }); + } + }, + ); + + socket.on('join_channel', async (id: string, test: string) => { + console.log('INFO> join_channel'); + + try { + await rateLimiter.consume(socket.handshake.address); + } catch (e) { + return; + } + + if (isDevelopment) { + console.log(`DEBUG> join_channel ${id} ${test}`); + } + + if (!validate(id)) { + socket.emit(`message-${id}`, { error: 'must specify a valid id' }); + return; + } + + const room = io.sockets.adapter.rooms.get(id); + if (isDevelopment) { + console.log(`DEBUG> join_channel ${id} room.size=${room && room.size}`); + } + + if (room && room.size > 2) { + if (isDevelopment) { + console.log(`DEBUG> join_channel ${id} room already full`); + } + socket.emit(`message-${id}`, { error: 'room already full' }); + io.sockets.in(id).socketsLeave(id); + return; + } + + socket.join(id); + + if (!room || room.size < 2) { + socket.emit(`clients_waiting_to_join-${id}`, room ? room.size : 1); + } + + socket.on('disconnect', function (error) { + console.log('INFO> disconnect'); + + if (isDevelopment) { + console.log(`DEBUG> disconnect event channel=${id}: `, error); + } + + io.sockets.in(id).emit(`clients_disconnected-${id}`, error); + // io.sockets.in(id).socketsLeave(id); + }); + + if (room && room.size === 2) { + io.sockets.in(id).emit(`clients_connected-${id}`, id); + } + }); + + socket.on('leave_channel', (id: string) => { + console.log('INFO> leave_channel'); + + if (isDevelopment) { + console.log(`DEBUG> leave_channel id=${id}`); + } + + socket.leave(id); + io.sockets.in(id).emit(`clients_disconnected-${id}`); + }); + + socket.on( + 'check_room', + ( + id: string, + callback: (error: Error | null, result?: { occupancy: number }) => void, + ) => { + console.log('INFO> check_room'); + + if (isDevelopment) { + console.log(`DEBUG> check_room id=${id}`); + } + + if (!validate(id)) { + return callback(new Error('must specify a valid id'), undefined); + } + + const room = io.sockets.adapter.rooms.get(id); + const occupancy = room ? room.size : 0; + + if (isDevelopment) { + console.log(`DEBUG> check_room id=${id} occupancy=${occupancy}`); + } + + // Callback with null as the first argument, meaning "no error" + return callback(null, { occupancy }); + }, + ); + }); + + return io; +}; diff --git a/packages/sdk-socket-server/tsconfig.json b/packages/sdk-socket-server/tsconfig.json new file mode 100644 index 000000000..4c8af248f --- /dev/null +++ b/packages/sdk-socket-server/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES6", + "module": "commonjs", + "outDir": "dist", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["*.ts"], + "exclude": [ + "node_modules", + "*.spec.ts", + "*.test.ts", + "*.spec.tsx", + "*.test.tsx" + ] +} diff --git a/packages/sdk-socket-server/tsconfig.test.json b/packages/sdk-socket-server/tsconfig.test.json new file mode 100644 index 000000000..68cc940be --- /dev/null +++ b/packages/sdk-socket-server/tsconfig.test.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES6", + "module": "commonjs", + "outDir": "dist", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "types": ["node", "jest", "@testing-library/jest-dom"] + }, + "include": ["*.ts"], + "exclude": ["node_modules"] +} diff --git a/packages/sdk-socket-server/utils.js b/packages/sdk-socket-server/utils.js deleted file mode 100644 index e94cd44c3..000000000 --- a/packages/sdk-socket-server/utils.js +++ /dev/null @@ -1,47 +0,0 @@ -// flushes all Segment events when Node process is interrupted for any reason -// https://segment.com/docs/connections/sources/catalog/libraries/server/node/#long-running-process -const exitGracefully = async (code, analytics) => { - console.log('INFO> Flushing events'); - try { - await analytics.flush(function (err) { - console.log('INFO> Flushed, and now this program can exit!'); - if (err) { - console.error(`ERROR> ${err}`); - } - }); - } catch (error) { - console.error('ERROR> Error on exitGracefully:', error); - } - // eslint-disable-next-line node/no-process-exit - process.exit(code); -}; - -// Define a variable to track if the server is shutting down -let isShuttingDown = false; - -// Function to perform cleanup operations before shutting down -const cleanupAndExit = async (server, analytics) => { - if (isShuttingDown) { - return; - } - - isShuttingDown = true; - - try { - // Close the server - await server.close(); - console.log('INFO> Server closed.'); - - // Perform any other necessary cleanup operations - exitGracefully(0, analytics); - } catch (err) { - console.error('ERROR> Error during server shutdown:', err); - // eslint-disable-next-line node/no-process-exit - process.exit(1); - } -}; - -module.exports = { - exitGracefully, - cleanupAndExit, -}; diff --git a/packages/sdk-socket-server/utils.ts b/packages/sdk-socket-server/utils.ts new file mode 100644 index 000000000..42dbffcb4 --- /dev/null +++ b/packages/sdk-socket-server/utils.ts @@ -0,0 +1,69 @@ +import { Server as HttpServer } from 'http'; + +export type FlushResponse = { + batch: any; + timestamp: string; + sentAt: string; +}; + +export type Analytics = { + flush(callback: (err: Error | null) => void): Promise; +}; + +export const flushAnalytics = async ( + analytics: Analytics, +): Promise => { + try { + const flushResponse = await analytics.flush((err) => { + if (err) { + throw err; + } + }); + return flushResponse; + } catch (error) { + return error as Error | FlushResponse; + } +}; + +type Server = HttpServer; + +export const closeServer = (server: Server): Promise => { + return new Promise((resolve, reject) => { + server.close((err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); +}; + +let isShuttingDown = false; + +export const setIsShuttingDown = (value: boolean) => { + isShuttingDown = value; +}; + +export const getIsShuttingDown = () => isShuttingDown; + +export const cleanupAndExit = async ( + server: Server, + analytics: Analytics, +): Promise => { + if (isShuttingDown) { + return; + } + isShuttingDown = true; + + const serverCloseResult = await closeServer(server); + const flushAnalyticsResult = await flushAnalytics(analytics); + + if ((serverCloseResult as any) instanceof Error) { + throw new Error(`Error during server shutdown: ${serverCloseResult}`); + } + + if (flushAnalyticsResult instanceof Error) { + throw new Error(`Error on exitGracefully: ${flushAnalyticsResult}`); + } +}; diff --git a/yarn.lock b/yarn.lock index 2711d8caa..7f126aa96 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2678,7 +2678,7 @@ __metadata: languageName: node linkType: hard -"@eslint-community/eslint-utils@npm:^4.2.0": +"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": version: 4.4.0 resolution: "@eslint-community/eslint-utils@npm:4.4.0" dependencies: @@ -2696,6 +2696,13 @@ __metadata: languageName: node linkType: hard +"@eslint-community/regexpp@npm:^4.5.1": + version: 4.9.1 + resolution: "@eslint-community/regexpp@npm:4.9.1" + checksum: 06fb839e9c756f6375cc545c2f2e05a0a64576bd6370e8e3c07983fd29a3d6e164ef4aa48a361f7d27e6713ab79c83053ff6a2ccb78748bc955e344279c4a3b6 + languageName: node + linkType: hard + "@eslint-community/regexpp@npm:^4.6.1": version: 4.8.0 resolution: "@eslint-community/regexpp@npm:4.8.0" @@ -5010,6 +5017,19 @@ __metadata: dependencies: "@lavamoat/allow-scripts": ^2.3.1 "@metamask/auto-changelog": ^2.3.0 + "@types/analytics-node": ^3.1.13 + "@types/body-parser": ^1.19.4 + "@types/cors": ^2.8.15 + "@types/dotenv": ^8.2.0 + "@types/express": ^4.17.20 + "@types/helmet": ^4.0.0 + "@types/jest": ^29.5.6 + "@types/node": ^20.8.7 + "@types/socket.io": ^3.0.2 + "@types/supertest": ^2.0.15 + "@types/uuid": ^8.3.1 + "@typescript-eslint/eslint-plugin": ^6.8.0 + "@typescript-eslint/parser": ^6.8.0 analytics-node: ^6.2.0 body-parser: ^1.20.2 cors: ^2.8.5 @@ -5030,6 +5050,12 @@ __metadata: rate-limiter-flexible: ^2.3.8 rimraf: ^4.4.0 socket.io: ^4.4.1 + socket.io-client: ^4.7.2 + supertest: ^6.3.3 + ts-jest: ^29.1.1 + ts-node: ^10.9.1 + ts-node-dev: ^2.0.0 + typescript: ^5.2.2 uuid: ^8.3.2 languageName: unknown linkType: soft @@ -7105,6 +7131,13 @@ __metadata: languageName: node linkType: hard +"@types/analytics-node@npm:^3.1.13": + version: 3.1.13 + resolution: "@types/analytics-node@npm:3.1.13" + checksum: c8c4fbfd8389dbe5c72a95fbf873d67a56818656f22a2179152f24f59e57a015bc2f1d3e932e23c8875620844ccb6b8efbc8cc6f6ae575a9e5709a535f67020d + languageName: node + linkType: hard + "@types/aria-query@npm:^5.0.1": version: 5.0.1 resolution: "@types/aria-query@npm:5.0.1" @@ -7181,6 +7214,16 @@ __metadata: languageName: node linkType: hard +"@types/body-parser@npm:^1.19.4": + version: 1.19.4 + resolution: "@types/body-parser@npm:1.19.4" + dependencies: + "@types/connect": "*" + "@types/node": "*" + checksum: 10accc30773319bd49af7d12d2cd5faf9a0293ea4764345297f26ba6ef31d5caa7609da7619584d6c61279e09b89d3ab13d28c5cb644807c5d9c722ae1454778 + languageName: node + linkType: hard + "@types/bonjour@npm:^3.5.9": version: 3.5.10 resolution: "@types/bonjour@npm:3.5.10" @@ -7245,6 +7288,13 @@ __metadata: languageName: node linkType: hard +"@types/cookiejar@npm:*": + version: 2.1.3 + resolution: "@types/cookiejar@npm:2.1.3" + checksum: eda98d77ee6475363b182be3ed4ea4169819e83371ab0e3dec11445d46009fbf8ebbdc698ca83a6cc7967bbca8b11d34b6deab16a06a2eea021e354e4bf5f479 + languageName: node + linkType: hard + "@types/cors@npm:2.8.12": version: 2.8.12 resolution: "@types/cors@npm:2.8.12" @@ -7261,6 +7311,15 @@ __metadata: languageName: node linkType: hard +"@types/cors@npm:^2.8.15": + version: 2.8.15 + resolution: "@types/cors@npm:2.8.15" + dependencies: + "@types/node": "*" + checksum: ef7b0aba4c6a4c1fe9d459bd471ebaa891a75319682c9248daa17720003d1d0d2c59de4bdb6868630596ade9b7c3c949e652d6141b14c6fe4387ffcc520d0f3f + languageName: node + linkType: hard + "@types/debug@npm:^4.1.7": version: 4.1.8 resolution: "@types/debug@npm:4.1.8" @@ -7277,6 +7336,15 @@ __metadata: languageName: node linkType: hard +"@types/dotenv@npm:^8.2.0": + version: 8.2.0 + resolution: "@types/dotenv@npm:8.2.0" + dependencies: + dotenv: "*" + checksum: a1f524da7dc18b09bdb6c2613b9abbbe8ca575621cce4625efc7f98daf2ba07c7dbab622c9bb394507a12c5c515f9cbbbba4aeec17b801c88faeb8e8e656d460 + languageName: node + linkType: hard + "@types/eslint-scope@npm:^3.7.3": version: 3.7.4 resolution: "@types/eslint-scope@npm:3.7.4" @@ -7379,6 +7447,18 @@ __metadata: languageName: node linkType: hard +"@types/express@npm:^4.17.20": + version: 4.17.20 + resolution: "@types/express@npm:4.17.20" + dependencies: + "@types/body-parser": "*" + "@types/express-serve-static-core": ^4.17.33 + "@types/qs": "*" + "@types/serve-static": "*" + checksum: bf8a97d283128e5129f9ccabbeef728ff3f0484465e0ae74a304bd0588fa6cb715ae68845650caba9a641944b7791ba125d02ddbd47a7e62aaefdd036570c6c5 + languageName: node + linkType: hard + "@types/filesystem@npm:*": version: 0.0.32 resolution: "@types/filesystem@npm:0.0.32" @@ -7411,6 +7491,15 @@ __metadata: languageName: node linkType: hard +"@types/helmet@npm:^4.0.0": + version: 4.0.0 + resolution: "@types/helmet@npm:4.0.0" + dependencies: + helmet: "*" + checksum: ded60d8d9960edbae4d10603511dca9b9c50a9937f49e0d2b14bc096302e85a038acd443889a132dab60bc950bfd0e47337bdca485d75bbb7b6224f74fc50ab7 + languageName: node + linkType: hard + "@types/hoist-non-react-statics@npm:*": version: 3.3.1 resolution: "@types/hoist-non-react-statics@npm:3.3.1" @@ -7496,6 +7585,16 @@ __metadata: languageName: node linkType: hard +"@types/jest@npm:^29.5.6": + version: 29.5.6 + resolution: "@types/jest@npm:29.5.6" + dependencies: + expect: ^29.0.0 + pretty-format: ^29.0.0 + checksum: fa13a27bd1c8efd0381a419478769d0d6d3a8e93e1952d7ac3a16274e8440af6f73ed6f96ac1ff00761198badf2ee226b5ab5583a5d87a78d609ea78da5c5a24 + languageName: node + linkType: hard + "@types/jsdom@npm:^20.0.0": version: 20.0.1 resolution: "@types/jsdom@npm:20.0.1" @@ -7514,6 +7613,13 @@ __metadata: languageName: node linkType: hard +"@types/json-schema@npm:^7.0.12": + version: 7.0.14 + resolution: "@types/json-schema@npm:7.0.14" + checksum: 4b3dd99616c7c808201c56f6c7f6552eb67b5c0c753ab3fa03a6cb549aae950da537e9558e53fa65fba23d1be624a1e4e8d20c15027efbe41e03ca56f2b04fb0 + languageName: node + linkType: hard + "@types/json5@npm:^0.0.29": version: 0.0.29 resolution: "@types/json5@npm:0.0.29" @@ -7635,6 +7741,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^20.8.7": + version: 20.8.7 + resolution: "@types/node@npm:20.8.7" + dependencies: + undici-types: ~5.25.1 + checksum: 2173c0c03daefcb60c03a61b1371b28c8fe412e7a40dc6646458b809d14a85fbc7aeb369d957d57f0aaaafd99964e77436f29b3b579232d8f2b20c58abbd1d25 + languageName: node + linkType: hard + "@types/normalize-package-data@npm:^2.4.0": version: 2.4.1 resolution: "@types/normalize-package-data@npm:2.4.1" @@ -7854,6 +7969,13 @@ __metadata: languageName: node linkType: hard +"@types/semver@npm:^7.5.0": + version: 7.5.4 + resolution: "@types/semver@npm:7.5.4" + checksum: 120c0189f6fec5f2d12d0d71ac8a4cfa952dc17fa3d842e8afddb82bba8828a4052f8799c1653e2b47ae1977435f38e8985658fde971905ce5afb8e23ee97ecf + languageName: node + linkType: hard + "@types/send@npm:*": version: 0.17.1 resolution: "@types/send@npm:0.17.1" @@ -7884,6 +8006,15 @@ __metadata: languageName: node linkType: hard +"@types/socket.io@npm:^3.0.2": + version: 3.0.2 + resolution: "@types/socket.io@npm:3.0.2" + dependencies: + socket.io: "*" + checksum: 168cf77e48b466582bd69d1c25930bb657225e742288d658a03a1e8af254350daa61f9441ec2be0d8354f1dcc747966e4342000bce875549fe54ec03e2fafec3 + languageName: node + linkType: hard + "@types/sockjs@npm:^0.3.33": version: 0.3.33 resolution: "@types/sockjs@npm:0.3.33" @@ -7900,6 +8031,20 @@ __metadata: languageName: node linkType: hard +"@types/strip-bom@npm:^3.0.0": + version: 3.0.0 + resolution: "@types/strip-bom@npm:3.0.0" + checksum: cb165d0c2ce6abbef95506ebee25be02bd453600ef1792dc1754236e5d6f9c830d52bdb85978d0b08ea1f36b96a61235ac5ad99e0f4c2767fb4ea004e141d2df + languageName: node + linkType: hard + +"@types/strip-json-comments@npm:0.0.30": + version: 0.0.30 + resolution: "@types/strip-json-comments@npm:0.0.30" + checksum: 829ddd389645073f347c5b1924a8c34b8813af29756576e511c46f40e218193cf93ccbade62661d47fc70f707e98f410331729b8c20edfcb2e807d51df1ad4b7 + languageName: node + linkType: hard + "@types/styled-components@npm:*": version: 5.1.26 resolution: "@types/styled-components@npm:5.1.26" @@ -7929,6 +8074,25 @@ __metadata: languageName: node linkType: hard +"@types/superagent@npm:*": + version: 4.1.20 + resolution: "@types/superagent@npm:4.1.20" + dependencies: + "@types/cookiejar": "*" + "@types/node": "*" + checksum: 63d14a76a5ebdbdf39ce09eb4e7172d2975f6146808003a4cecd1f0c1464985cbcca8259803fd9cae230ad5129b9c9873b5be58075ea6889a927501fbeddc21d + languageName: node + linkType: hard + +"@types/supertest@npm:^2.0.15": + version: 2.0.15 + resolution: "@types/supertest@npm:2.0.15" + dependencies: + "@types/superagent": "*" + checksum: 89c1983662f0ab20969b3a6c44344397fd222d0f78b282619aabbe817f7c88a64210fd2b8b8f075ea22a27084e30ebc287bc5105619cbbf9af7f008e77f6eb93 + languageName: node + linkType: hard + "@types/testing-library__jest-dom@npm:^5.9.1": version: 5.14.8 resolution: "@types/testing-library__jest-dom@npm:5.14.8" @@ -7959,6 +8123,13 @@ __metadata: languageName: node linkType: hard +"@types/uuid@npm:^8.3.1": + version: 8.3.4 + resolution: "@types/uuid@npm:8.3.4" + checksum: 6f11f3ff70f30210edaa8071422d405e9c1d4e53abbe50fdce365150d3c698fe7bbff65c1e71ae080cbfb8fded860dbb5e174da96fdbbdfcaa3fb3daa474d20f + languageName: node + linkType: hard + "@types/uuid@npm:^9.0.0": version: 9.0.2 resolution: "@types/uuid@npm:9.0.2" @@ -8092,6 +8263,31 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/eslint-plugin@npm:^6.8.0": + version: 6.8.0 + resolution: "@typescript-eslint/eslint-plugin@npm:6.8.0" + dependencies: + "@eslint-community/regexpp": ^4.5.1 + "@typescript-eslint/scope-manager": 6.8.0 + "@typescript-eslint/type-utils": 6.8.0 + "@typescript-eslint/utils": 6.8.0 + "@typescript-eslint/visitor-keys": 6.8.0 + debug: ^4.3.4 + graphemer: ^1.4.0 + ignore: ^5.2.4 + natural-compare: ^1.4.0 + semver: ^7.5.4 + ts-api-utils: ^1.0.1 + peerDependencies: + "@typescript-eslint/parser": ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: c36ccf606ebcaff8263c4ffa3b4cda58c6f93474b9eea9906e51be2fef8596977a245cc13770b21c6bfd38ccf45a3cf3613d5f4499429f62ec80afe15ae345bd + languageName: node + linkType: hard + "@typescript-eslint/experimental-utils@npm:4.33.0, @typescript-eslint/experimental-utils@npm:^4.0.1": version: 4.33.0 resolution: "@typescript-eslint/experimental-utils@npm:4.33.0" @@ -8171,6 +8367,24 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/parser@npm:^6.8.0": + version: 6.8.0 + resolution: "@typescript-eslint/parser@npm:6.8.0" + dependencies: + "@typescript-eslint/scope-manager": 6.8.0 + "@typescript-eslint/types": 6.8.0 + "@typescript-eslint/typescript-estree": 6.8.0 + "@typescript-eslint/visitor-keys": 6.8.0 + debug: ^4.3.4 + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10d7a3ae383fee5a5cba9541c72e23d6ab01cca6b414a62b44dacb5ebc15c80b80aa6c105b6469d3795f2f8514ae2499c069cd2d9dcac61f3db9ef6c7a75e080 + languageName: node + linkType: hard + "@typescript-eslint/scope-manager@npm:4.33.0": version: 4.33.0 resolution: "@typescript-eslint/scope-manager@npm:4.33.0" @@ -8201,6 +8415,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/scope-manager@npm:6.8.0": + version: 6.8.0 + resolution: "@typescript-eslint/scope-manager@npm:6.8.0" + dependencies: + "@typescript-eslint/types": 6.8.0 + "@typescript-eslint/visitor-keys": 6.8.0 + checksum: b6cf2803531d1c14b56c30fd3cd807b80e17fe48d0da8e5aa9ae50915407ed732c7e2a7ac8030b7cf8ed07b8e481a1138d76bf05b727837a0e016280c2f6873b + languageName: node + linkType: hard + "@typescript-eslint/type-utils@npm:5.62.0": version: 5.62.0 resolution: "@typescript-eslint/type-utils@npm:5.62.0" @@ -8218,6 +8442,23 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/type-utils@npm:6.8.0": + version: 6.8.0 + resolution: "@typescript-eslint/type-utils@npm:6.8.0" + dependencies: + "@typescript-eslint/typescript-estree": 6.8.0 + "@typescript-eslint/utils": 6.8.0 + debug: ^4.3.4 + ts-api-utils: ^1.0.1 + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 9b7d56904dc1a5719ef79eb1b7989d6fad10c71fb07ec3e66cf69b8c8dc5383d644ab122d4701bc4960fb7c99cc08aee4e645db3e4675d488d5779197e15dfda + languageName: node + linkType: hard + "@typescript-eslint/types@npm:4.33.0": version: 4.33.0 resolution: "@typescript-eslint/types@npm:4.33.0" @@ -8239,6 +8480,13 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/types@npm:6.8.0": + version: 6.8.0 + resolution: "@typescript-eslint/types@npm:6.8.0" + checksum: 1fcd85f6d575116d51c6ee757ed37610ae5e7e4296a29f93c9c6949f6cd16d24550eb7fc5bae7a43119cc08e13836f69a7ae7c54ebba6c95aef96b34d3bfb7f7 + languageName: node + linkType: hard + "@typescript-eslint/typescript-estree@npm:4.33.0": version: 4.33.0 resolution: "@typescript-eslint/typescript-estree@npm:4.33.0" @@ -8293,6 +8541,24 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/typescript-estree@npm:6.8.0": + version: 6.8.0 + resolution: "@typescript-eslint/typescript-estree@npm:6.8.0" + dependencies: + "@typescript-eslint/types": 6.8.0 + "@typescript-eslint/visitor-keys": 6.8.0 + debug: ^4.3.4 + globby: ^11.1.0 + is-glob: ^4.0.3 + semver: ^7.5.4 + ts-api-utils: ^1.0.1 + peerDependenciesMeta: + typescript: + optional: true + checksum: 388db7f33ef1bc0e7b960c0bce9c744c2e32c66c7ab8dfae73d8533958202ad6f31663b0010f79c45b5ff93159c67f45b00693d73b9da2472b17156dfd26b4a8 + languageName: node + linkType: hard + "@typescript-eslint/utils@npm:5.62.0, @typescript-eslint/utils@npm:^5.58.0": version: 5.62.0 resolution: "@typescript-eslint/utils@npm:5.62.0" @@ -8311,6 +8577,23 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/utils@npm:6.8.0": + version: 6.8.0 + resolution: "@typescript-eslint/utils@npm:6.8.0" + dependencies: + "@eslint-community/eslint-utils": ^4.4.0 + "@types/json-schema": ^7.0.12 + "@types/semver": ^7.5.0 + "@typescript-eslint/scope-manager": 6.8.0 + "@typescript-eslint/types": 6.8.0 + "@typescript-eslint/typescript-estree": 6.8.0 + semver: ^7.5.4 + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + checksum: 6d9f90db504502a9aa10e834830c3ffa25483757414670acc6141a3ebef9171a57688a3a179febf35a0e1e0b322f37228d9537bf1b279f1af7fc97888b873bc3 + languageName: node + linkType: hard + "@typescript-eslint/visitor-keys@npm:4.33.0": version: 4.33.0 resolution: "@typescript-eslint/visitor-keys@npm:4.33.0" @@ -8341,6 +8624,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/visitor-keys@npm:6.8.0": + version: 6.8.0 + resolution: "@typescript-eslint/visitor-keys@npm:6.8.0" + dependencies: + "@typescript-eslint/types": 6.8.0 + eslint-visitor-keys: ^3.4.1 + checksum: 710d9067b85d7715a400ae625c083c41733abb891d7b35108de083913980f9642e79d27689599fa39915f0fecae16dbfc30367007fccc838ccd917943660de22 + languageName: node + linkType: hard + "@wagmi/chains@npm:1.6.0": version: 1.6.0 resolution: "@wagmi/chains@npm:1.6.0" @@ -9856,7 +10149,7 @@ __metadata: languageName: node linkType: hard -"asap@npm:~2.0.6": +"asap@npm:^2.0.0, asap@npm:~2.0.6": version: 2.0.6 resolution: "asap@npm:2.0.6" checksum: b296c92c4b969e973260e47523207cd5769abd27c245a68c26dc7a0fe8053c55bb04360237cb51cab1df52be939da77150ace99ad331fb7fb13b3423ed73ff3d @@ -11359,7 +11652,7 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:3.5.3, chokidar@npm:^3.4.2, chokidar@npm:^3.5.2, chokidar@npm:^3.5.3": +"chokidar@npm:3.5.3, chokidar@npm:^3.4.2, chokidar@npm:^3.5.1, chokidar@npm:^3.5.2, chokidar@npm:^3.5.3": version: 3.5.3 resolution: "chokidar@npm:3.5.3" dependencies: @@ -11808,6 +12101,13 @@ __metadata: languageName: node linkType: hard +"component-emitter@npm:^1.3.0": + version: 1.3.0 + resolution: "component-emitter@npm:1.3.0" + checksum: b3c46de38ffd35c57d1c02488355be9f218e582aec72d72d1b8bbec95a3ac1b38c96cd6e03ff015577e68f550fbb361a3bfdbd9bb248be9390b7b3745691be6b + languageName: node + linkType: hard + "component-type@npm:^1.2.1": version: 1.2.1 resolution: "component-type@npm:1.2.1" @@ -12015,6 +12315,13 @@ __metadata: languageName: node linkType: hard +"cookiejar@npm:^2.1.4": + version: 2.1.4 + resolution: "cookiejar@npm:2.1.4" + checksum: c4442111963077dc0e5672359956d6556a195d31cbb35b528356ce5f184922b99ac48245ac05ed86cf993f7df157c56da10ab3efdadfed79778a0d9b1b092d5b + languageName: node + linkType: hard + "copy-to-clipboard@npm:^3.3.1, copy-to-clipboard@npm:^3.3.3": version: 3.3.3 resolution: "copy-to-clipboard@npm:3.3.3" @@ -13118,6 +13425,16 @@ __metadata: languageName: unknown linkType: soft +"dezalgo@npm:^1.0.4": + version: 1.0.4 + resolution: "dezalgo@npm:1.0.4" + dependencies: + asap: ^2.0.0 + wrappy: 1 + checksum: 895389c6aead740d2ab5da4d3466d20fa30f738010a4d3f4dcccc9fc645ca31c9d10b7e1804ae489b1eb02c7986f9f1f34ba132d409b043082a86d9a4e745624 + languageName: node + linkType: hard + "didyoumean@npm:^1.2.2": version: 1.2.2 resolution: "didyoumean@npm:1.2.2" @@ -13392,6 +13709,13 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:*, dotenv@npm:^16.0.3": + version: 16.3.1 + resolution: "dotenv@npm:16.3.1" + checksum: 15d75e7279018f4bafd0ee9706593dd14455ddb71b3bcba9c52574460b7ccaf67d5cf8b2c08a5af1a9da6db36c956a04a1192b101ee102a3e0cf8817bbcf3dfd + languageName: node + linkType: hard + "dotenv@npm:^10.0.0": version: 10.0.0 resolution: "dotenv@npm:10.0.0" @@ -13399,13 +13723,6 @@ __metadata: languageName: node linkType: hard -"dotenv@npm:^16.0.3": - version: 16.3.1 - resolution: "dotenv@npm:16.3.1" - checksum: 15d75e7279018f4bafd0ee9706593dd14455ddb71b3bcba9c52574460b7ccaf67d5cf8b2c08a5af1a9da6db36c956a04a1192b101ee102a3e0cf8817bbcf3dfd - languageName: node - linkType: hard - "double-ended-queue@npm:2.1.0-0": version: 2.1.0-0 resolution: "double-ended-queue@npm:2.1.0-0" @@ -13432,6 +13749,15 @@ __metadata: languageName: node linkType: hard +"dynamic-dedupe@npm:^0.3.0": + version: 0.3.0 + resolution: "dynamic-dedupe@npm:0.3.0" + dependencies: + xtend: ^4.0.0 + checksum: 5178b99ad30a59234c63b38b453183cfd0a6cb7acbe7b94b7aea9bf0f75376fdaab6e2ea7922931cfc0152390ccb20bd024d8d80b4fc8c3c3255a2fcadf2cafb + languageName: node + linkType: hard + "eastasianwidth@npm:^0.2.0": version: 0.2.0 resolution: "eastasianwidth@npm:0.2.0" @@ -13629,6 +13955,19 @@ __metadata: languageName: node linkType: hard +"engine.io-client@npm:~6.5.2": + version: 6.5.2 + resolution: "engine.io-client@npm:6.5.2" + dependencies: + "@socket.io/component-emitter": ~3.1.0 + debug: ~4.3.1 + engine.io-parser: ~5.2.1 + ws: ~8.11.0 + xmlhttprequest-ssl: ~2.0.0 + checksum: f93e09b842535a3f3e31b708cd30085b9e08a7a7ebf28f453e50e79e3fccf3f019474a46b41f7dc9164e3b8342c0b5d5a50a45299c1e2465d708c65140d05c92 + languageName: node + linkType: hard + "engine.io-parser@npm:~5.1.0": version: 5.1.0 resolution: "engine.io-parser@npm:5.1.0" @@ -13636,6 +13975,13 @@ __metadata: languageName: node linkType: hard +"engine.io-parser@npm:~5.2.1": + version: 5.2.1 + resolution: "engine.io-parser@npm:5.2.1" + checksum: 55b0e8e18500f50c1573675c53597c5552554ead08d3f30ff19fde6409e48f882a8e01f84e9772cd155c18a1d653d06f6bf57b4e1f8b834c63c9eaf3b657b88e + languageName: node + linkType: hard + "engine.io@npm:~6.5.0": version: 6.5.1 resolution: "engine.io@npm:6.5.1" @@ -13654,6 +14000,24 @@ __metadata: languageName: node linkType: hard +"engine.io@npm:~6.5.2": + version: 6.5.3 + resolution: "engine.io@npm:6.5.3" + dependencies: + "@types/cookie": ^0.4.1 + "@types/cors": ^2.8.12 + "@types/node": ">=10.0.0" + accepts: ~1.3.4 + base64id: 2.0.0 + cookie: ~0.4.1 + cors: ~2.8.5 + debug: ~4.3.1 + engine.io-parser: ~5.2.1 + ws: ~8.11.0 + checksum: b71769b17a663d07b14a2ba6de7a729bce47cef936a25b46dcc1cc75e42b377d82548cfbaf66ce4a0b237536c488a29d301c2514ab1cafbdc1f594bd327fc498 + languageName: node + linkType: hard + "enhanced-resolve@npm:^5.12.0, enhanced-resolve@npm:^5.15.0": version: 5.15.0 resolution: "enhanced-resolve@npm:5.15.0" @@ -15458,7 +15822,7 @@ __metadata: languageName: node linkType: hard -"fast-safe-stringify@npm:^2.0.6": +"fast-safe-stringify@npm:^2.0.6, fast-safe-stringify@npm:^2.1.1": version: 2.1.1 resolution: "fast-safe-stringify@npm:2.1.1" checksum: a851cbddc451745662f8f00ddb622d6766f9bd97642dabfd9a405fb0d646d69fc0b9a1243cbf67f5f18a39f40f6fa821737651ff1bceeba06c9992ca2dc5bd3d @@ -15807,6 +16171,18 @@ __metadata: languageName: node linkType: hard +"formidable@npm:^2.1.2": + version: 2.1.2 + resolution: "formidable@npm:2.1.2" + dependencies: + dezalgo: ^1.0.4 + hexoid: ^1.0.0 + once: ^1.4.0 + qs: ^6.11.0 + checksum: 81c8e5d89f5eb873e992893468f0de22c01678ca3d315db62be0560f9de1c77d4faefc9b1f4575098eb2263b3c81ba1024833a9fc3206297ddbac88a4f69b7a8 + languageName: node + linkType: hard + "forwarded@npm:0.2.0": version: 0.2.0 resolution: "forwarded@npm:0.2.0" @@ -16640,6 +17016,13 @@ __metadata: languageName: node linkType: hard +"helmet@npm:*": + version: 7.0.0 + resolution: "helmet@npm:7.0.0" + checksum: 3622b8b68b7ac4736369fe7717adb5b87ddcdcda7c11c13277d79fb5670acab032e8c971835565b1d2144d234f3e0dbcd327a44249b9e61f0f64c0c753faf1bc + languageName: node + linkType: hard + "helmet@npm:^5.1.1": version: 5.1.1 resolution: "helmet@npm:5.1.1" @@ -16647,6 +17030,13 @@ __metadata: languageName: node linkType: hard +"hexoid@npm:^1.0.0": + version: 1.0.0 + resolution: "hexoid@npm:1.0.0" + checksum: 27a148ca76a2358287f40445870116baaff4a0ed0acc99900bf167f0f708ffd82e044ff55e9949c71963852b580fc024146d3ac6d5d76b508b78d927fa48ae2d + languageName: node + linkType: hard + "hey-listen@npm:^1.0.8": version: 1.0.8 resolution: "hey-listen@npm:1.0.8" @@ -17343,6 +17733,15 @@ __metadata: languageName: node linkType: hard +"is-core-module@npm:^2.13.0": + version: 2.13.0 + resolution: "is-core-module@npm:2.13.0" + dependencies: + has: ^1.0.3 + checksum: 053ab101fb390bfeb2333360fd131387bed54e476b26860dc7f5a700bbf34a0ec4454f7c8c4d43e8a0030957e4b3db6e16d35e1890ea6fb654c833095e040355 + languageName: node + linkType: hard + "is-date-object@npm:^1.0.1, is-date-object@npm:^1.0.5": version: 1.0.5 resolution: "is-date-object@npm:1.0.5" @@ -21437,7 +21836,7 @@ __metadata: languageName: unknown linkType: soft -"methods@npm:~1.1.2": +"methods@npm:^1.1.2, methods@npm:~1.1.2": version: 1.1.2 resolution: "methods@npm:1.1.2" checksum: 0917ff4041fa8e2f2fda5425a955fe16ca411591fbd123c0d722fcf02b73971ed6f764d85f0a6f547ce49ee0221ce2c19a5fa692157931cecb422984f1dcd13a @@ -21498,6 +21897,15 @@ __metadata: languageName: node linkType: hard +"mime@npm:2.6.0": + version: 2.6.0 + resolution: "mime@npm:2.6.0" + bin: + mime: cli.js + checksum: 1497ba7b9f6960694268a557eae24b743fd2923da46ec392b042469f4b901721ba0adcf8b0d3c2677839d0e243b209d76e5edcbd09cfdeffa2dfb6bb4df4b862 + languageName: node + linkType: hard + "mime@npm:>=2.4.6": version: 3.0.0 resolution: "mime@npm:3.0.0" @@ -24933,7 +25341,7 @@ __metadata: languageName: node linkType: hard -"qs@npm:^6.10.3": +"qs@npm:^6.10.3, qs@npm:^6.11.0": version: 6.11.2 resolution: "qs@npm:6.11.2" dependencies: @@ -25826,6 +26234,19 @@ __metadata: languageName: node linkType: hard +"resolve@npm:^1.0.0": + version: 1.22.8 + resolution: "resolve@npm:1.22.8" + dependencies: + is-core-module: ^2.13.0 + path-parse: ^1.0.7 + supports-preserve-symlinks-flag: ^1.0.0 + bin: + resolve: bin/resolve + checksum: f8a26958aa572c9b064562750b52131a37c29d072478ea32e129063e2da7f83e31f7f11e7087a18225a8561cfe8d2f0df9dbea7c9d331a897571c0a2527dbb4c + languageName: node + linkType: hard + "resolve@npm:^1.1.7, resolve@npm:^1.10.0, resolve@npm:^1.10.1, resolve@npm:^1.12.0, resolve@npm:^1.14.2, resolve@npm:^1.17.0, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.2": version: 1.22.3 resolution: "resolve@npm:1.22.3" @@ -25852,6 +26273,19 @@ __metadata: languageName: node linkType: hard +"resolve@patch:resolve@^1.0.0#~builtin": + version: 1.22.8 + resolution: "resolve@patch:resolve@npm%3A1.22.8#~builtin::version=1.22.8&hash=c3c19d" + dependencies: + is-core-module: ^2.13.0 + path-parse: ^1.0.7 + supports-preserve-symlinks-flag: ^1.0.0 + bin: + resolve: bin/resolve + checksum: 5479b7d431cacd5185f8db64bfcb7286ae5e31eb299f4c4f404ad8aa6098b77599563ac4257cb2c37a42f59dfc06a1bec2bcf283bb448f319e37f0feb9a09847 + languageName: node + linkType: hard + "resolve@patch:resolve@^1.1.7#~builtin, resolve@patch:resolve@^1.10.0#~builtin, resolve@patch:resolve@^1.10.1#~builtin, resolve@patch:resolve@^1.12.0#~builtin, resolve@patch:resolve@^1.14.2#~builtin, resolve@patch:resolve@^1.17.0#~builtin, resolve@patch:resolve@^1.19.0#~builtin, resolve@patch:resolve@^1.20.0#~builtin, resolve@patch:resolve@^1.22.1#~builtin, resolve@patch:resolve@^1.22.2#~builtin": version: 1.22.3 resolution: "resolve@patch:resolve@npm%3A1.22.3#~builtin::version=1.22.3&hash=c3c19d" @@ -25925,7 +26359,7 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:^2.2.8, rimraf@npm:^2.6.3": +"rimraf@npm:^2.2.8, rimraf@npm:^2.6.1, rimraf@npm:^2.6.3": version: 2.7.1 resolution: "rimraf@npm:2.7.1" dependencies: @@ -26969,6 +27403,18 @@ __metadata: languageName: node linkType: hard +"socket.io-client@npm:^4.7.2": + version: 4.7.2 + resolution: "socket.io-client@npm:4.7.2" + dependencies: + "@socket.io/component-emitter": ~3.1.0 + debug: ~4.3.2 + engine.io-client: ~6.5.2 + socket.io-parser: ~4.2.4 + checksum: 8f0ab6b623e014d889bae0cd847ef7826658e8f131bd9367ee5ae4404bb52a6d7b1755b8fbe8e68799b60e92149370a732b381f913b155e40094facb135cd088 + languageName: node + linkType: hard + "socket.io-parser@npm:~4.2.4": version: 4.2.4 resolution: "socket.io-parser@npm:4.2.4" @@ -26979,6 +27425,21 @@ __metadata: languageName: node linkType: hard +"socket.io@npm:*": + version: 4.7.2 + resolution: "socket.io@npm:4.7.2" + dependencies: + accepts: ~1.3.4 + base64id: ~2.0.0 + cors: ~2.8.5 + debug: ~4.3.2 + engine.io: ~6.5.2 + socket.io-adapter: ~2.5.2 + socket.io-parser: ~4.2.4 + checksum: 2dfac8983a75e100e889c3dafc83b21b75a9863d0d1ee79cdc60c4391d5d9dffcf3a86fc8deca7568032bc11c2572676335fd2e469c7982f40d19f1141d4b266 + languageName: node + linkType: hard + "socket.io@npm:^4.4.1": version: 4.7.1 resolution: "socket.io@npm:4.7.1" @@ -27087,7 +27548,7 @@ __metadata: languageName: node linkType: hard -"source-map-support@npm:^0.5.6, source-map-support@npm:~0.5.20": +"source-map-support@npm:^0.5.12, source-map-support@npm:^0.5.6, source-map-support@npm:~0.5.20": version: 0.5.21 resolution: "source-map-support@npm:0.5.21" dependencies: @@ -27624,6 +28085,13 @@ __metadata: languageName: node linkType: hard +"strip-json-comments@npm:^2.0.0": + version: 2.0.1 + resolution: "strip-json-comments@npm:2.0.1" + checksum: 1074ccb63270d32ca28edfb0a281c96b94dc679077828135141f27d52a5a398ef5e78bcf22809d23cadc2b81dfbe345eb5fd8699b385c8b1128907dec4a7d1e1 + languageName: node + linkType: hard + "style-inject@npm:^0.3.0": version: 0.3.0 resolution: "style-inject@npm:0.3.0" @@ -27705,6 +28173,24 @@ __metadata: languageName: node linkType: hard +"superagent@npm:^8.0.5": + version: 8.1.2 + resolution: "superagent@npm:8.1.2" + dependencies: + component-emitter: ^1.3.0 + cookiejar: ^2.1.4 + debug: ^4.3.4 + fast-safe-stringify: ^2.1.1 + form-data: ^4.0.0 + formidable: ^2.1.2 + methods: ^1.1.2 + mime: 2.6.0 + qs: ^6.11.0 + semver: ^7.3.8 + checksum: f3601c5ccae34d5ba684a03703394b5d25931f4ae2e1e31a1de809f88a9400e997ece037f9accf148a21c408f950dc829db1e4e23576a7f9fe0efa79fd5c9d2f + languageName: node + linkType: hard + "superstruct@npm:^0.14.2": version: 0.14.2 resolution: "superstruct@npm:0.14.2" @@ -27719,6 +28205,16 @@ __metadata: languageName: node linkType: hard +"supertest@npm:^6.3.3": + version: 6.3.3 + resolution: "supertest@npm:6.3.3" + dependencies: + methods: ^1.1.2 + superagent: ^8.0.5 + checksum: 38239e517f7ba62b7a139a79c5c48d55f8d67b5ff4b6e51d5b07732ca8bbc4a28ffa1b10916fbb403dd013a054dbf028edc5850057d9a43aecbff439d494673e + languageName: node + linkType: hard + "supports-color@npm:8.1.1, supports-color@npm:^8.0.0": version: 8.1.1 resolution: "supports-color@npm:8.1.1" @@ -28279,6 +28775,15 @@ __metadata: languageName: node linkType: hard +"tree-kill@npm:^1.2.2": + version: 1.2.2 + resolution: "tree-kill@npm:1.2.2" + bin: + tree-kill: cli.js + checksum: 49117f5f410d19c84b0464d29afb9642c863bc5ba40fcb9a245d474c6d5cc64d1b177a6e6713129eb346b40aebb9d4631d967517f9fbe8251c35b21b13cd96c7 + languageName: node + linkType: hard + "trim-newlines@npm:^3.0.0": version: 3.0.1 resolution: "trim-newlines@npm:3.0.1" @@ -28373,7 +28878,7 @@ __metadata: languageName: node linkType: hard -"ts-jest@npm:^29.0.3": +"ts-jest@npm:^29.0.3, ts-jest@npm:^29.1.1": version: 29.1.1 resolution: "ts-jest@npm:29.1.1" dependencies: @@ -28406,7 +28911,34 @@ __metadata: languageName: node linkType: hard -"ts-node@npm:^10.7.0, ts-node@npm:^10.8.1, ts-node@npm:^10.9.1": +"ts-node-dev@npm:^2.0.0": + version: 2.0.0 + resolution: "ts-node-dev@npm:2.0.0" + dependencies: + chokidar: ^3.5.1 + dynamic-dedupe: ^0.3.0 + minimist: ^1.2.6 + mkdirp: ^1.0.4 + resolve: ^1.0.0 + rimraf: ^2.6.1 + source-map-support: ^0.5.12 + tree-kill: ^1.2.2 + ts-node: ^10.4.0 + tsconfig: ^7.0.0 + peerDependencies: + node-notifier: "*" + typescript: "*" + peerDependenciesMeta: + node-notifier: + optional: true + bin: + ts-node-dev: lib/bin.js + tsnd: lib/bin.js + checksum: d654b401de3d13c167981481be2a375229f6bfd2aeedf43bc0b6816e57676fcbfba3afdcf209c7a06fb6bd8768ca548c2eb0a0c9d38fa42246be3f50df1b28fb + languageName: node + linkType: hard + +"ts-node@npm:^10.4.0, ts-node@npm:^10.7.0, ts-node@npm:^10.8.1, ts-node@npm:^10.9.1": version: 10.9.1 resolution: "ts-node@npm:10.9.1" dependencies: @@ -28467,6 +28999,18 @@ __metadata: languageName: node linkType: hard +"tsconfig@npm:^7.0.0": + version: 7.0.0 + resolution: "tsconfig@npm:7.0.0" + dependencies: + "@types/strip-bom": ^3.0.0 + "@types/strip-json-comments": 0.0.30 + strip-bom: ^3.0.0 + strip-json-comments: ^2.0.0 + checksum: 8bce05e93c673defd56d93d83d4055e49651d3947c076339c4bc15d47b7eb5029bed194087e568764213a2e4bf45c477ba9f4da16adfd92cd901af7c09e4517e + languageName: node + linkType: hard + "tslib@npm:1.14.1, tslib@npm:^1.8.1, tslib@npm:^1.9.0": version: 1.14.1 resolution: "tslib@npm:1.14.1" @@ -28871,6 +29415,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~5.25.1": + version: 5.25.3 + resolution: "undici-types@npm:5.25.3" + checksum: ec9d2cc36520cbd9fbe3b3b6c682a87fe5be214699e1f57d1e3d9a2cb5be422e62735f06e0067dc325fd3dd7404c697e4d479f9147dc8a804e049e29f357f2ff + languageName: node + linkType: hard + "unicode-canonical-property-names-ecmascript@npm:^2.0.0": version: 2.0.0 resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.0"