Socket.io acknowledgement in Nest.js

I'm trying to enable usage of socket.io acknowledgement 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 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?
Thanks in advance!

Answers:

Answer

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!!

Answer

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

Answer

You can try this module: https://www.npmjs.com/package/nestjs-socket-handlers-with-ack. It calls acknowledgement function under the hood, you only need to return some value or throw error. Hope it helps

Answer

Simply use the return statement from SubscribeMessage

// server
@SubscribeMessage('message')
  async onMessage(
    client: Socket, query: string
  ) {
    try {
      console.log(query) 
      return 'hello'
    } catch (e) {
      // ...
    } 
  }

on the client side use as third param a function

// client
this.socket.emit('message', query, (res) => {
  console.log(res); // should log 'hello'
});

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.