From 2d3544fe3779f0ecff7595c5127ac60fa0102662 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sat, 24 Feb 2024 00:18:48 +0200 Subject: [PATCH] feat: add socker connection between client and server --- packages/server/package.json | 1 + .../src/api/controllers/Webhooks/Webhooks.ts | 4 +- packages/server/src/loaders/express.ts | 30 ++++ packages/server/src/server.ts | 13 -- .../Plaid/PlaidFetchTransactionsJob.ts | 4 + .../src/services/Banking/Plaid/PlaidItem.ts | 7 +- .../Plaid/PlaidWebhookTenantBootMiddleware.ts | 32 ++++ .../services/Banking/Plaid/PlaidWebhooks.ts | 14 +- ...20240222134235_create_plaid_items_table.js | 15 ++ .../src/system/models/SystemPlaidItem.ts | 49 ++++++ packages/server/src/system/models/index.ts | 10 +- packages/webapp/package.json | 2 + .../src/components/Dashboard/Dashboard.tsx | 2 + .../components/Dashboard/DashboardSockets.tsx | 31 ++++ .../Receipts/ReceiptForm/ReceiptForm.tsx | 5 +- pnpm-lock.yaml | 165 +++++++++++++++++- 16 files changed, 357 insertions(+), 27 deletions(-) create mode 100644 packages/server/src/services/Banking/Plaid/PlaidWebhookTenantBootMiddleware.ts create mode 100644 packages/server/src/system/migrations/20240222134235_create_plaid_items_table.js create mode 100644 packages/server/src/system/models/SystemPlaidItem.ts create mode 100644 packages/webapp/src/components/Dashboard/DashboardSockets.tsx diff --git a/packages/server/package.json b/packages/server/package.json index 5265e986d..b47058471 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -98,6 +98,7 @@ "reflect-metadata": "^0.1.13", "rtl-detect": "^1.0.4", "source-map-loader": "^4.0.1", + "socket.io": "^4.7.4", "tmp-promise": "^3.0.3", "ts-transformer-keys": "^0.4.2", "tsyringe": "^4.3.0", diff --git a/packages/server/src/api/controllers/Webhooks/Webhooks.ts b/packages/server/src/api/controllers/Webhooks/Webhooks.ts index dd39ad526..955e382fb 100644 --- a/packages/server/src/api/controllers/Webhooks/Webhooks.ts +++ b/packages/server/src/api/controllers/Webhooks/Webhooks.ts @@ -3,6 +3,7 @@ import { PlaidApplication } from '@/services/Banking/Plaid/PlaidApplication'; import { Request, Response } from 'express'; import { Inject, Service } from 'typedi'; import BaseController from '../BaseController'; +import { PlaidWebhookTenantBootMiddleware } from '@/services/Banking/Plaid/PlaidWebhookTenantBootMiddleware'; @Service() export class Webhooks extends BaseController { @@ -15,6 +16,7 @@ export class Webhooks extends BaseController { router() { const router = Router(); + router.use(PlaidWebhookTenantBootMiddleware); router.post('/plaid', this.plaidWebhooks.bind(this)); return router; @@ -34,8 +36,6 @@ export class Webhooks extends BaseController { item_id: plaidItemId, } = req.body; - console.log(req.body, 'triggered'); - await this.plaidApp.webhooks( tenantId, plaidItemId, diff --git a/packages/server/src/loaders/express.ts b/packages/server/src/loaders/express.ts index cf9787a95..1ecb33020 100644 --- a/packages/server/src/loaders/express.ts +++ b/packages/server/src/loaders/express.ts @@ -5,6 +5,8 @@ import boom from 'express-boom'; import errorHandler from 'errorhandler'; import bodyParser from 'body-parser'; import fileUpload from 'express-fileupload'; +import { Server } from 'socket.io'; +import Container from 'typedi'; import routes from 'api'; import LoggerMiddleware from '@/api/middleware/LoggerMiddleware'; import AgendashController from '@/api/controllers/Agendash'; @@ -72,4 +74,32 @@ export default ({ app }) => { app.use((req: Request, res: Response, next: NextFunction) => { return res.boom.notFound(); }); + const server = app.listen(app.get('port'), (err) => { + if (err) { + console.log(err); + process.exit(1); + return; + } + console.log(` + ################################################ + Server listening on port: ${app.get('port')} + ################################################ + `); + }); + const io = new Server(server, {}); + + // Set socket.io listeners. + io.on('connection', (socket) => { + console.log('SOCKET CONNECTED'); + + socket.on('disconnect', () => { + console.log('SOCKET DISCONNECTED'); + }); + }); + // Middleware to pass socket to each request object. + app.use((req: Request, res: Response, next: NextFunction) => { + req.io = io; + next(); + }); + Container.set('socket', io); }; diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts index 2bbc9a789..9c08e6533 100644 --- a/packages/server/src/server.ts +++ b/packages/server/src/server.ts @@ -10,19 +10,6 @@ async function startServer() { // Intiialize all registered loaders. await loadersFactory({ expressApp: app }); - - app.listen(app.get('port'), (err) => { - if (err) { - console.log(err); - process.exit(1); - return; - } - console.log(` - ################################################ - Server listening on port: ${app.get('port')} - ################################################ - `); - }); } startServer(); diff --git a/packages/server/src/services/Banking/Plaid/PlaidFetchTransactionsJob.ts b/packages/server/src/services/Banking/Plaid/PlaidFetchTransactionsJob.ts index 6ff699fd7..a397037c2 100644 --- a/packages/server/src/services/Banking/Plaid/PlaidFetchTransactionsJob.ts +++ b/packages/server/src/services/Banking/Plaid/PlaidFetchTransactionsJob.ts @@ -25,11 +25,15 @@ export class PlaidFetchTransactionsJob { const plaidFetchTransactionsService = Container.get( PlaidUpdateTransactions ); + const io = Container.get('socket'); + try { await plaidFetchTransactionsService.updateTransactions( tenantId, plaidItemId ); + // Notify the frontend to reflect the new transactions changes. + io.emit('NEW_TRANSACTIONS_DATA', { plaidItemId }); done(); } catch (error) { console.log(error); diff --git a/packages/server/src/services/Banking/Plaid/PlaidItem.ts b/packages/server/src/services/Banking/Plaid/PlaidItem.ts index bde464096..9e83202f9 100644 --- a/packages/server/src/services/Banking/Plaid/PlaidItem.ts +++ b/packages/server/src/services/Banking/Plaid/PlaidItem.ts @@ -7,6 +7,7 @@ import { IPlaidItemCreatedEventPayload, PlaidItemDTO, } from '@/interfaces/Plaid'; +import SystemPlaidItem from '@/system/models/SystemPlaidItem'; @Service() export class PlaidItemService { @@ -29,19 +30,23 @@ export class PlaidItemService { const plaidInstance = new PlaidClientWrapper(); - // exchange the public token for a private access token and store with the item. + // Exchange the public token for a private access token and store with the item. const response = await plaidInstance.itemPublicTokenExchange({ public_token: publicToken, }); const plaidAccessToken = response.data.access_token; const plaidItemId = response.data.item_id; + // Store the Plaid item metadata on tenant scope. const plaidItem = await PlaidItem.query().insertAndFetch({ tenantId, plaidAccessToken, plaidItemId, plaidInstitutionId: institutionId, }); + // Stores the Plaid item id on system scope. + await SystemPlaidItem.query().insert({ tenantId, plaidItemId }); + // Triggers `onPlaidItemCreated` event. await this.eventPublisher.emitAsync(events.plaid.onItemCreated, { tenantId, diff --git a/packages/server/src/services/Banking/Plaid/PlaidWebhookTenantBootMiddleware.ts b/packages/server/src/services/Banking/Plaid/PlaidWebhookTenantBootMiddleware.ts new file mode 100644 index 000000000..6a1c177ba --- /dev/null +++ b/packages/server/src/services/Banking/Plaid/PlaidWebhookTenantBootMiddleware.ts @@ -0,0 +1,32 @@ +import { Request, Response, NextFunction } from 'express'; +import { SystemPlaidItem, Tenant } from '@/system/models'; +import tenantDependencyInjection from '@/api/middleware/TenantDependencyInjection'; + +export const PlaidWebhookTenantBootMiddleware = async ( + req: Request, + res: Response, + next: NextFunction +) => { + const { item_id: plaidItemId } = req.body; + const plaidItem = await SystemPlaidItem.query().findOne({ plaidItemId }); + + const notFoundOrganization = () => { + return res.boom.unauthorized('Organization identication not found.', { + errors: [{ type: 'ORGANIZATION.ID.NOT.FOUND', code: 100 }], + }); + }; + // In case the given organization not found. + if (!plaidItem) { + return notFoundOrganization(); + } + const tenant = await Tenant.query() + .findById(plaidItem.tenantId) + .withGraphFetched('metadata'); + + // When the given organization id not found on the system storage. + if (!tenant) { + return notFoundOrganization(); + } + tenantDependencyInjection(req, tenant); + next(); +}; diff --git a/packages/server/src/services/Banking/Plaid/PlaidWebhooks.ts b/packages/server/src/services/Banking/Plaid/PlaidWebhooks.ts index f0389e893..5c3afb1ec 100644 --- a/packages/server/src/services/Banking/Plaid/PlaidWebhooks.ts +++ b/packages/server/src/services/Banking/Plaid/PlaidWebhooks.ts @@ -8,17 +8,17 @@ export class PlaidWebooks { /** * Listens to Plaid webhooks - * @param {number} tenantId - * @param {string} webhookType - * @param {string} plaidItemId - * @param {string} webhookCode + * @param {number} tenantId - Tenant Id. + * @param {string} webhookType - Webhook type. + * @param {string} plaidItemId - Plaid item Id. + * @param {string} webhookCode - webhook code. */ public async webhooks( tenantId: number, plaidItemId: string, webhookType: string, webhookCode: string - ) { + ): Promise { const _webhookType = webhookType.toLowerCase(); // There are five types of webhooks: AUTH, TRANSACTIONS, ITEM, INCOME, and ASSETS. @@ -43,7 +43,7 @@ export class PlaidWebooks { webhookType: string, webhookCode: string, plaidItemId: string - ) { + ): Promise { console.log( `UNHANDLED ${webhookType} WEBHOOK: ${webhookCode}: Plaid item id ${plaidItemId}: unhandled webhook type received.` ); @@ -59,7 +59,7 @@ export class PlaidWebooks { additionalInfo: string, webhookCode: string, plaidItemId: string - ) { + ): void { console.log( `WEBHOOK: TRANSACTIONS: ${webhookCode}: Plaid_item_id ${plaidItemId}: ${additionalInfo}` ); diff --git a/packages/server/src/system/migrations/20240222134235_create_plaid_items_table.js b/packages/server/src/system/migrations/20240222134235_create_plaid_items_table.js new file mode 100644 index 000000000..4098bccca --- /dev/null +++ b/packages/server/src/system/migrations/20240222134235_create_plaid_items_table.js @@ -0,0 +1,15 @@ +exports.up = function (knex) { + return knex.schema.createTable('plaid_items', (table) => { + table.bigIncrements('id'); + table + .bigInteger('tenant_id') + .unsigned() + .index() + .references('id') + .inTable('tenants'); + table.string('plaid_item_id'); + table.timestamps(); + }); +}; + +exports.down = (knex) => {}; diff --git a/packages/server/src/system/models/SystemPlaidItem.ts b/packages/server/src/system/models/SystemPlaidItem.ts new file mode 100644 index 000000000..43b8fb4af --- /dev/null +++ b/packages/server/src/system/models/SystemPlaidItem.ts @@ -0,0 +1,49 @@ +import { Model } from 'objection'; +import SystemModel from '@/system/models/SystemModel'; + +export default class SystemPlaidItem extends SystemModel { + tenantId: number; + plaidItemId: string; + + /** + * Table name. + */ + static get tableName() { + return 'plaid_items'; + } + + /** + * Timestamps columns. + */ + get timestamps() { + return ['createdAt', 'updatedAt']; + } + + /** + * Virtual attributes. + */ + static get virtualAttributes() { + return []; + } + + /** + * Relationship mapping. + */ + static get relationMappings() { + const Tenant = require('system/models/Tenant'); + + return { + /** + * System user may belongs to tenant model. + */ + tenant: { + relation: Model.BelongsToOneRelation, + modelClass: Tenant.default, + join: { + from: 'users.tenantId', + to: 'tenants.id', + }, + }, + }; + } +} diff --git a/packages/server/src/system/models/index.ts b/packages/server/src/system/models/index.ts index 44a92ea27..61fa5b708 100644 --- a/packages/server/src/system/models/index.ts +++ b/packages/server/src/system/models/index.ts @@ -3,5 +3,13 @@ import TenantMetadata from './TenantMetadata'; import SystemUser from './SystemUser'; import PasswordReset from './PasswordReset'; import Invite from './Invite'; +import SystemPlaidItem from './SystemPlaidItem'; -export { Tenant, TenantMetadata, SystemUser, PasswordReset, Invite }; +export { + Tenant, + TenantMetadata, + SystemUser, + PasswordReset, + Invite, + SystemPlaidItem, +}; diff --git a/packages/webapp/package.json b/packages/webapp/package.json index a11d565bd..9d7ab57ae 100644 --- a/packages/webapp/package.json +++ b/packages/webapp/package.json @@ -40,6 +40,7 @@ "@types/react-transition-group": "^4.4.5", "@types/styled-components": "^5.1.25", "@types/yup": "^0.29.13", + "@types/socket.io-client": "^3.0.0", "@typescript-eslint/eslint-plugin": "^2.10.0", "@typescript-eslint/parser": "^2.10.0", "@welldone-software/why-did-you-render": "^6.0.0-rc.1", @@ -114,6 +115,7 @@ "style-loader": "0.23.1", "styled-components": "^5.3.1", "stylis-rtlcss": "^2.1.1", + "socket.io-client": "^4.7.4", "typescript": "^4.8.3", "yup": "^0.28.1" }, diff --git a/packages/webapp/src/components/Dashboard/Dashboard.tsx b/packages/webapp/src/components/Dashboard/Dashboard.tsx index 467963849..8ab7ef365 100644 --- a/packages/webapp/src/components/Dashboard/Dashboard.tsx +++ b/packages/webapp/src/components/Dashboard/Dashboard.tsx @@ -14,6 +14,7 @@ import GlobalHotkeys from './GlobalHotkeys'; import DashboardProvider from './DashboardProvider'; import DrawersContainer from '@/components/DrawersContainer'; import AlertsContainer from '@/containers/AlertsContainer'; +import { DashboardSockets } from './DashboardSockets'; /** * Dashboard preferences. @@ -50,6 +51,7 @@ export default function Dashboard() { + diff --git a/packages/webapp/src/components/Dashboard/DashboardSockets.tsx b/packages/webapp/src/components/Dashboard/DashboardSockets.tsx new file mode 100644 index 000000000..ec905261e --- /dev/null +++ b/packages/webapp/src/components/Dashboard/DashboardSockets.tsx @@ -0,0 +1,31 @@ +import { useEffect, useRef } from 'react'; +import { useQueryClient } from 'react-query'; +import { io } from 'socket.io-client'; +import t from '@/hooks/query/types'; +import { AppToaster } from '@/components'; +import { Intent } from '@blueprintjs/core'; + +export function DashboardSockets() { + const socket = useRef(); + const client = useQueryClient(); + + useEffect(() => { + socket.current = io('ws://localhost:4000'); + + socket.current.on('NEW_TRANSACTIONS_DATA', () => { + client.invalidateQueries(t.ACCOUNTS); + client.invalidateQueries(t.ACCOUNT_TRANSACTION); + client.invalidateQueries(t.CASH_FLOW_ACCOUNTS); + client.invalidateQueries(t.CASH_FLOW_TRANSACTIONS); + + AppToaster.show({ + message: 'The Plaid connected accounts have been updated.', + intent: Intent.SUCCESS, + }); + }); + return () => { + socket.current.removeAllListeners(); + socket.current.close(); + }; + }, []); +} diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.tsx index 62206bdbf..990c083ba 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.tsx @@ -34,7 +34,10 @@ import { transformFormValuesToRequest, resetFormState, } from './utils'; -import { ReceiptSyncAutoExRateToForm, ReceiptSyncIncrementSettingsToForm } from './components'; +import { + ReceiptSyncAutoExRateToForm, + ReceiptSyncIncrementSettingsToForm, +} from './components'; /** * Receipt form. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1a27262df..cb8bc2771 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -263,6 +263,9 @@ importers: rtl-detect: specifier: ^1.0.4 version: 1.0.4 + socket.io: + specifier: ^4.7.4 + version: 4.7.4 source-map-loader: specifier: ^4.0.1 version: 4.0.1(webpack@5.76.0) @@ -528,6 +531,9 @@ importers: '@types/react-transition-group': specifier: ^4.4.5 version: 4.4.5 + '@types/socket.io-client': + specifier: ^3.0.0 + version: 3.0.0 '@types/styled-components': specifier: ^5.1.25 version: 5.1.26 @@ -747,6 +753,9 @@ importers: semver: specifier: 6.3.0 version: 6.3.0 + socket.io-client: + specifier: ^4.7.4 + version: 4.7.4 style-loader: specifier: 0.23.1 version: 0.23.1 @@ -5908,6 +5917,10 @@ packages: resolution: {integrity: sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==} dev: true + /@socket.io/component-emitter@3.1.0: + resolution: {integrity: sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==} + dev: false + /@surma/rollup-plugin-off-main-thread@2.2.3: resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} dependencies: @@ -6460,10 +6473,20 @@ packages: '@types/node': 14.18.36 dev: false + /@types/cookie@0.4.1: + resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} + dev: false + /@types/cookiejar@2.1.2: resolution: {integrity: sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==} dev: true + /@types/cors@2.8.17: + resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==} + dependencies: + '@types/node': 14.18.36 + dev: false + /@types/dom4@2.0.2: resolution: {integrity: sha512-Rt4IC1T7xkCWa0OG1oSsPa0iqnxlDeQqKXZAHrQGLb7wFGncWm85MaxKUjAGejOrUynOgWlFi4c6S6IyJwoK4g==} dev: false @@ -6601,7 +6624,7 @@ packages: /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 18.13.0 + '@types/node': 14.18.36 dev: false /@types/knex@0.16.1(mysql2@1.7.0)(mysql@2.18.1): @@ -6781,7 +6804,7 @@ packages: /@types/responselike@1.0.1: resolution: {integrity: sha512-TiGnitEDxj2X0j+98Eqk5lv/Cij8oHd32bU4D/Yw6AOq7vvTk0gSD2GPj0G/HkvhMoVsdlhYF4yqqlyPBTM6Sg==} dependencies: - '@types/node': 18.13.0 + '@types/node': 14.18.36 dev: false /@types/retry@0.12.0: @@ -6816,6 +6839,17 @@ packages: '@types/node': 14.18.36 dev: false + /@types/socket.io-client@3.0.0: + resolution: {integrity: sha512-s+IPvFoEIjKA3RdJz/Z2dGR4gLgysKi8owcnrVwNjgvc01Lk68LJDDsG2GRqegFITcxmvCMYM7bhMpwEMlHmDg==} + deprecated: This is a stub types definition. socket.io-client provides its own type definitions, so you do not need this installed. + dependencies: + socket.io-client: 4.7.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: false + /@types/sockjs@0.3.34: resolution: {integrity: sha512-R+n7qBFnm/6jinlteC9DBL5dGiDGjWAvjo4viUanpnc/dG1y7uDoacXPIQ/PQEg1fI912SMHIa014ZjRpvDw4g==} dependencies: @@ -8604,6 +8638,11 @@ packages: /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + /base64id@2.0.0: + resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} + engines: {node: ^4.5.0 || >= 5.9} + dev: false + /base@0.11.2: resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} engines: {node: '>=0.10.0'} @@ -10041,6 +10080,11 @@ packages: engines: {node: '>= 0.6'} dev: false + /cookie@0.4.2: + resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} + engines: {node: '>= 0.6'} + dev: false + /cookie@0.5.0: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} @@ -10103,6 +10147,14 @@ packages: /core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + /cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + dev: false + /cosmiconfig-typescript-loader@4.3.0(@types/node@18.13.0)(cosmiconfig@8.0.0)(ts-node@10.9.1)(typescript@4.9.5): resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==} engines: {node: '>=12', npm: '>=6'} @@ -11407,6 +11459,45 @@ packages: dependencies: once: 1.4.0 + /engine.io-client@6.5.3: + resolution: {integrity: sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==} + dependencies: + '@socket.io/component-emitter': 3.1.0 + debug: 4.3.4(supports-color@5.5.0) + engine.io-parser: 5.2.2 + ws: 8.11.0 + xmlhttprequest-ssl: 2.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: false + + /engine.io-parser@5.2.2: + resolution: {integrity: sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==} + engines: {node: '>=10.0.0'} + dev: false + + /engine.io@6.5.4: + resolution: {integrity: sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==} + engines: {node: '>=10.2.0'} + dependencies: + '@types/cookie': 0.4.1 + '@types/cors': 2.8.17 + '@types/node': 14.18.36 + accepts: 1.3.8 + base64id: 2.0.0 + cookie: 0.4.2 + cors: 2.8.5 + debug: 4.3.4(supports-color@5.5.0) + engine.io-parser: 5.2.2 + ws: 8.11.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: false + /enhanced-resolve@0.9.1: resolution: {integrity: sha512-kxpoMgrdtkXZ5h0SeraBS1iRntpTpQ3R8ussdb38+UAFnMGX5DDyJXePm+OCHOcoXvHDw7mc2erbJBpDnl7TPw==} engines: {node: '>=0.6'} @@ -22943,6 +23034,58 @@ packages: - supports-color dev: false + /socket.io-adapter@2.5.4: + resolution: {integrity: sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==} + dependencies: + debug: 4.3.4(supports-color@5.5.0) + ws: 8.11.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: false + + /socket.io-client@4.7.4: + resolution: {integrity: sha512-wh+OkeF0rAVCrABWQBaEjLfb7DVPotMbu0cgWgyR0v6eA4EoVnAwcIeIbcdTE3GT/H3kbdLl7OoH2+asoDRIIg==} + engines: {node: '>=10.0.0'} + dependencies: + '@socket.io/component-emitter': 3.1.0 + debug: 4.3.4(supports-color@5.5.0) + engine.io-client: 6.5.3 + socket.io-parser: 4.2.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: false + + /socket.io-parser@4.2.4: + resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==} + engines: {node: '>=10.0.0'} + dependencies: + '@socket.io/component-emitter': 3.1.0 + debug: 4.3.4(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + dev: false + + /socket.io@4.7.4: + resolution: {integrity: sha512-DcotgfP1Zg9iP/dH9zvAQcWrE0TtbMVwXmlV4T4mqsvY+gw+LqUGPfx2AoVyRk0FLME+GQhufDMyacFmw7ksqw==} + engines: {node: '>=10.2.0'} + dependencies: + accepts: 1.3.8 + base64id: 2.0.0 + cors: 2.8.5 + debug: 4.3.4(supports-color@5.5.0) + engine.io: 6.5.4 + socket.io-adapter: 2.5.4 + socket.io-parser: 4.2.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: false + /sockjs@0.3.24: resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==} dependencies: @@ -25815,6 +25958,19 @@ packages: optional: true dev: false + /ws@8.11.0: + resolution: {integrity: sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: false + /ws@8.14.2: resolution: {integrity: sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==} engines: {node: '>=10.0.0'} @@ -25860,6 +26016,11 @@ packages: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} dev: false + /xmlhttprequest-ssl@2.0.0: + resolution: {integrity: sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==} + engines: {node: '>=0.4.0'} + dev: false + /xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'}