Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Socket.io acknowledgement in Nest.js

I'm trying to enable usage of socket.io acknowledgment callbacks in Nest.js WebSocketGateways.

I'd like to be able to emit this:

socket.emit('event', 'some data', function (response) {
  //do something
})

And use the message handler like this:

@SubscribeMessage('event')
onStart(client, data, ack) {
  //Do stuff
  ack('stuff completed');
}

According to this nestjs/nest GitHub issue issue there is no support for it in the library so you'd have to build your own websocket adapter. I tried it but am not sure how to do it exactly. I guess I need to do something special in the bindMessageHandlers function but my attempts have been in vain. This is the bindMessageHandlers implementation in the default socket.io adapter bundled in the framework:

public bindMessageHandlers(
  client,
  handlers: MessageMappingProperties[],
  process: (data: any) => Observable<any>,
) {
  handlers.forEach(({ message, callback }) =>
    Observable.fromEvent(client, message)
      .switchMap(data => process(callback(data)))
      .filter(result => !!result && result.event)
      .subscribe(({ event, data }) => client.emit(event, data)),
  );
}

Does anyone have any pointers on how I would go about implementing this?

like image 697
awnton Avatar asked Apr 02 '18 13:04

awnton


2 Answers

After short time researching NestJS. Here is my solution.

src
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── common
│   └── adapters
│       └── ws-adapter.ts
├── events
│   ├── events.gateway.ts
│   └── events.module.ts
└── main.ts

main.ts File

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { WsAdapter } from './common/adapters/ws-adapter.ts';
import * as cors from 'cors';

let corsOptions = {
    origin: 'http://nestjs.test',
    credentials: true
}

async function bootstrap() {
    const app = await NestFactory.create(AppModule);
    app.useWebSocketAdapter(new WsAdapter(3000));
    app.use(cors(corsOptions));
    await app.listen(4000);
}
bootstrap();

Because when we using WebSocket Adapter we can't use same port with NestJS application anymore.

common\adapters\ws-adapter.ts File

import * as WebSocket from 'ws';
import { WebSocketAdapter } from '@nestjs/common';
import { IoAdapter } from '@nestjs/websockets';
import { MessageMappingProperties } from '@nestjs/websockets';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/fromEvent';
import 'rxjs/add/observable/empty';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/filter';

export class WsAdapter extends IoAdapter {
  public bindMessageHandlers(
    client,
    handlers: MessageMappingProperties[],
    process: (data: any) => Observable<any>,
  ) {
    handlers.forEach(({ message, callback }) => {
        client.on('event', function (data, ack) {
            console.log('DATA', data)
            ack('woot')
        })
        Observable.fromEvent(client, message)
            .switchMap(data => process(callback(data)))
            .filter(result => !!result && result.event)
            .subscribe(({ event, data }) => client.emit(event, data))
        });
  }
}

My Client side source code

socket.emit('event', {data: 'some data'}, function (response) {
    console.log('RESPONSE', response)
});
socket.on('event', function(data) {
    console.log('ON EVENT', data);
});

And here is my result

enter image description here

enter image description here

Hope this help!!

like image 147
Quynh Nguyen Avatar answered Sep 24 '22 10:09

Quynh Nguyen


Update: Support for acknowledgements have been added in Nest 5.0.
If the socket provider passes multiple arguments into the SubscribeMessage handler the request parameter will be an array with these arguments.

For example with the default socket.io-adapter:

@SubscribeMessage('event')
async onEvent(client, request) {
  let data = request[0]
  let ack = request[1] //the acknowledgement function
}

One issue is that if you don't provide a acknowledgement function request will not be an array, and just be the data object.

In one of my current projects I've worked around this by creating a helper function that extracts the data and acknowledgement function, or creates a placeholder, which means that I can always call the ack function without considering it's existence:

export function extractRequest (req: any): { data: any, ack?: Function } {
  if (Array.isArray(req)) {
    const [data, ack] = req
    return { data, ack }
  } else {
    return { data: req, ack: () => {} }
  }
}

Old answer: The current status is that this is not possible without modifying Nest source. It will be added in the upcoming 5.0 release. I'll update this answer with an example when it is released.

Source: https://github.com/nestjs/nest/issues/581

like image 39
awnton Avatar answered Sep 25 '22 10:09

awnton