Skip to content

Commit

Permalink
feat(nestjs-json-rpc,nestjs-json-rpc-sdk): add ws transport
Browse files Browse the repository at this point in the history
Add WebSocket transport for backend
Add Websocket transport for sdk,
  • Loading branch information
klerick committed Apr 3, 2024
1 parent 1532cf9 commit 31450b3
Show file tree
Hide file tree
Showing 39 changed files with 842 additions and 86 deletions.
5 changes: 5 additions & 0 deletions apps/json-api-front/proxy.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,10 @@
"/api": {
"target": "http://localhost:3000",
"secure": false
},
"/rpc": {
"target": "http://localhost:3000",
"secure": false,
"ws": true
}
}
24 changes: 13 additions & 11 deletions apps/json-api-front/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<number>;
test2(firstArg: string, secondArg: number): Promise<string>;
}

type RpcMap = {
TestRpc: TestRpc;
RpcService: IRpcService;
};

@Component({
Expand All @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 6 additions & 2 deletions apps/json-api-front/src/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand All @@ -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' }),
})
),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
102 changes: 102 additions & 0 deletions apps/json-api-server-e2e/src/json-api/json-rpc/run-ws-json-rpc.spec.ts
Original file line number Diff line number Diff line change
@@ -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<MapperRpc>['rpc'];
let rpcBatch: ResultRpcFactoryPromise<MapperRpc>['rpcBatch'];
let rpcForBatch: ResultRpcFactoryPromise<MapperRpc>['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');
}
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);

Expand Down Expand Up @@ -61,9 +64,21 @@ export const creatRpcSdk = (config: Partial<RpcConfig> = {}) =>
{
...config,
rpcHost: `http://localhost:${port}`,
rpcPath: `${globalPrefix}/rpc`,
rpcPath: `/rpc`,
transport: TransportType.HTTP,
httpAgentFactory: axiosTransportFactory(axios),
},
true
);

export const creatWsRpcSdk = (config: Partial<RpcConfig> = {}) =>
RpcFactory<MapperRpc>(
{
transport: TransportType.WS,
useWsNativeSocket: true,
webSocketCtor: WebSocket,
rpcHost: `http://localhost:${port}`,
rpcPath: `/rpc`,
},
true
);
43 changes: 42 additions & 1 deletion apps/json-api-server/src/app/rpc/rpc.module.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,54 @@
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<WsResponse<number>> {
return from([1, 2, 3]).pipe(
map((item) => ({ event: 'events', data: item }))
);
}
}

@Module({
imports: [
NestjsJsonRpcModule.forRootAsync({
path: 'rpc',
transport: TransportType.HTTP,
}),
NestjsJsonRpcModule.forRootAsync({
transport: TransportType.WS,
wsConfig: {
path: '/rpc',
cors: {
origin: '*',
},
},
}),
],
providers: [RpcService],
})
Expand Down
4 changes: 3 additions & 1 deletion apps/json-api-server/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const JSON_RPC_VERSION = '2.0';
export const WS_EVENT_NAME = 'rpc';
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { fromFetch } from 'rxjs/fetch';
import { LoopFunc, PayloadRpc, RpcResult, Transport } from '../types';

export function fetchTransportFactory<T extends LoopFunc>(
url: string
): Transport<T> {
return (body: PayloadRpc<T>) =>
fromFetch<RpcResult<T>>(url, {
method: 'post',
body: JSON.stringify(body),
selector: (r) => r.json(),
});
}
2 changes: 2 additions & 0 deletions libs/json-rpc/nestjs-json-rpc-sdk/src/lib/factory/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Loading

0 comments on commit 31450b3

Please sign in to comment.