Skip to content

Commit

Permalink
feat: implement swith method when emit ws
Browse files Browse the repository at this point in the history
  • Loading branch information
Sotatek-JohnnyNguyen committed Apr 25, 2024
1 parent 48b4744 commit f7a0ed8
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 74 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"console:dev": "ts-node -r tsconfig-paths/register src/console.ts",
"console": "node dist/console.js",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
Expand Down
71 changes: 14 additions & 57 deletions src/communicates/__tests__/communicate.gateway.spec.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,15 @@
import { INestApplication, Logger } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { CommunicateGateway } from '../communicate.gateway';
import { Socket, io } from 'socket.io-client';
import { CommunicateService } from '../communicate.service';
import { ConfigModule, ConfigService } from '@nestjs/config';
import configuration from '../../config/configuration';

async function createNestApp(...gateways: any): Promise<INestApplication> {
const testingModule = await Test.createTestingModule({
imports: [ConfigModule.forRoot({ load: [configuration] })],
providers: gateways,
}).compile();
testingModule.useLogger(new Logger());

return testingModule.createNestApplication();
}

describe('Communicate gateway', () => {
let gateway: CommunicateGateway;
let app: INestApplication;
let ioClient: Socket;

beforeAll(async () => {
app = await createNestApp(
CommunicateGateway,
CommunicateService,
ConfigService,
);
gateway = app.get<CommunicateGateway>(CommunicateGateway);
app.listen(3000);
});

beforeEach(async () => {
ioClient = io('http://localhost:3000', {
ioClient = io('http://localhost:3001', {
autoConnect: false,
transports: ['websocket', 'pooling'],
});
});

afterAll(async () => {
await app.close();
});

it(`Should be defined`, () => {
expect(gateway).toBeDefined();
});

it(`Should emit "pong" on "ping"`, async () => {
ioClient.connect();
ioClient.emit('ping', 'Hello World!');
Expand All @@ -62,33 +26,26 @@ describe('Communicate gateway', () => {
ioClient.disconnect();
});

it(`Should emit "executed" on "execute"`, async () => {
ioClient.connect();
ioClient.emit('execute', { method: 'eth_chainId', data: '' });
await new Promise<void>((resolve) => {
ioClient.on('connect', () => {
console.log('Connected');
});
ioClient.on('executed', (data) => {
expect(data.method).toBe('eth_chainId');
expect(data.response).toBe('');
resolve();
});
});

ioClient.disconnect();
});

it(`Should throw "method not allowed" on "execute"`, async () => {
it('body is JsonrpcArray', async () => {
const body = {
jsonrpc: '2.0',
method: 'eth_sendRawTransaction',
params: [
'0xf8620180825208948626f6940e2eb28930efb4cef49b2d1f2c9c11998080831e84a2a06c33b39c89e987ad08bc2cab79243dbb2a44955d2539d4f5d58001ae9ab0a2caa06943316733bd0fd81a0630a9876f6f07db970b93f367427404aabd0621ea5ec1',
],
id: 1,
};
ioClient.connect();
ioClient.emit('execute', { method: 'bnb_chainId', data: '' });
ioClient.emit('execute', { method: 'bnb_chainId', data: body });
await new Promise<void>((resolve) => {
ioClient.on('connect', () => {
console.log('Connected');
});
ioClient.on('executed', (data) => {
expect(data.method).toBe('bnb_chainId');
expect(data.response).toBe('Method bnb_chainId is not allowed');
expect(data.response.error.message).toBe(
'Method bnb_chainId is not allowed',
);
resolve();
});
});
Expand Down
14 changes: 10 additions & 4 deletions src/communicates/communicate.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import { Logger } from '@nestjs/common';
import { Server, Socket } from 'socket.io';
import { CommunicateService } from './communicate.service';
import { JsonrpcRequestBody } from 'src/entities';

@WebSocketGateway()
export class CommunicateGateway
Expand Down Expand Up @@ -43,16 +44,21 @@ export class CommunicateGateway
}

@SubscribeMessage('execute')
async executeCommand(client: Socket, data: { method: string; data: string }) {
async executeCommand(client: Socket, data: JsonrpcRequestBody) {
this.logger.log(`Message execute received from client id: ${client.id}`);
this.logger.debug(`Payload: ${data}`);

const requestContext = {
ip: client.conn.remoteAddress,
headers: client.handshake.headers,
};
const response = await this.communicateService.sendRequest(
data.method,
data.data,
requestContext,
data,
);
return {
event: 'executed',
data: { method: data.method, response: response },
data: { method: data.method, response: response.data },
};
}
}
85 changes: 72 additions & 13 deletions src/communicates/communicate.service.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,91 @@
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { WebsocketError } from '../entities';
import { JsonrpcError, JsonrpcRequestBody, RequestContext } from '../entities';
import { VerseService, TransactionService, ProxyService } from 'src/services';

@Injectable()
export class CommunicateService {
private allowedMethods: RegExp[];
private isUseBlockNumberCache: boolean;

constructor(private readonly configService: ConfigService) {
constructor(
private readonly configService: ConfigService,
private verseService: VerseService,
private txService: TransactionService,
private proxyService: ProxyService,
) {
this.isUseBlockNumberCache = !!this.configService.get<number>(
'blockNumberCacheExpire',
);
this.allowedMethods = this.configService.get<RegExp[]>(
'allowedMethods',
) ?? [/^.*$/];
}

async sendRequest(method: string, data: string): Promise<string> {
const err = this.checkMethod(method);
if (err) {
return err;
}
return data;
async sendRequest(requestContext: RequestContext, body: JsonrpcRequestBody) {
const isUseReadNode = !!this.configService.get<string>('verseReadNodeUrl');
const result = await this.send(isUseReadNode, requestContext, body);

return result;
}
checkMethod(method: string): string | null {

checkMethod(method: string) {
const checkMethod = this.allowedMethods.some((allowedMethod) => {
return allowedMethod.test(method);
});
if (!checkMethod) {
return `Method ${method} is not allowed`;
} else {
return null;
if (!checkMethod)
throw new JsonrpcError(`Method ${method} is not allowed`, -32601);
}

async send(
isUseReadNode: boolean,
requestContext: RequestContext,
body: JsonrpcRequestBody,
) {
try {
const method = body.method;
const { headers } = requestContext;
this.checkMethod(method);

if (method === 'eth_sendRawTransaction') {
return await this.proxyService.sendTransaction(requestContext, body);
} else if (method === 'eth_estimateGas') {
return await this.verseService.postVerseMasterNode(headers, body);
} else if (method === 'eth_blockNumber' && this.isUseBlockNumberCache) {
return await this.txService.getBlockNumberCacheRes(
requestContext,
body.jsonrpc,
body.id,
);
}

if (isUseReadNode) {
return await this.verseService.postVerseReadNode(headers, body);
} else {
return await this.verseService.postVerseMasterNode(headers, body);
}
} catch (err) {
const status = 200;
if (err instanceof JsonrpcError) {
const data = {
jsonrpc: body.jsonrpc,
id: body.id,
error: {
code: err.code,
message: err.message,
},
};
console.error(err.message);
return {
status,
data,
};
}
console.error(err);
return {
status,
data: err,
};
}
}
}
1 change: 1 addition & 0 deletions src/config/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ export default () => ({
/^eth_.*Filter$/,
],
inheritHostHeader: true,
wsPort: process.env.WS_PORT,
});
12 changes: 12 additions & 0 deletions src/console.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { NestFactory } from '@nestjs/core';
import { EventsModule } from './events.module';
import { ConfigService } from '@nestjs/config';

async function bootstrap() {
const app = await NestFactory.create(EventsModule);
const configService = app.get(ConfigService);
const wsPort = configService.get('wsPort');
await app.listen(wsPort);
}

bootstrap();
37 changes: 37 additions & 0 deletions src/events.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { CacheModule, Module } from '@nestjs/common';
import { CommunicateGateway } from './communicates/communicate.gateway';
import {
AllowCheckService,
ProxyService,
RateLimitService,
TransactionService,
TypeCheckService,
VerseService,
} from './services';
import { CommunicateService } from './communicates/communicate.service';
import { ConfigModule } from '@nestjs/config';
import { HttpModule } from '@nestjs/axios';
import configuration from './config/configuration';
import { DatastoreService } from './repositories';

@Module({
imports: [
ConfigModule.forRoot({
load: [configuration],
}),
HttpModule,
CacheModule.register(),
],
providers: [
CommunicateGateway,
CommunicateService,
VerseService,
AllowCheckService,
TransactionService,
ProxyService,
TypeCheckService,
DatastoreService,
RateLimitService,
],
})
export class EventsModule {}

0 comments on commit f7a0ed8

Please sign in to comment.