Skip to content

Commit 9de8c96

Browse files
committed
feat(ws): implements parser for websocket
The WsParser is now written in its entirity. There are a few hardcoded values, though those could be overriden by dev if they choose by extending the parser itself. Tests are written (P.S. writing tests to satisfy Socket.io **and** Websocket was almost impossible, but I got it done). Only thing to do from here is to finish off the microservice parsers 😄 fix #24
1 parent 7ec49bc commit 9de8c96

File tree

8 files changed

+137
-26
lines changed

8 files changed

+137
-26
lines changed

integration/src/ws/ws.filter.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import { ArgumentsHost, Catch, HttpException } from '@nestjs/common';
22
import { BaseWsExceptionFilter } from '@nestjs/websockets';
3+
import WebSocket = require('ws');
34

45
@Catch()
56
export class ExceptionFilter extends BaseWsExceptionFilter {
67
catch(exception: HttpException, host: ArgumentsHost) {
78
const client = host.switchToWs().getClient();
8-
client.emit('exception', exception.message);
9+
if (client instanceof WebSocket) {
10+
return client.send('exception');
11+
} else {
12+
return client.emit('exception', exception.message);
13+
}
914
}
1015
}

integration/test/utils/ws-promise.ts

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,53 @@
11
import * as WebSocket from 'ws';
22

3+
export const createConnection = (
4+
client: (url: string) => SocketIOClient.Socket | WebSocket,
5+
url: string,
6+
): Promise<SocketIOClient.Socket | WebSocket> =>
7+
new Promise((resolve, reject) => {
8+
const socket = client(url);
9+
if (Object.getOwnPropertyDescriptor(socket, 'io')) {
10+
resolve(socket);
11+
}
12+
socket.on('open', () => {
13+
resolve(socket);
14+
});
15+
socket.on('error', (err) => {
16+
reject(err);
17+
});
18+
});
19+
320
export const wsPromise = (
421
ws: WebSocket | SocketIOClient.Socket,
522
message: string,
23+
sendMethod: 'send' | 'emit',
624
): Promise<any> =>
725
new Promise((resolve, reject) => {
8-
ws.emit(message, '', (data) => resolve(data));
9-
ws.on('error', (err) => {
10-
console.error(err);
11-
reject(err);
26+
ws[sendMethod](message, {}, (data: any) => {
27+
if (data) {
28+
resolve(data);
29+
}
1230
});
1331
ws.on('message', (data) => {
14-
console.log(`Got data ${data}`);
1532
resolve(data);
33+
return false;
1634
});
17-
ws.on('ping', () => {
18-
resolve('ping');
19-
});
20-
ws.on('pong', () => {
21-
resolve('pong');
35+
ws.on('error', (err) => {
36+
console.error(err);
37+
reject(err);
2238
});
2339
ws.on('exception' as any, (...args) => {
2440
resolve(args);
2541
});
42+
ws.on('unexpected-response', () => {
43+
reject('Unexpected-response');
44+
});
45+
});
46+
export const wsClose = (ws: WebSocket | SocketIOClient.Socket): Promise<void> =>
47+
new Promise((resolve, reject) => {
48+
ws.close();
49+
ws.on('close', () => {
50+
resolve();
51+
});
52+
ws.on('error', (err) => reject(err));
2653
});

integration/test/ws.spec.ts

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { INestApplication, WebSocketAdapter } from '@nestjs/common';
22
import { IoAdapter } from '@nestjs/platform-socket.io';
3+
import { WsAdapter } from '@nestjs/platform-ws';
34
import { Test } from '@nestjs/testing';
45
import { color } from '@ogma/logger';
56
import {
@@ -8,14 +9,22 @@ import {
89
Type,
910
} from '@ogma/nestjs-module';
1011
import { SocketIOParser } from '@ogma/platform-socket.io';
12+
import { WsParser } from '@ogma/platform-ws';
1113
import * as Io from 'socket.io-client';
1214
import * as WebSocket from 'ws';
1315
import { WsModule } from '../src/ws/ws.module';
14-
import { hello, serviceOptionsFactory, wsPromise } from './utils';
16+
import {
17+
createConnection,
18+
hello,
19+
serviceOptionsFactory,
20+
wsClose,
21+
wsPromise,
22+
} from './utils';
1523

1624
describe.each`
17-
adapter | server | parser | client | protocol
18-
${IoAdapter} | ${'socket.io'} | ${SocketIOParser} | ${(url: string) => Io(url)} | ${'http'}
25+
adapter | server | parser | client | protocol | sendMethod | serializer
26+
${IoAdapter} | ${'Socket.io'} | ${SocketIOParser} | ${(url: string) => Io(url)} | ${'http'} | ${'emit'} | ${(message: string) => message}
27+
${WsAdapter} | ${'Websocket'} | ${WsParser} | ${(url: string) => new WebSocket(url)} | ${'ws'} | ${'send'} | ${(message: string) => JSON.stringify({ event: message })}
1928
`(
2029
'$server server',
2130
({
@@ -24,12 +33,16 @@ describe.each`
2433
parser,
2534
client,
2635
protocol,
36+
sendMethod,
37+
serializer,
2738
}: {
2839
adapter: Type<WebSocketAdapter>;
2940
server: string;
3041
parser: Type<AbstractInterceptorService>;
3142
client: (url: string) => SocketIOClient.Socket | WebSocket;
3243
protocol: 'http' | 'ws';
44+
sendMethod: 'send' | 'emit';
45+
serializer: (message: string) => string;
3346
}) => {
3447
let app: INestApplication;
3548
let interceptor: OgmaInterceptor;
@@ -62,11 +75,11 @@ describe.each`
6275
beforeAll(async () => {
6376
let baseUrl = await app.getUrl();
6477
baseUrl = baseUrl.replace('http', protocol);
65-
ws = client(baseUrl);
78+
ws = await createConnection(client, baseUrl);
6679
});
6780

68-
afterAll(() => {
69-
ws.close();
81+
afterAll(async () => {
82+
await wsClose(ws);
7083
});
7184

7285
beforeEach(() => {
@@ -84,15 +97,22 @@ describe.each`
8497
`(
8598
'$message',
8699
async ({ message, status }: { message: string; status: string }) => {
87-
await wsPromise(ws, message);
100+
await wsPromise(ws, serializer(message), sendMethod);
88101
expect(logSpy).toHaveBeenCalledTimes(1);
89102
const logObject = logSpy.mock.calls[0][0];
90-
expect(logObject).toBeALogObject(server, message, 'WS', status);
103+
expect(logObject).toBeALogObject(
104+
server.toLowerCase(),
105+
message,
106+
'WS',
107+
status,
108+
);
91109
},
92110
);
93111
it('should get the data from skip but not log', async () => {
94-
const data = await wsPromise(ws, 'skip');
95-
expect(data).toEqual(JSON.parse(hello));
112+
const data = await wsPromise(ws, serializer('skip'), sendMethod);
113+
expect(data).toEqual(
114+
server === 'Websocket' ? hello : JSON.parse(hello),
115+
);
96116
expect(logSpy).toHaveBeenCalledTimes(0);
97117
});
98118
});

packages/platform-socket.io/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,7 @@ This plugin is to be used in the `OgmaInterceptorOptions` portion of the `OgmaMo
2020
)
2121
export class AppModule {}
2222
```
23+
24+
### Note
25+
26+
As the Gateway/Websocket context runs in parallel with the HTTP Context, and as the application configuration is not shared between the two, to bind the `OgmaInterceptor` to the GateWay, you **must** use `@UseInterceptor(OgmaInterceptor)` **and** have `OgmaModule.forFeature()` in the `imports` array of the same module.

packages/platform-ws/README.md

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
11
# `@ogma/platform-ws`
22

3-
> TODO: description
3+
The `WsParser` parser for the `OgmaInterceptor`. This plugin class parses TCP request and response object to be able to successfully log the data about the request. For more information, check out [the @ogma/nestjs-module](../nestjs-module/README.md) documentation.
4+
5+
## Installation
6+
7+
Nothing special, standard `npm i @ogma/platform-ws` or `yarn add @ogma/platform-ws`
48

59
## Usage
610

7-
```
8-
const platformWs = require('@ogma/platform-ws');
11+
This plugin is to be used in the `OgmaInterceptorOptions` portion of the `OgmaModule` during `forRoot` or `forRootAsync` registration. It can be used like so:
912

10-
// TODO: DEMONSTRATE API
13+
```ts
14+
@Module(
15+
OgmaModule.forRoot({
16+
interceptor: {
17+
ws: WsParser
18+
}
19+
})
20+
)
21+
export class AppModule {}
1122
```
23+
24+
### Note
25+
26+
As the Gateway/Websocket context runs in parallel with the HTTP Context, and as the application configuration is not shared between the two, to bind the `OgmaInterceptor` to the GateWay, you **must** use `@UseInterceptor(OgmaInterceptor)` **and** have `OgmaModule.forFeature()` in the `imports` array of the same module.

packages/platform-ws/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@
2626
"url": "git+https://github.com/jmcdo29/ogma.git"
2727
},
2828
"scripts": {
29-
"test": "echo \"No tests to run\""
29+
"prebuild": "rimraf lib",
30+
"build": "tsc -p tsconfig.build.json",
31+
"postbuild": "mv ./lib/src/* ./lib && rmdir lib/src",
32+
"test": "jest",
33+
"test:cov": "jest --coverage"
3034
},
3135
"bugs": {
3236
"url": "https://github.com/jmcdo29/ogma/issues"

packages/platform-ws/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './ws-interceptor.service';
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { ExecutionContext, HttpException, Injectable } from '@nestjs/common';
2+
import { MESSAGE_METADATA } from '@nestjs/websockets/constants';
3+
import { AbstractInterceptorService } from '@ogma/nestjs-module';
4+
import WebSocket = require('ws');
5+
6+
@Injectable()
7+
export class WsParser extends AbstractInterceptorService {
8+
getCallPoint(context: ExecutionContext): string {
9+
return this.reflector.get<string>(MESSAGE_METADATA, context.getHandler());
10+
}
11+
12+
getCallerIp(context: ExecutionContext): string {
13+
return (this.getClient(context) as any)._socket.remoteAddress;
14+
}
15+
16+
getProtocol(): string {
17+
return 'WS';
18+
}
19+
20+
getMethod(): string {
21+
return 'websocket';
22+
}
23+
24+
getStatus(
25+
context: ExecutionContext,
26+
inColor: boolean,
27+
error?: HttpException | Error,
28+
): string {
29+
const status = error ? 500 : 200;
30+
return inColor ? this.wrapInColor(status) : status.toString();
31+
}
32+
private getClient(context: ExecutionContext): WebSocket {
33+
return context.switchToWs().getClient<WebSocket>();
34+
}
35+
}

0 commit comments

Comments
 (0)