awnton
awnton

Reputation: 659

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?

Upvotes: 19

Views: 12500

Answers (5)

Riccardo Venturini
Riccardo Venturini

Reputation: 478

On client side:

 socket.emit('my-message', payload, (response) => {
   console.log(response)  
 })

On server side, just return response data:

  @SubscribeMessage('my-message')
  async myMessage (client, payload) {
     console.log(payload)

     return {
       ... your response data here
     }
  }

Upvotes: 1

kenny
kenny

Reputation: 1776

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'
});

Upvotes: 8

awnton
awnton

Reputation: 659

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

Upvotes: 3

Andrew Gura
Andrew Gura

Reputation: 382

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

Upvotes: 1

Quynh Nguyen
Quynh Nguyen

Reputation: 3009

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

Upvotes: 5

Related Questions