diff --git a/apps/json-api-front/proxy.conf.json b/apps/json-api-front/proxy.conf.json index 63dd627..6974fc0 100644 --- a/apps/json-api-front/proxy.conf.json +++ b/apps/json-api-front/proxy.conf.json @@ -2,5 +2,10 @@ "/api": { "target": "http://localhost:3000", "secure": false + }, + "/rpc": { + "target": "http://localhost:3000", + "secure": false, + "ws": true } } diff --git a/apps/json-api-front/src/app/app.component.ts b/apps/json-api-front/src/app/app.component.ts index 30f8445..43a3508 100644 --- a/apps/json-api-front/src/app/app.component.ts +++ b/apps/json-api-front/src/app/app.component.ts @@ -8,15 +8,11 @@ import { Rpc, } from '@klerick/nestjs-json-rpc-sdk/json-rpc-sdk.module'; +import { RpcService as IRpcService } from '@nestjs-json-api/type-for-rpc'; import { switchMap } from 'rxjs'; -interface TestRpc { - test(a: number, b: number): Promise; - test2(firstArg: string, secondArg: number): Promise; -} - type RpcMap = { - TestRpc: TestRpc; + RpcService: IRpcService; }; @Component({ @@ -33,9 +29,15 @@ export class AppComponent implements OnInit { private rpcBatch = inject(RPC_BATCH); ngOnInit(): void { - const rpc1 = this.rpc.TestRpc.test(1, 2); - const rpc2 = this.rpc.TestRpc.test2('string', 2); + const rpc1 = this.rpc.RpcService.someMethode(1); + + const rpc2 = this.rpc.RpcService.methodeWithObjectParams({ + a: 1, + b: 1, + }); + this.rpcBatch(rpc2, rpc1).subscribe(([r2, r1]) => console.log(r1, r2)); + this.JsonApiSdkService.getAll(class Users {}, { page: { size: 2, @@ -74,9 +76,9 @@ export class AppComponent implements OnInit { const tmpUsers = new Users(); tmpUsers.id = 1; - // this.JsonApiSdkService.getRelationships(tmpUsers, 'addresses').subscribe( - // (r) => console.log(r) - // ); + this.JsonApiSdkService.getRelationships(tmpUsers, 'addresses').subscribe( + (r) => console.log(r) + ); const roles = new Roles(); roles.id = 10000; diff --git a/apps/json-api-front/src/app/app.config.ts b/apps/json-api-front/src/app/app.config.ts index f02b645..d916faf 100644 --- a/apps/json-api-front/src/app/app.config.ts +++ b/apps/json-api-front/src/app/app.config.ts @@ -4,6 +4,7 @@ import { JsonRpcAngular, TransportType, } from '@klerick/nestjs-json-rpc-sdk/json-rpc-sdk.module'; +import io from 'socket.io-client'; export const appConfig: ApplicationConfig = { providers: [ @@ -17,9 +18,12 @@ export const appConfig: ApplicationConfig = { ), importProvidersFrom( JsonRpcAngular.forRoot({ - transport: TransportType.HTTP, + transport: TransportType.WS, rpcPath: 'rpc', - rpcHost: 'http://localhost:4200', + rpcHost: 'ws://localhost:4200', + useWsNativeSocket: true, + // useWsNativeSocket: false, + // webSocketCtor: io('http://localhost:3000', { path: '/rpc' }), }) ), ], diff --git a/apps/json-api-server-e2e/src/json-api/json-api-sdk/atomic-sdk.spec.ts b/apps/json-api-server-e2e/src/json-api/json-api-sdk/atomic-sdk.spec.ts index 2bbf853..7b7f3c2 100644 --- a/apps/json-api-server-e2e/src/json-api/json-api-sdk/atomic-sdk.spec.ts +++ b/apps/json-api-server-e2e/src/json-api/json-api-sdk/atomic-sdk.spec.ts @@ -3,7 +3,7 @@ import { FilterOperand, JsonSdkPromise } from 'json-api-nestjs-sdk'; import { Addresses, CommentKind, Comments, Roles, Users } from 'database'; import { faker } from '@faker-js/faker'; import { getUser } from '../utils/data-utils'; -import { run, creatSdk } from '../utils/run-ppplication'; +import { run, creatSdk } from '../utils/run-application'; let app: INestApplication; diff --git a/apps/json-api-server-e2e/src/json-api/json-api-sdk/check-common-decorator.spec.ts b/apps/json-api-server-e2e/src/json-api/json-api-sdk/check-common-decorator.spec.ts index 91751fb..47b18d9 100644 --- a/apps/json-api-server-e2e/src/json-api/json-api-sdk/check-common-decorator.spec.ts +++ b/apps/json-api-server-e2e/src/json-api/json-api-sdk/check-common-decorator.spec.ts @@ -3,7 +3,7 @@ import { FilterOperand, JsonSdkPromise } from 'json-api-nestjs-sdk'; import { AxiosError } from 'axios'; import { Users } from 'database'; -import { run, creatSdk } from '../utils/run-ppplication'; +import { run, creatSdk } from '../utils/run-application'; let app: INestApplication; diff --git a/apps/json-api-server-e2e/src/json-api/json-api-sdk/check-othe-call.spec.ts b/apps/json-api-server-e2e/src/json-api/json-api-sdk/check-othe-call.spec.ts index 3a44773..076dc27 100644 --- a/apps/json-api-server-e2e/src/json-api/json-api-sdk/check-othe-call.spec.ts +++ b/apps/json-api-server-e2e/src/json-api/json-api-sdk/check-othe-call.spec.ts @@ -4,7 +4,7 @@ import { BookList, Users } from 'database'; import { AxiosError } from 'axios'; import { faker } from '@faker-js/faker'; import { lastValueFrom } from 'rxjs'; -import { creatSdk, run, axiosAdapter } from '../utils/run-ppplication'; +import { creatSdk, run, axiosAdapter } from '../utils/run-application'; let app: INestApplication; diff --git a/apps/json-api-server-e2e/src/json-api/json-api-sdk/get-method.spec.ts b/apps/json-api-server-e2e/src/json-api/json-api-sdk/get-method.spec.ts index 8dec9b0..03e470c 100644 --- a/apps/json-api-server-e2e/src/json-api/json-api-sdk/get-method.spec.ts +++ b/apps/json-api-server-e2e/src/json-api/json-api-sdk/get-method.spec.ts @@ -4,7 +4,7 @@ import { faker } from '@faker-js/faker'; import { FilterOperand, JsonSdkPromise } from 'json-api-nestjs-sdk'; import { getUser } from '../utils/data-utils'; -import { creatSdk, run } from '../utils/run-ppplication'; +import { creatSdk, run } from '../utils/run-application'; let app: INestApplication; diff --git a/apps/json-api-server-e2e/src/json-api/json-api-sdk/patch-methode.spec.ts b/apps/json-api-server-e2e/src/json-api/json-api-sdk/patch-methode.spec.ts index ffdd50f..b2bf226 100644 --- a/apps/json-api-server-e2e/src/json-api/json-api-sdk/patch-methode.spec.ts +++ b/apps/json-api-server-e2e/src/json-api/json-api-sdk/patch-methode.spec.ts @@ -3,7 +3,7 @@ import { Addresses, CommentKind, Comments, Users } from 'database'; import { faker } from '@faker-js/faker'; import { JsonSdkPromise } from 'json-api-nestjs-sdk'; -import { creatSdk, run } from '../utils/run-ppplication'; +import { creatSdk, run } from '../utils/run-application'; let app: INestApplication; diff --git a/apps/json-api-server-e2e/src/json-api/json-api-sdk/post-method.spec.ts b/apps/json-api-server-e2e/src/json-api/json-api-sdk/post-method.spec.ts index 640eb99..ebc9b48 100644 --- a/apps/json-api-server-e2e/src/json-api/json-api-sdk/post-method.spec.ts +++ b/apps/json-api-server-e2e/src/json-api/json-api-sdk/post-method.spec.ts @@ -2,7 +2,7 @@ import { Addresses, BookList, CommentKind, Comments, Users } from 'database'; import { faker } from '@faker-js/faker'; import { JsonSdkPromise } from 'json-api-nestjs-sdk'; -import { creatSdk, run } from '../utils/run-ppplication'; +import { creatSdk, run } from '../utils/run-application'; import { INestApplication } from '@nestjs/common'; let app: INestApplication; diff --git a/apps/json-api-server-e2e/src/json-api/json-rpc/run-json-rpc.spec.ts b/apps/json-api-server-e2e/src/json-api/json-rpc/run-json-rpc.spec.ts index 7ce927b..3c15ada 100644 --- a/apps/json-api-server-e2e/src/json-api/json-rpc/run-json-rpc.spec.ts +++ b/apps/json-api-server-e2e/src/json-api/json-rpc/run-json-rpc.spec.ts @@ -5,7 +5,7 @@ import { RpcError, } from '@klerick/nestjs-json-rpc-sdk'; -import { creatRpcSdk, MapperRpc, run } from '../utils/run-ppplication'; +import { creatRpcSdk, MapperRpc, run } from '../utils/run-application'; let app: INestApplication; diff --git a/apps/json-api-server-e2e/src/json-api/json-rpc/run-ws-json-rpc.spec.ts b/apps/json-api-server-e2e/src/json-api/json-rpc/run-ws-json-rpc.spec.ts new file mode 100644 index 0000000..7f0752a --- /dev/null +++ b/apps/json-api-server-e2e/src/json-api/json-rpc/run-ws-json-rpc.spec.ts @@ -0,0 +1,102 @@ +import { INestApplication } from '@nestjs/common'; +import { + ResultRpcFactoryPromise, + ErrorCodeType, + RpcError, +} from '@klerick/nestjs-json-rpc-sdk'; + +import { creatWsRpcSdk, MapperRpc, run } from '../utils/run-application'; + +let app: INestApplication; + +beforeAll(async () => { + app = await run(); +}); + +afterAll(async () => { + await app.close(); +}); + +describe('Run ws json rpc:', () => { + let rpc: ResultRpcFactoryPromise['rpc']; + let rpcBatch: ResultRpcFactoryPromise['rpcBatch']; + let rpcForBatch: ResultRpcFactoryPromise['rpcForBatch']; + beforeEach(() => { + ({ rpc, rpcBatch, rpcForBatch } = creatWsRpcSdk()); + }); + + describe('Should be correct response', () => { + it('Should be call one method', async () => { + const input = 1; + const result = await rpc.RpcService.someMethode(input); + expect(result).toBe(input); + }); + + it('Should be correct response batch', async () => { + const input = 1; + const input2 = { + a: 1, + b: 2, + }; + const call1 = rpcForBatch.RpcService.someMethode(input); + const call2 = rpcForBatch.RpcService.methodeWithObjectParams(input2); + + const [result1, result2] = await rpcBatch(call1, call2); + expect(result1).toBe(input); + if ('error' in result2) { + throw Error('Return error'); + } + expect(result2.d).toEqual(`${input2.a}`); + expect(result2.c).toEqual(`${input2.b}`); + }); + }); + + describe('Check error', () => { + it('Should throw an error ' + ErrorCodeType.MethodNotFound, async () => { + const input = 1; + expect.assertions(6); + try { + // @ts-ignore + await rpc.IncorrectService.incorrectMethode(input); + } catch (e) { + expect(e).toBeInstanceOf(RpcError); + expect((e as RpcError).code).toBe(-32601); + expect((e as RpcError).message).toBe(ErrorCodeType.MethodNotFound); + } + try { + // @ts-ignore + await rpc.RpcService.incorrectMethode(input); + } catch (e) { + expect(e).toBeInstanceOf(RpcError); + expect((e as RpcError).code).toBe(-32601); + expect((e as RpcError).message).toBe(ErrorCodeType.MethodNotFound); + } + }); + + it('Should throw an error ' + ErrorCodeType.InvalidParams, async () => { + const input = 'llll'; + expect.assertions(3); + try { + // @ts-ignore + await rpc.RpcService.someMethode(input); + } catch (e) { + expect(e).toBeInstanceOf(RpcError); + expect((e as RpcError).code).toBe(-32602); + expect((e as RpcError).message).toBe(ErrorCodeType.InvalidParams); + } + }); + + it('Should throw an error ' + ErrorCodeType.ServerError, async () => { + const input = 5; + expect.assertions(4); + try { + await rpc.RpcService.someMethode(input); + } catch (e) { + expect(e).toBeInstanceOf(RpcError); + expect((e as RpcError).code).toBe(-32099); + expect((e as RpcError).message).toBe(ErrorCodeType.ServerError); + expect((e as RpcError).data.title).toBe('Custom Error'); + } + }); + }); +}); diff --git a/apps/json-api-server-e2e/src/json-api/utils/run-ppplication.ts b/apps/json-api-server-e2e/src/json-api/utils/run-application.ts similarity index 80% rename from apps/json-api-server-e2e/src/json-api/utils/run-ppplication.ts rename to apps/json-api-server-e2e/src/json-api/utils/run-application.ts index 8f1de12..4493a22 100644 --- a/apps/json-api-server-e2e/src/json-api/utils/run-ppplication.ts +++ b/apps/json-api-server-e2e/src/json-api/utils/run-application.ts @@ -7,13 +7,15 @@ import { RpcConfig, } from '@klerick/nestjs-json-rpc-sdk'; import { RpcService } from '@nestjs-json-api/type-for-rpc'; +import { TransportType } from '@klerick/nestjs-json-rpc-sdk'; import axios from 'axios'; import { Logger } from 'nestjs-pino'; +import { WebSocket } from 'ws'; import { AppModule } from '../../../../json-api-server/src/app/app.module'; import { JsonConfig } from '../../../../../libs/json-api/json-api-nestjs-sdk/src/lib/types'; -import { TransportType } from '@klerick/nestjs-json-rpc-sdk'; +import { WsAdapter } from '@nestjs/platform-ws'; export const axiosAdapter = adapterForAxios(axios); let saveApp: INestApplication; @@ -32,6 +34,7 @@ export const run = async () => { app.useLogger(app.get(Logger)); // const app = await NestFactory.create(AppModule); app.setGlobalPrefix(globalPrefix); + app.useWebSocketAdapter(new WsAdapter(app)); await app.init(); await app.listen(port); @@ -61,9 +64,21 @@ export const creatRpcSdk = (config: Partial = {}) => { ...config, rpcHost: `http://localhost:${port}`, - rpcPath: `${globalPrefix}/rpc`, + rpcPath: `/rpc`, transport: TransportType.HTTP, httpAgentFactory: axiosTransportFactory(axios), }, true ); + +export const creatWsRpcSdk = (config: Partial = {}) => + RpcFactory( + { + transport: TransportType.WS, + useWsNativeSocket: true, + webSocketCtor: WebSocket, + rpcHost: `http://localhost:${port}`, + rpcPath: `/rpc`, + }, + true + ); diff --git a/apps/json-api-server/src/app/rpc/rpc.module.ts b/apps/json-api-server/src/app/rpc/rpc.module.ts index d86d9d8..38cdfb3 100644 --- a/apps/json-api-server/src/app/rpc/rpc.module.ts +++ b/apps/json-api-server/src/app/rpc/rpc.module.ts @@ -1,6 +1,38 @@ -import { Module } from '@nestjs/common'; +import { Injectable, Module, ParseIntPipe, UsePipes } from '@nestjs/common'; import { NestjsJsonRpcModule, TransportType } from '@klerick/nestjs-json-rpc'; import { RpcService } from './service/rpc.service'; +import { + MessageBody, + SubscribeMessage, + WebSocketGateway, + WsResponse, +} from '@nestjs/websockets'; +import { from, Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { + ArgumentMetadata, + PipeTransform, +} from '@nestjs/common/interfaces/features/pipe-transform.interface'; + +@WebSocketGateway({ + cors: { + origin: '*', + }, + path: '/rpc/', +}) +class TesWebSocketService { + constructor() { + console.log(1213); + } + + @UsePipes(ParseIntPipe) + @SubscribeMessage('events') + findAll(@MessageBody() data: number): Observable> { + return from([1, 2, 3]).pipe( + map((item) => ({ event: 'events', data: item })) + ); + } +} @Module({ imports: [ @@ -8,6 +40,15 @@ import { RpcService } from './service/rpc.service'; path: 'rpc', transport: TransportType.HTTP, }), + NestjsJsonRpcModule.forRootAsync({ + transport: TransportType.WS, + wsConfig: { + path: '/rpc', + cors: { + origin: '*', + }, + }, + }), ], providers: [RpcService], }) diff --git a/apps/json-api-server/src/main.ts b/apps/json-api-server/src/main.ts index 9fbec19..4f76509 100644 --- a/apps/json-api-server/src/main.ts +++ b/apps/json-api-server/src/main.ts @@ -5,12 +5,14 @@ import { Logger } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import { WsAdapter } from '@nestjs/platform-ws'; import { AppModule } from './app/app.module'; -import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; async function bootstrap() { const app = await NestFactory.create(AppModule); + app.useWebSocketAdapter(new WsAdapter(app)); const globalPrefix = 'api'; app.setGlobalPrefix(globalPrefix); diff --git a/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/constans/index.ts b/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/constans/index.ts index af8cbdd..2b6782b 100644 --- a/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/constans/index.ts +++ b/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/constans/index.ts @@ -1 +1,2 @@ export const JSON_RPC_VERSION = '2.0'; +export const WS_EVENT_NAME = 'rpc'; diff --git a/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/factory/fetch-transport.factory.ts b/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/factory/fetch-transport.factory.ts new file mode 100644 index 0000000..2b5307e --- /dev/null +++ b/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/factory/fetch-transport.factory.ts @@ -0,0 +1,13 @@ +import { fromFetch } from 'rxjs/fetch'; +import { LoopFunc, PayloadRpc, RpcResult, Transport } from '../types'; + +export function fetchTransportFactory( + url: string +): Transport { + return (body: PayloadRpc) => + fromFetch>(url, { + method: 'post', + body: JSON.stringify(body), + selector: (r) => r.json(), + }); +} diff --git a/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/factory/index.ts b/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/factory/index.ts index da8d773..b44a48a 100644 --- a/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/factory/index.ts +++ b/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/factory/index.ts @@ -2,3 +2,5 @@ export * from './axios-transport.factory'; export * from './id-request'; export * from './rpc.factory'; export * from './transport.factory'; +export * from './fetch-transport.factory'; +export * from './io-transport.factory'; diff --git a/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/factory/io-transport.factory.ts b/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/factory/io-transport.factory.ts new file mode 100644 index 0000000..d64aeb3 --- /dev/null +++ b/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/factory/io-transport.factory.ts @@ -0,0 +1,30 @@ +import { filter, of, Subject, switchMap, take, tap } from 'rxjs'; +import type { Socket } from 'socket.io-client'; +import { LoopFunc, PayloadRpc, RpcResult, Transport } from '../types'; +import { WS_EVENT_NAME } from '../constans'; + +interface ServerToClientEvents { + rpc: (result: RpcResult) => void; +} + +interface ClientToServerEvents { + rpc: (payload: PayloadRpc) => void; +} + +export function ioTransportFactory( + io: Socket, ClientToServerEvents> +): Transport { + const subjectData = new Subject>(); + io.on(WS_EVENT_NAME, (event) => subjectData.next(event)); + + return (body: PayloadRpc) => { + const { id } = body; + return of(true).pipe( + tap(() => io.emit(WS_EVENT_NAME, body)), + switchMap(() => + subjectData.pipe(filter((response) => response.id === id)) + ), + take(1) + ); + }; +} diff --git a/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/factory/transport.factory.ts b/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/factory/transport.factory.ts index c8ef5a7..025a9e3 100644 --- a/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/factory/transport.factory.ts +++ b/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/factory/transport.factory.ts @@ -1,13 +1,14 @@ -import { fromFetch } from 'rxjs/fetch'; import { RpcConfig, Transport, TransportType, RpcHttpConfig, + RpcWsConfig, LoopFunc, - PayloadRpc, - RpcResult, } from '../types'; +import { fetchTransportFactory } from './fetch-transport.factory'; +import { wsTransportFactory } from './ws-transport.factory'; +import { ioTransportFactory } from './io-transport.factory'; function httpTransport( config: RpcHttpConfig @@ -17,12 +18,16 @@ function httpTransport( return config.httpAgentFactory(url); } - return (body: PayloadRpc) => - fromFetch>(url, { - method: 'post', - body: JSON.stringify(body), - selector: (r) => r.json(), - }); + return fetchTransportFactory(url); +} + +function wsTransport(config: RpcWsConfig): Transport { + if (config.useWsNativeSocket) { + const url = new URL(config.rpcPath, config.rpcHost).toString(); + return wsTransportFactory(url, config.webSocketCtor); + } + + return ioTransportFactory(config.webSocketCtor); } export function transportFactory( @@ -32,7 +37,7 @@ export function transportFactory( case TransportType.HTTP: return httpTransport(rpcConfig); case TransportType.WS: - throw new Error('Unknown transport'); + return wsTransport(rpcConfig); default: throw new Error('Unknown transport'); } diff --git a/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/factory/ws-transport.factory.ts b/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/factory/ws-transport.factory.ts new file mode 100644 index 0000000..15a1304 --- /dev/null +++ b/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/factory/ws-transport.factory.ts @@ -0,0 +1,46 @@ +import { filter, of, Subject, switchMap, take, tap } from 'rxjs'; +import { webSocket } from 'rxjs/webSocket'; +import { map } from 'rxjs/operators'; +import { WS_EVENT_NAME } from '../constans'; +import { LoopFunc, PayloadRpc, RpcResult, Transport, WsEvent } from '../types'; + +export interface WsResponse { + event: WsEvent; + data: T; +} + +export function wsTransportFactory( + url: string, + webSocketCtor?: any +): Transport { + const subject = webSocket | RpcResult>>({ + url, + ...(webSocketCtor ? { WebSocketCtor: webSocketCtor } : {}), + }); + const subjectData = new Subject>(); + subject + .pipe( + filter((response): response is WsResponse> => { + if (typeof response !== 'object' || response === null) return false; + return 'event' in response && response['event'] === 'rpc'; + }), + map((response) => response.data) + ) + .subscribe((r) => subjectData.next(r)); + + return (body: PayloadRpc) => { + const { id } = body; + return of(true).pipe( + tap(() => + subject.next({ + event: WS_EVENT_NAME, + data: body, + }) + ), + switchMap(() => + subjectData.pipe(filter((response) => response.id === id)) + ), + take(1) + ); + }; +} diff --git a/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/types/config.ts b/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/types/config.ts index 23e2d56..6d264df 100644 --- a/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/types/config.ts +++ b/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/types/config.ts @@ -1,26 +1,13 @@ -import { Observable } from 'rxjs'; import { Transport } from './rpc'; import { HttpAgentFactory, LoopFunc } from './utils'; +import type { Socket } from 'socket.io-client'; + export enum TransportType { HTTP, WS, } -// export type RpcHttpConfig = { -// rpcPath: string; -// rpcHost: string; -// transport: TransportType.HTTP; -// httpAgent?: (data: T) => Promise; -// }; - -// export type HttpConfig = { -// transport: TransportType.HTTP, -// rpcPath: string; -// rpcHost: string; -// httpAgent?: (data: T) => Promise; -// }; - export type RpcMainHttpConfig = { transport: TransportType.HTTP; rpcPath: string; @@ -33,11 +20,22 @@ export type RpcTransportHttpConfig = { export type RpcHttpConfig = RpcMainHttpConfig & RpcTransportHttpConfig; +type UseNativeSocket = + | { + useWsNativeSocket: true; + rpcPath: string; + rpcHost: string; + webSocketCtor?: { + new (url: string, protocols?: string | string[]): any; + }; + } + | { + useWsNativeSocket: false; + webSocketCtor: Socket; + }; + export type RpcWsConfig = { transport: TransportType.WS; - rpcPath: string; - rpcHost: string; - rpcPort: number; -}; +} & UseNativeSocket; export type RpcConfig = RpcHttpConfig | RpcWsConfig; diff --git a/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/types/rpc.ts b/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/types/rpc.ts index f16381a..23be2cc 100644 --- a/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/types/rpc.ts +++ b/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/types/rpc.ts @@ -4,6 +4,8 @@ import { LoopFunc, ReturnGenericType } from './utils'; export type JsonRpcVersion = '2.0'; +export type WsEvent = 'rpc'; + export type PayloadRpc = { jsonrpc: JsonRpcVersion; method: string; diff --git a/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/utils/wrapper-call.ts b/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/utils/wrapper-call.ts index ccfb738..5c3cc2d 100644 --- a/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/utils/wrapper-call.ts +++ b/libs/json-rpc/nestjs-json-rpc-sdk/src/lib/utils/wrapper-call.ts @@ -1,7 +1,7 @@ import { Observable } from 'rxjs'; import { LoopFunc, PayloadRpc, ReturnTransportCall, Transport } from '../types'; import { generateBody, generateBodyMethod } from './body'; -import { idRequest } from '../factory/id-request'; +import { idRequest } from '../factory'; import { parseResponse, throwRpcError } from './pipe'; export class WrapperCall extends Observable< diff --git a/libs/json-rpc/nestjs-json-rpc/src/lib/modules/http-transport/filter/rpc-error-exception.filter.spec.ts b/libs/json-rpc/nestjs-json-rpc/src/lib/modules/http-transport/filter/rpc-error-exception.filter.spec.ts index 8ddd631..e4b95de 100644 --- a/libs/json-rpc/nestjs-json-rpc/src/lib/modules/http-transport/filter/rpc-error-exception.filter.spec.ts +++ b/libs/json-rpc/nestjs-json-rpc/src/lib/modules/http-transport/filter/rpc-error-exception.filter.spec.ts @@ -1,15 +1,9 @@ import { ArgumentsHost } from '@nestjs/common'; import { RpcErrorExceptionFilter } from './rpc-error-exception.filter'; -import { - createError, - fromRpcErrorToRpcErrorObject, - RpcError, -} from '../../../utils'; +import { createError, fromRpcErrorToRpcErrorObject } from '../../../utils'; import { ErrorCodeType } from '../../../types'; -import * as ts from '@schematics/angular/third_party/github.com/Microsoft/TypeScript/lib/typescript'; -import Response = ts.server.protocol.Response; import { HttpArgumentsHost } from '@nestjs/common/interfaces'; describe('rpc-error-exception.filter', () => { diff --git a/libs/json-rpc/nestjs-json-rpc/src/lib/modules/http-transport/filter/rpc-error-exception.filter.ts b/libs/json-rpc/nestjs-json-rpc/src/lib/modules/http-transport/filter/rpc-error-exception.filter.ts index 16b204f..b9c5397 100644 --- a/libs/json-rpc/nestjs-json-rpc/src/lib/modules/http-transport/filter/rpc-error-exception.filter.ts +++ b/libs/json-rpc/nestjs-json-rpc/src/lib/modules/http-transport/filter/rpc-error-exception.filter.ts @@ -1,23 +1,10 @@ import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common'; -import { - RpcError, - fromRpcErrorToRpcErrorObject, - createError, -} from '../../../utils'; -import { RpcErrorObject, ErrorCodeType } from '../../../types'; +import { getBodyError } from '../../../utils'; @Catch() export class RpcErrorExceptionFilter implements ExceptionFilter { catch(exception: Error, host: ArgumentsHost): void { - let body: RpcErrorObject; - if (exception instanceof RpcError) { - body = fromRpcErrorToRpcErrorObject(exception); - } else { - body = fromRpcErrorToRpcErrorObject( - createError(ErrorCodeType.ServerError, exception.message) - ); - } - host.switchToHttp().getResponse().send(body); + host.switchToHttp().getResponse().send(getBodyError(exception)); } } diff --git a/libs/json-rpc/nestjs-json-rpc/src/lib/modules/index.ts b/libs/json-rpc/nestjs-json-rpc/src/lib/modules/index.ts index e720cd3..d176f45 100644 --- a/libs/json-rpc/nestjs-json-rpc/src/lib/modules/index.ts +++ b/libs/json-rpc/nestjs-json-rpc/src/lib/modules/index.ts @@ -1,2 +1,3 @@ export * from './http-transport/http-transport.module'; export * from './util/util.module'; +export * from './ws-socket-transport/ws-socket-transport.module'; diff --git a/libs/json-rpc/nestjs-json-rpc/src/lib/modules/ws-socket-transport/constants/index.ts b/libs/json-rpc/nestjs-json-rpc/src/lib/modules/ws-socket-transport/constants/index.ts new file mode 100644 index 0000000..d2578e0 --- /dev/null +++ b/libs/json-rpc/nestjs-json-rpc/src/lib/modules/ws-socket-transport/constants/index.ts @@ -0,0 +1 @@ +export const WS_EVENT_NAME = 'rpc'; diff --git a/libs/json-rpc/nestjs-json-rpc/src/lib/modules/ws-socket-transport/factory/create-gateway.factory.ts b/libs/json-rpc/nestjs-json-rpc/src/lib/modules/ws-socket-transport/factory/create-gateway.factory.ts new file mode 100644 index 0000000..d693648 --- /dev/null +++ b/libs/json-rpc/nestjs-json-rpc/src/lib/modules/ws-socket-transport/factory/create-gateway.factory.ts @@ -0,0 +1,12 @@ +import { WebSocketGatewayService } from '../service'; +import { GatewayMetadata } from '@nestjs/websockets/interfaces'; +import { WebSocketGateway } from '@nestjs/websockets'; +import { Type } from '@nestjs/common'; + +export function createGatewayFactory( + service: Type, + config: GatewayMetadata +): Type { + WebSocketGateway(config)(service); + return service; +} diff --git a/libs/json-rpc/nestjs-json-rpc/src/lib/modules/ws-socket-transport/factory/index.ts b/libs/json-rpc/nestjs-json-rpc/src/lib/modules/ws-socket-transport/factory/index.ts new file mode 100644 index 0000000..c568c11 --- /dev/null +++ b/libs/json-rpc/nestjs-json-rpc/src/lib/modules/ws-socket-transport/factory/index.ts @@ -0,0 +1 @@ +export * from './create-gateway.factory'; diff --git a/libs/json-rpc/nestjs-json-rpc/src/lib/modules/ws-socket-transport/filter/rpc-ws-error-exception.filter.spec.ts b/libs/json-rpc/nestjs-json-rpc/src/lib/modules/ws-socket-transport/filter/rpc-ws-error-exception.filter.spec.ts new file mode 100644 index 0000000..485a61e --- /dev/null +++ b/libs/json-rpc/nestjs-json-rpc/src/lib/modules/ws-socket-transport/filter/rpc-ws-error-exception.filter.spec.ts @@ -0,0 +1,115 @@ +import { ArgumentsHost } from '@nestjs/common'; +import { WsArgumentsHost } from '@nestjs/common/interfaces/features/arguments-host.interface'; +import { WebSocket } from 'ws'; +import { Socket } from 'socket.io'; + +import { RpcWsErrorExceptionFilter } from './rpc-ws-error-exception.filter'; +import { createError, fromRpcErrorToRpcErrorObject } from '../../../utils'; +import { ErrorCodeType } from '../../../types'; +import { WS_EVENT_NAME } from '../constants'; + +describe('rpc-ws-error-exception.filter', () => { + describe('WebSocket', () => { + const WebSocketInst = new WebSocket('http://0.0.0.0', {}); + let argumentsHost: ArgumentsHost; + let getClient: () => WebSocket; + + beforeEach(() => { + getClient = () => WebSocketInst; + argumentsHost = { + switchToWs(): WsArgumentsHost { + return { + getClient, + } as any; + }, + } as any; + }); + + it('should catch RpcError and transform it to RpcErrorObject', () => { + const filter = new RpcWsErrorExceptionFilter(); + const exception = createError( + ErrorCodeType.InvalidRequest, + 'InvalidRequest' + ); + + const spySend = jest.spyOn(WebSocketInst, 'send').mockImplementation(); + filter.catch(exception, argumentsHost); + expect(spySend).toHaveBeenCalledWith( + JSON.stringify({ + event: WS_EVENT_NAME, + data: fromRpcErrorToRpcErrorObject(exception), + }) + ); + }); + + it('should catch Error and transform it to RpcErrorObject', () => { + const filter = new RpcWsErrorExceptionFilter(); + const exception = new Error('Test Error'); + const spySend = jest.spyOn(WebSocketInst, 'send').mockImplementation(); + filter.catch(exception, argumentsHost); + expect(spySend).toHaveBeenCalledWith( + JSON.stringify({ + event: WS_EVENT_NAME, + data: fromRpcErrorToRpcErrorObject( + createError(ErrorCodeType.ServerError, exception.message) + ), + }) + ); + }); + }); + + describe('socket.io', () => { + // @ts-ignore + const WebSocketInst = new Socket( + { + server: { _opts: {} }, + }, + { + conn: { + protocol: 1, + }, + } + ); + let argumentsHost: ArgumentsHost; + let getClient: () => Socket; + + beforeEach(() => { + getClient = () => WebSocketInst; + argumentsHost = { + switchToWs(): WsArgumentsHost { + return { + getClient, + } as any; + }, + } as any; + }); + + it('should catch RpcError and transform it to RpcErrorObject', () => { + const filter = new RpcWsErrorExceptionFilter(); + const exception = createError( + ErrorCodeType.InvalidRequest, + 'InvalidRequest' + ); + + const spySend = jest.spyOn(WebSocketInst, 'emit').mockImplementation(); + filter.catch(exception, argumentsHost); + expect(spySend).toHaveBeenCalledWith( + WS_EVENT_NAME, + fromRpcErrorToRpcErrorObject(exception) + ); + }); + + it('should catch Error and transform it to RpcErrorObject', () => { + const filter = new RpcWsErrorExceptionFilter(); + const exception = new Error('Test Error'); + const spySend = jest.spyOn(WebSocketInst, 'emit').mockImplementation(); + filter.catch(exception, argumentsHost); + expect(spySend).toHaveBeenCalledWith( + WS_EVENT_NAME, + fromRpcErrorToRpcErrorObject( + createError(ErrorCodeType.ServerError, exception.message) + ) + ); + }); + }); +}); diff --git a/libs/json-rpc/nestjs-json-rpc/src/lib/modules/ws-socket-transport/filter/rpc-ws-error-exception.filter.ts b/libs/json-rpc/nestjs-json-rpc/src/lib/modules/ws-socket-transport/filter/rpc-ws-error-exception.filter.ts new file mode 100644 index 0000000..7ea2be7 --- /dev/null +++ b/libs/json-rpc/nestjs-json-rpc/src/lib/modules/ws-socket-transport/filter/rpc-ws-error-exception.filter.ts @@ -0,0 +1,19 @@ +import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common'; +import { WebSocket } from 'ws'; +import { Socket } from 'socket.io'; + +import { getBodyError } from '../../../utils'; +import { WS_EVENT_NAME } from '../constants'; + +@Catch() +export class RpcWsErrorExceptionFilter implements ExceptionFilter { + catch(exception: Error, host: ArgumentsHost): void { + const body = getBodyError(exception); + const client = host.switchToWs().getClient(); + if (client instanceof WebSocket) { + client.send(JSON.stringify({ event: WS_EVENT_NAME, data: body })); + } else { + client.emit(WS_EVENT_NAME, body); + } + } +} diff --git a/libs/json-rpc/nestjs-json-rpc/src/lib/modules/ws-socket-transport/service/index.ts b/libs/json-rpc/nestjs-json-rpc/src/lib/modules/ws-socket-transport/service/index.ts new file mode 100644 index 0000000..128b13b --- /dev/null +++ b/libs/json-rpc/nestjs-json-rpc/src/lib/modules/ws-socket-transport/service/index.ts @@ -0,0 +1 @@ +export * from './web-socket-gateway.service'; diff --git a/libs/json-rpc/nestjs-json-rpc/src/lib/modules/ws-socket-transport/service/web-socket-gateway.service.ts b/libs/json-rpc/nestjs-json-rpc/src/lib/modules/ws-socket-transport/service/web-socket-gateway.service.ts new file mode 100644 index 0000000..5b675a2 --- /dev/null +++ b/libs/json-rpc/nestjs-json-rpc/src/lib/modules/ws-socket-transport/service/web-socket-gateway.service.ts @@ -0,0 +1,29 @@ +import { + MessageBody, + SubscribeMessage, + WebSocketGateway, + WsResponse, +} from '@nestjs/websockets'; +import { Inject, UseFilters, UsePipes } from '@nestjs/common'; +import { HandlerService } from '../../util/service'; +import { PayloadRpcData, RpcErrorObject, RpcResult } from '../../../types'; +import { InputDataPipe } from '../../util/pipe/input-data.pipe'; +import { WS_EVENT_NAME } from '../constants'; +import { RpcWsErrorExceptionFilter } from '../filter/rpc-ws-error-exception.filter'; + +type WsRpcResponse = WsResponse< + RpcResult | RpcErrorObject | Array +>; + +@WebSocketGateway() +export class WebSocketGatewayService { + @Inject(HandlerService) private readonly handlerService!: HandlerService; + + @UsePipes(InputDataPipe) + @UseFilters(new RpcWsErrorExceptionFilter()) + @SubscribeMessage(WS_EVENT_NAME) + async run(@MessageBody() body: PayloadRpcData): Promise { + const result = await this.handlerService.runRpc(body); + return { data: result, event: WS_EVENT_NAME }; + } +} diff --git a/libs/json-rpc/nestjs-json-rpc/src/lib/modules/ws-socket-transport/ws-socket-transport.module.ts b/libs/json-rpc/nestjs-json-rpc/src/lib/modules/ws-socket-transport/ws-socket-transport.module.ts new file mode 100644 index 0000000..4d3c669 --- /dev/null +++ b/libs/json-rpc/nestjs-json-rpc/src/lib/modules/ws-socket-transport/ws-socket-transport.module.ts @@ -0,0 +1,26 @@ +import { DynamicModule, Provider } from '@nestjs/common'; +import { Type } from '@nestjs/common/interfaces/type.interface'; +import { ForwardReference } from '@nestjs/common/interfaces/modules/forward-reference.interface'; +import { GatewayMetadata } from '@nestjs/websockets/interfaces'; + +import { WebSocketGatewayService } from './service'; +import { createGatewayFactory } from './factory'; + +export class WsSocketTransportModule { + static forRoot( + wsConfig: GatewayMetadata, + providers: Provider[], + imports: Array< + Type | DynamicModule | Promise | ForwardReference + > = [] + ): DynamicModule { + return { + module: WsSocketTransportModule, + providers: [ + ...providers, + createGatewayFactory(WebSocketGatewayService, wsConfig), + ], + imports, + }; + } +} diff --git a/libs/json-rpc/nestjs-json-rpc/src/lib/nestjs-json-rpc.module.ts b/libs/json-rpc/nestjs-json-rpc/src/lib/nestjs-json-rpc.module.ts index e935678..1a30a93 100644 --- a/libs/json-rpc/nestjs-json-rpc/src/lib/nestjs-json-rpc.module.ts +++ b/libs/json-rpc/nestjs-json-rpc/src/lib/nestjs-json-rpc.module.ts @@ -1,7 +1,11 @@ import { DynamicModule, Module, Provider } from '@nestjs/common'; import { RouterModule } from '@nestjs/core'; import { JsonRpcConfig, TransportType } from './types'; -import { HttpTransportModule, UtilModule } from './modules'; +import { + HttpTransportModule, + UtilModule, + WsSocketTransportModule, +} from './modules'; @Module({ controllers: [], @@ -11,7 +15,7 @@ import { HttpTransportModule, UtilModule } from './modules'; export class NestjsJsonRpcModule { static forRootAsync(options: JsonRpcConfig): DynamicModule { const providers: Provider[] = []; - + const { transport } = options; switch (options.transport) { case TransportType.HTTP: { const httpModule = HttpTransportModule.forRoot(providers, [UtilModule]); @@ -30,8 +34,21 @@ export class NestjsJsonRpcModule { exports: [httpModule], }; } - default: - throw new Error(`Transport ${options.transport} not implement`); + case TransportType.WS: { + const wsModule = WsSocketTransportModule.forRoot( + options.wsConfig, + providers, + [UtilModule] + ); + return { + module: NestjsJsonRpcModule, + imports: [...(options.imports || []), wsModule], + exports: [wsModule], + }; + } + default: { + throw new Error(`Transport ${transport} not implement`); + } } } } diff --git a/libs/json-rpc/nestjs-json-rpc/src/lib/types/module-options.ts b/libs/json-rpc/nestjs-json-rpc/src/lib/types/module-options.ts index 86fc93b..7af5e65 100644 --- a/libs/json-rpc/nestjs-json-rpc/src/lib/types/module-options.ts +++ b/libs/json-rpc/nestjs-json-rpc/src/lib/types/module-options.ts @@ -4,6 +4,7 @@ import { Provider, Type, } from '@nestjs/common'; +import { GatewayMetadata } from '@nestjs/websockets/interfaces'; export enum TransportType { HTTP, @@ -15,6 +16,11 @@ export type HttpTransportConfig = { path: string; }; +export type WSTransportConfig = { + transport: TransportType.WS; + wsConfig: GatewayMetadata; +}; + export type CommonRpcConfig = { providers?: Provider[]; imports?: Array< @@ -22,4 +28,7 @@ export type CommonRpcConfig = { >; }; -export type JsonRpcConfig = CommonRpcConfig & HttpTransportConfig; +export type JsonRpcHttpConfig = CommonRpcConfig & HttpTransportConfig; +export type JsonRpcWsConfig = CommonRpcConfig & WSTransportConfig; + +export type JsonRpcConfig = JsonRpcHttpConfig | JsonRpcWsConfig; diff --git a/libs/json-rpc/nestjs-json-rpc/src/lib/utils/error.ts b/libs/json-rpc/nestjs-json-rpc/src/lib/utils/error.ts index e107500..56a4b3e 100644 --- a/libs/json-rpc/nestjs-json-rpc/src/lib/utils/error.ts +++ b/libs/json-rpc/nestjs-json-rpc/src/lib/utils/error.ts @@ -1,5 +1,5 @@ import { ErrorCode } from '../constants'; -import { RpcErrorData, RpcErrorObject } from '../types/error-payloade'; +import { RpcErrorData, RpcErrorObject } from '../types'; import { ErrorCodeType } from '../types'; export class RpcError extends Error { @@ -67,3 +67,12 @@ export function fromRpcErrorToRpcErrorObject( id: error.id ? error.id : id, }; } + +export function getBodyError(exception: Error): RpcErrorObject { + if (exception instanceof RpcError) { + return fromRpcErrorToRpcErrorObject(exception); + } + return fromRpcErrorToRpcErrorObject( + createError(ErrorCodeType.ServerError, exception.message) + ); +} diff --git a/package-lock.json b/package-lock.json index 7cdded5..dbb7cb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,14 +22,18 @@ "@nestjs/common": "^10.3.0", "@nestjs/core": "^10.3.0", "@nestjs/platform-express": "10.3.3", + "@nestjs/platform-socket.io": "^10.3.7", + "@nestjs/platform-ws": "^10.3.7", "@nestjs/swagger": "^7.3.0", "@nestjs/typeorm": "^10.0.2", + "@nestjs/websockets": "^10.3.7", "axios": "1.6.7", "nestjs-pino": "4.0.0", "pg": "8.11.3", "pino-http": "9.0.0", "reflect-metadata": "0.2.1", "rxjs": "^7.8.0", + "socket.io-client": "^4.7.5", "tslib": "^2.3.0", "typeorm": "^0.3.20", "uuid": "^9.0.1", @@ -4810,6 +4814,42 @@ "@nestjs/core": "^10.0.0" } }, + "node_modules/@nestjs/platform-socket.io": { + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.3.7.tgz", + "integrity": "sha512-T9VbVgEUnbid/RiywN9/8YQ8pAGDP++0nX73l4kIWeDWkz5DEh4aLB7O/JvLA3/xRHdjTZ4RiRZazwqSWi1Sog==", + "dependencies": { + "socket.io": "4.7.5", + "tslib": "2.6.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/websockets": "^10.0.0", + "rxjs": "^7.1.0" + } + }, + "node_modules/@nestjs/platform-ws": { + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/platform-ws/-/platform-ws-10.3.7.tgz", + "integrity": "sha512-lOvZ8u5UdL0FgAOdWosDXefVgDikPd4j5el81emkx+H0pFsysfHXSSoSvMLQijdhENqHSl3buRhO5n3M3uia1w==", + "dependencies": { + "tslib": "2.6.2", + "ws": "8.16.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/websockets": "^10.0.0", + "rxjs": "^7.1.0" + } + }, "node_modules/@nestjs/schematics": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.1.1.tgz", @@ -4981,6 +5021,36 @@ "typeorm": "^0.3.0" } }, + "node_modules/@nestjs/websockets": { + "version": "10.3.7", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.3.7.tgz", + "integrity": "sha512-iYdsWiRNPUy0XzPoW44bx2MW1griuraTr5fNhoe2rUSNO0mEW1aeXp4v56KeZDLAss31WbeckC5P3N223Fys5g==", + "dependencies": { + "iterare": "1.2.1", + "object-hash": "3.0.0", + "tslib": "2.6.2" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/platform-socket.io": "^10.0.0", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "@nestjs/platform-socket.io": { + "optional": true + } + } + }, + "node_modules/@nestjs/websockets/node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" + } + }, "node_modules/@ngtools/webpack": { "version": "17.2.1", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-17.2.1.tgz", @@ -7261,6 +7331,11 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, "node_modules/@sqltools/formatter": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", @@ -7677,6 +7752,19 @@ "@types/node": "*" } }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/eslint": { "version": "8.56.3", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.3.tgz", @@ -7829,8 +7917,7 @@ "node_modules/@types/node": { "version": "18.16.20", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.20.tgz", - "integrity": "sha512-nL54VfDjThdP2UXJXZao5wp76CDiDw4zSRO8d4Tk7UgDqNKGKVEQB0/t3ti63NS+YNNkIQDvwEAF04BO+WYu7Q==", - "devOptional": true + "integrity": "sha512-nL54VfDjThdP2UXJXZao5wp76CDiDw4zSRO8d4Tk7UgDqNKGKVEQB0/t3ti63NS+YNNkIQDvwEAF04BO+WYu7Q==" }, "node_modules/@types/node-forge": { "version": "1.3.11", @@ -10064,6 +10151,14 @@ } ] }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -12213,6 +12308,94 @@ "once": "^1.4.0" } }, + "node_modules/engine.io": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", + "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "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" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz", + "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==", + "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" + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "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 + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "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 + } + } + }, "node_modules/enhanced-resolve": { "version": "5.15.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", @@ -21607,6 +21790,78 @@ "npm": ">= 3.0.0" } }, + "node_modules/socket.io": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", + "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "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" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz", + "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "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 + } + } + }, + "node_modules/socket.io-client": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz", + "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -24465,7 +24720,6 @@ "version": "8.16.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", - "dev": true, "engines": { "node": ">=10.0.0" }, @@ -24497,6 +24751,14 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 42bcf34..a5fb64e 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "typeorm:run": "npm run typeorm migration:run", "typeorm:revert": "npm run typeorm migration:revert", "seed:run": "ts-node -r tsconfig-paths/register -r dotenv/config --project libs/database/tsconfig.lib.json ./node_modules/@jorgebodega/typeorm-seeding/dist/cli.js -d libs/database/src/lib/config-cli.ts seed libs/database/src/lib/seeders/root.seeder.ts", - "demo:json-api": "nx run json-api-server:serve:development" + "demo:json-api": "nx run json-api-server:serve:development", + "demo:json-api-front": "nx run json-api-front:serve:development" }, "private": true, "dependencies": { @@ -24,14 +25,18 @@ "@nestjs/common": "^10.3.0", "@nestjs/core": "^10.3.0", "@nestjs/platform-express": "10.3.3", + "@nestjs/platform-socket.io": "^10.3.7", + "@nestjs/platform-ws": "^10.3.7", "@nestjs/swagger": "^7.3.0", "@nestjs/typeorm": "^10.0.2", + "@nestjs/websockets": "^10.3.7", "axios": "1.6.7", "nestjs-pino": "4.0.0", "pg": "8.11.3", "pino-http": "9.0.0", "reflect-metadata": "0.2.1", "rxjs": "^7.8.0", + "socket.io-client": "^4.7.5", "tslib": "^2.3.0", "typeorm": "^0.3.20", "uuid": "^9.0.1",