Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"printWidth": 120,
"singleQuote": true,
"semi": false,
"bracketSpacing": true,
"trailingComma": "all"
}
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# Changelog

## 0.1.0
Published by **[jarcodallo](https://github.com/jarcodallo)** on **pending**
Published by **[jarcodallo](https://github.com/jarcodallo)** on **2021/09/17**
- [#2](https://github.com/blockcoders/nestjs-websocket/pull/2) Update README.md and linter config
- [#d13d2db](https://github.com/blockcoders/nestjs-websocket/commit/d13d2db7287ae9784b64940a2fdcb07d39975b03) Update README.md
- [#a9a54ca](https://github.com/blockcoders/nestjs-websocket/commit/a9a54ca0796d466e35adcbb96e8a90412063402f) Websocket client for nest
239 changes: 230 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[![vulnerabilities](https://img.shields.io/snyk/vulnerabilities/npm/nestjs-websocket)](https://snyk.io/test/github/blockcoders/nestjs-websocket)
[![supported platforms](https://img.shields.io/badge/platforms-Express%20%26%20Fastify-green)](https://img.shields.io/badge/platforms-Express%20%26%20Fastify-green)

Websocket utility for NestJS based on [WS](https://www.npmjs.com/package/ws)
Websocket Client for NestJS based on [ws](https://www.npmjs.com/package/ws)

## Install

Expand All @@ -18,17 +18,35 @@ npm i nestjs-websocket

### Configuration params

The url param that websocket module expects should be a string, for example:
`nestjs-websocket` can be configured with this options:

```ts
{
url: 'ws://localhost:3000',
/**
* WebSocket Client options
* @see {@link https://github.com/websockets/ws/blob/master/doc/ws.md#class-websocket}
*/
interface WebSocketModuleOptions {
/**
* Required parameter a URL to connect to.
* such as http://localhost:3000 or wss://localhost:3000.
*/
url: string | URL;

/**
* Optional parameter a list of subprotocols.
*/
protocols?: string | string[]

/**
* Optional parameter a client or http request options.
*/
options?: ClientOptions | ClientRequestArgs
}
```

### Synchronous configuration

Use `WebSocketModule.forRoot` method with [String param](#configuration-params):
Use `WebSocketModule.forRoot` method with [Options interface](#configuration-params):

```ts
import { WebSocketModule } from 'nestjs-websocket'
Expand All @@ -37,6 +55,17 @@ import { WebSocketModule } from 'nestjs-websocket'
imports: [
WebSocketModule.forRoot({
url: 'ws://localhost:3000',
protocols: ['foo', 'bar'],
options: {
followRedirects: false,
handshakeTimeout: 10000,
maxPayload: 2000000,
maxRedirects: 10,
origin: 'http:/example.com',
perMessageDeflate: false,
protocolVersion: 1,
skipUTF8Validation: false,
},
}),
],
...
Expand All @@ -46,22 +75,35 @@ class MyModule {}

### Asynchronous configuration

`WebSocketModule.forRootAsync` allows you, for example, inject `ConfigService` to use it in Nest `useFactory` method.
With `WebSocketModule.forRootAsync` you can, for example, import your `ConfigModule` and inject `ConfigService` to use it in `useFactory` method.

`useFactory` should return object with [Options interface](#configuration-params)

`useFactory` should return an object with [String param](#configuration-params).
Here's an example:

```ts
import { Module, Injectable } from '@nestjs/common'
import { WebSocketModule } from 'nestjs-websocket'

@Injectable()
class ConfigService {
public readonly url = 'ws://localhost:3000'
}

@Module({
providers: [ConfigService],
exports: [ConfigService]
})
class ConfigModule {}

@Module({
imports: [
WebSocketModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => {
return {
url: 'ws://localhost:3000'
url: config.url,
}
},
}),
Expand All @@ -71,10 +113,189 @@ import { WebSocketModule } from 'nestjs-websocket'
class MyModule {}
```

Or you can just pass `ConfigService` to `providers`, if you don't have any `ConfigModule`:

```ts
import { Module, Injectable } from '@nestjs/common'
import { WebSocketModule } from 'nestjs-websocket'

@Injectable()
class ConfigService {
public readonly url = 'ws://localhost:3000'
}

@Module({
imports: [
WebSocketModule.forRootAsync({
providers: [ConfigService],
inject: [ConfigService],
useFactory: (config: ConfigService) => {
return {
url: config.url,
}
},
}),
],
controllers: [TestController]
})
class TestModule {}
```

## WebSocketClient

`WebSocketClient` implements a [WebSocket](https://github.com/websockets/ws/blob/master/doc/ws.md#class-websocket). So if you are familiar with it, you are ready to go.

```ts
import { Injectable } from '@nestjs/common'
import {
InjectWebSocketProvider,
WebSocketClient,
OnOpen,
OnMessage,
} from 'nestjs-websocket';

@Injectable()
class TestService {
private data: Record<any, any> = {}

constructor(
@InjectWebSocketProvider()
private readonly ws: WebSocketClient,
) {}

@OnOpen()
onOpen() {
this.ws.send(JSON.stringify(eventData))
}

@OnMessage()
message(data: WebSocketClient.Data) {
this.data = JSON.parse(data.toString())
}

async getData(): Promise<Record<any, any>> {
return this.data
}
}
```

## Websocket Events

### EventListener

`@EventListener` decorator will handle any event emitted from websocket server.

```ts
import { Injectable } from '@nestjs/common'
import { ClientRequest, IncomingMessage } from 'http'
import {
EventListener
} from 'nestjs-websocket';

@Injectable()
class TestService {
@EventListener('open')
open() {
console.log('The connection is established.')
}

@EventListener('ping')
ping(data: Buffer) {
console.log(`A ping ${data.toString()} is received from the server.`)
}

@EventListener('unexpected-response')
unexpectedResponse(request: ClientRequest, response: IncomingMessage) {
console.log(`The server response ${response} is not the expected one.`)
}

@EventListener('upgrade')
upgrade(response: IncomingMessage) {
console.log(`Response headers ${response} are received from the server as part of the handshake.`)
}
}
```

### OnOpen

`@OnOpen` is a shortcut for `@EventListener('open')`. Event emitted when the connection is established.

```ts
import { Injectable } from '@nestjs/common'
import {
OnOpen
} from 'nestjs-websocket';

@Injectable()
class TestService {
@OnOpen()
open() {
console.log('The connection is established.')
}
}
```

### OnClose

`@OnClose` is a shortcut for `@EventListener('close')` Event emitted when the connection is closed. `code` property is a numeric value for status code explaining why the connection has been closed. `reason` is a Buffer containing a human-readable string explaining why the connection has been closed.

```ts
import { Injectable } from '@nestjs/common'
import {
OnClose
} from 'nestjs-websocket';

@Injectable()
class TestService {
@OnClose()
close(code: number, reason: string) {
console.log(`The connection is closed. Reason: ${code} - ${reason}`)
}
}
```

### OnError

`@OnError` is a shortcut for `@EventListener('error')`. Event emitted when an error occurs. Errors may have a [.code](https://github.com/websockets/ws/blob/HEAD/doc/ws.md#ws-error-codes) property.

```ts
import { Injectable } from '@nestjs/common'
import {
OnError
} from 'nestjs-websocket';

@Injectable()
class TestService {
@OnError()
error(err: Error) {
console.log(`An error occurs: ${err}`)
}
}
```

### OnMessage

`@OnMessage` is a shortcut for `@EventListener('message')`. Event emitted when a message is received. `data` is the message content.

```ts
import { Injectable } from '@nestjs/common'
import {
OnMessage
} from 'nestjs-websocket';

@Injectable()
class TestService {
@OnMessage()
message(data: WebSocketClient.Data) {
console.log(`Data received: ${JSON.parse(data.toString())}`)
}
}
```

## Testing a class that uses @InjectWebSocketProvider

This package exposes a `getWebSocketToken()` function that returns a prepared injection token based on the provided context.
Using this token, you can easily provide a mock implementation of the [WS](https://github.com/websockets/ws) using any of the standard custom provider techniques, including useClass, useValue, and useFactory.
Using this token, you can easily provide a mock implementation of the [ws](https://github.com/websockets/ws) using any of the standard custom provider techniques, including useClass, useValue, and useFactory.

```ts
const module: TestingModule = await Test.createTestingModule({
Expand Down
8 changes: 2 additions & 6 deletions __tests__/utils/app.gateway.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
MessageBody,
SubscribeMessage,
WebSocketGateway,
} from '@nestjs/websockets';
import { MessageBody, SubscribeMessage, WebSocketGateway } from '@nestjs/websockets'

@WebSocketGateway()
export class ApplicationGateway {
Expand All @@ -11,6 +7,6 @@ export class ApplicationGateway {
return {
event: 'pop',
data,
};
}
}
}
16 changes: 8 additions & 8 deletions __tests__/utils/createGatewayApp.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { Test } from '@nestjs/testing';
import { WsAdapter } from '@nestjs/platform-ws';
import { INestApplication } from '@nestjs/common';
import { ApplicationGateway } from './app.gateway';
import { Test } from '@nestjs/testing'
import { WsAdapter } from '@nestjs/platform-ws'
import { INestApplication } from '@nestjs/common'
import { ApplicationGateway } from './app.gateway'

export async function createGatewayApp(): Promise<INestApplication> {
const testingModule = await Test.createTestingModule({
providers: [ApplicationGateway],
}).compile();
const app = await testingModule.createNestApplication();
}).compile()
const app = await testingModule.createNestApplication()

app.useWebSocketAdapter(new WsAdapter(app));
app.useWebSocketAdapter(new WsAdapter(app))

return app;
return app
}
10 changes: 5 additions & 5 deletions __tests__/utils/extraWait.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Adapter } from './platforms';
import { INestApplication } from '@nestjs/common';
import { FastifyAdapter } from '@nestjs/platform-fastify';
import { Adapter } from './platforms'
import { INestApplication } from '@nestjs/common'
import { FastifyAdapter } from '@nestjs/platform-fastify'

export async function extraWait(adapter: Adapter, app: INestApplication) {
if (adapter === FastifyAdapter) {
const instance = app.getHttpAdapter().getInstance();
const instance = app.getHttpAdapter().getInstance()
if (instance && typeof instance.ready === 'function') {
await instance.ready();
await instance.ready()
}
}
}
12 changes: 6 additions & 6 deletions __tests__/utils/platforms.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { AbstractHttpAdapter } from '@nestjs/core';
import { Type } from '@nestjs/common';
import { FastifyAdapter } from '@nestjs/platform-fastify';
import { ExpressAdapter } from '@nestjs/platform-express';
import { AbstractHttpAdapter } from '@nestjs/core'
import { Type } from '@nestjs/common'
import { FastifyAdapter } from '@nestjs/platform-fastify'
import { ExpressAdapter } from '@nestjs/platform-express'

export type Adapter = Type<AbstractHttpAdapter<any, any, any>>;
export type Adapter = Type<AbstractHttpAdapter<any, any, any>>

export const platforms: Adapter[] = [ExpressAdapter, FastifyAdapter];
export const platforms: Adapter[] = [ExpressAdapter, FastifyAdapter]
2 changes: 1 addition & 1 deletion __tests__/utils/randomPort.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export function randomPort(min = 5000, max = 7000): number {
return parseInt((Math.random() * (max - min) + min).toFixed());
return parseInt((Math.random() * (max - min) + min).toFixed())
}
Loading