DEV Community

Cover image for Nuxt Socket.IO: How to Create a Stealth-mode Chat Application in Under 10 minutes

Nuxt Socket.IO: How to Create a Stealth-mode Chat Application in Under 10 minutes

Richard Schloss on December 29, 2019

TL;DR - In the last post in this series, a new namespace configuration feature was presented. A special syntax was presented with the feature, and ...
Collapse
 
ahereandnow profile image
A-Here-And-Now • Edited

How do I respond directly to a client's emitter so-as to trigger the response assignement to the --> compomentProp?
I have tried:

const nsp = io.of('/usr');
nsp.on('connection', function (socket) {
    socket.on('beep', function (sock, callback) {
        socket.emit('beep', { data: "1234567890" })
        socket.emit('boop', { data: "1234567890" })
        socket.emit('bap', { data: "1234567890" })
    })
});

none of these trigger my clients special nuxt.config parameters/listeners:

namespaces: {
    '/usr': {
        emitters: ['beep --> boop'],
        listeners: ['bap --> blop']
    }
}

this is what my data looks like in the component in which I call this.socket = this.$nuxtSocket({ name: 'main', channel: '/usr'}):

data() {
    return { boop: {}, blop: {} }
}

When I call this.beep() it does trigger your module's special config to actually send the emitter, and the server gets it and runs the socket.on('beep'){} from above, but nothing else is working at all. My data props boop and blop experience no change.

EDIT: correction... the blop data was indeed filled... but still, I'm clueless as to how to reply to the 'beep' emitter directly so-as to trigger the setting of its respective componentProp (called 'boop' above).

Collapse
 
richardeschloss profile image
Richard Schloss • Edited

It would be done like this, in the current implementation:

namespaces: {
  '/usr': {
    emitters: ['beep --> boop'], // You emit 'beep' and want the response to be assigned to 'boop'
    listeners: [
      'beep', // 'beep' emitter triggers 'beep' to be emitted back
      'bap --> blop', // 'beep' also triggers 'bap', but assign 'bap' to 'blop'
    ]
  }
}

However, on the server-side you also have "callback" as an argument which can also just be called directly to callback to the emitter:

const nsp = io.of('/usr');
nsp.on('connection', function (socket) {
  socket.on('beep', function (sock, callback) { // "sock" should be "msg", contains client data
    const data = "1234567890"
    const others = ['boop', 'bap']

    others.forEach((clientEvt) => {
      socket.emit(clientEvt, { data })
    })

    // [omit]: socket.emit('beep', { data })
    // Instead use the callback argument:
    callback({ data: "1234567890" })    
  })
});
Collapse
 
ahereandnow profile image
A-Here-And-Now • Edited

Wow. That is exactly what I wanted. Thank you so much for the speedy reply! This helps a lot. I just have two more questions, if you can answer these as well I am pretty sure I would be all set to fully implement this in my project.

If I read the documentation correctly, with the new 'listener' you have:

listeners['beep']

this will have the corresponding response data assigned to a prop called 'beep',
due to NOT assigning a prop name with:

--> propName, 

Is that correct?

Also, for this listener you've implemented, the callback method you've shown will not trigger this listener, correct? I don't specifically need it to, just want to be clear what invoking callback() is doing.

Basically my question is... what exactly does the callback invocation do?

Just to set forth what I believe it to be based on your docs and my minor testing, it looks like the callback will go to the emitter and assign 'boop' but will not trigger the 'emit' listener on the client side. The flip side of that is that this:

socket.emit('beep', {data})

will trigger the client-side 'emit' listener but not the emitter, therefore a property of 'beep' will be assigned to the response data and 'boop' will remain empty.

With that said, when calling the 'beep' emitter method from the component like so:

this.beep();

Will that immediately trigger this 'beep' listener on the client side that you have put in place?

Do I seem to understand it fully? The callback goes directly back to the source of the request as a response and is not an emit-event at all?

Thread Thread
 
richardeschloss profile image
Richard Schloss

First statement is correct. When 'beep' event comes in, it's data will be assigned to prop 'beep' if it's defined on the component.

The callback doesn't fire an event, it only responds to the emitter directly; i.e., there's no need to have an extra listener set up to listen for the server's response to the emitted event. Just call the callback function.

Thread Thread
 
ahereandnow profile image
A-Here-And-Now

Perfect. One more thing:

With the this.beep() method that gets declared by your module according to my nuxt.config file, say I want to actually do stuff in my beep() method that will affect the data that is sent along with the emit event. Can I just do some processing in my beep() method and say

data = {myData:"myString"}
return data

or does the emitter fire asynchronously before the beep method processing is completed?

How does this work?

Thread Thread
 
ahereandnow profile image
A-Here-And-Now

Also, How does the pre-emit hook and post-emit hook work with respect to passing data into the emit event or receiving the leftover data after the componentProp setter functions are processed? Is there a way to cancel the emit propagation if in the pre-emit hook function the application finds something wrong with the data the user has provided?

Thread Thread
 
richardeschloss profile image
Richard Schloss

Actually, in your specific code snippet, the processing there is simply setting custom data, which would be specified in the nuxt.config entry as the "msg":

// nuxt.config:
emitters: [ 'beep + msg --> boop' ]

// component.vue:
data {
  return () { 
    msg: { myData: 'myString' }
  } 
}

That msg will get sent with the beep event. If you require custom processing, you do that in the pre-emit hook, and share the data using Vue's data.

With regards to cancelling an emitter event, while it's not currently implemented in the plugin, it would be done by setting a validation flag in the pre-emit hook, and watching that value. When it changes and becomes true, emit the event (i.e., call this.beep).

Thread Thread
 
ahereandnow profile image
A-Here-And-Now

Thank you for all of your replies, Richard. This has set me up well!

Thread Thread
 
richardeschloss profile image
Richard Schloss

Actually, I apologize for my answer to the second question. I realize I may have misspoke to quickly and perhaps stated something that could be incorrect. In the current implementation of the plugin, this is what is going on in the emitter:

  await runHook(ctx, pre) // run the pre-emit hook
  return new Promise((resolve, reject) => {
    ...
    // Emit the event "emitEvt" with msg:
    socket.emit(emitEvt, msg, (resp) => { 
      // Handle response
    })
    ...
  })

So, the plugin is currently not checking the return value of the runHook, but I guess the next version can check for a validation value (true or false). If validation fails, the emitter should not emit the event. However, to prevent breaking code for existing users of the plugin, it should only operate on the return value if it's defined (I'll keep thinking about this). The other design challenge is: do I treat the return value as a validation value or as data that should be propagated to the emit "msg"? Maybe some happy combination can be made.

But, back to your question...so for now since the plugin does't check the pre-emit hook's validation value, you would just update your code as follows:

nuxt.config:

// emitters: [ 'checkBeep] beep + msg --> resp' ] // old
emitters: [ 'beep + msg --> resp' ] // workaround

And then in your component.js:

checkBeep() { // You'd still have this method defined
  if (this.inputValid) {
    // valid input:
    this.beep() // "beep" gets emitted with "msg"
  }
}

This way, you can still have checkBeep defined, but just omit it from the nuxt.config entry so that the plugin doesn't call it. I know going forward, it will be cleaner to let the plugin do it, so I'll just need some time to think about it. (the challenge is when a lot of people have already downloaded it, there's a small risk I'll break there existing code, so I just have to take that into consideration too)

Collapse
 
jlums profile image
jlums • Edited

hello, I think post is over a year old but I'm trying to use nuxtSocket with strapi. I have tried to implement code similar to yours but the methods defined in the nuxt.config.js aren't being recognized. This is a snippet of my nuxt.config.js

namespaces: {
'/room': {
emitters: [
'join + joinMsg --> roomInfo',
'leave + leaveMsg'
],

Then I use it in the _room.vue component like this:-

mounted() {
this.socket = this.$nuxtSocket({ channel: '/room' });
this.userRoomData = {user: this.username, room: this.$route.params.room};
this.join();
}

But I get the following error: -
TypeError: this.join is not a function
at VueComponent.mounted (_room.vue?9bf3:154)
at invokeWithErrorHandling (vue.runtime.esm.js?2b0e:1854)
at callHook (vue.runtime.esm.js?2b0e:4219)
at insert (vue.runtime.esm.js?2b0e:3139)
at invokeWithErrorHandling (vue.runtime.esm.js?2b0e:1854)
at Object.invoker as insert
at invokeInsertHook (vue.runtime.esm.js?2b0e:6346)
at Vue.patch as patch
at Vue._update (vue.runtime.esm.js?2b0e:3945)
at Vue.updateComponent (vue.runtime.esm.js?2b0e:4060)

Any help would be greatly appreciated
Thanks

Collapse
 
richardeschloss profile image
Richard Schloss

What version of vue are you using? The code was tested against 2.6.x a while ago.

Collapse
 
jlums profile image
jlums

Hi Richard, I actually got it to work out for me, without the namespaces. Thanks for the initial reply, I appreciate it.

Thread Thread
 
richardeschloss profile image
Richard Schloss

Ok great.

Collapse
 
jlums profile image
jlums

thanks for the reply, I'm using 2.6.12

Collapse
 
kp profile image
KP

Thanks for this tutorial @richardeschloss ...it's exactly what I was looking for.
One question..when you say "I wont track your chats"...does the data hit your servers? Does this codebase depend on any 3rd party servers or services at all? Thx!

Collapse
 
richardeschloss profile image
Richard Schloss

I won't track your chats because I technically can't. The chats only go between your IO clients and IO server. There is no code in the plugin that relays the chats to anywhere else. I believe the same can be said for the underlying EngineIO. However, the beautiful thing with open source is, if anyone does not trust my statement, he/she is free to look in the source code (plugin.js) to search for suspicious code and comment out as much or as little of the code as desired. However, I don't have any such tracking code in my plugin.

Collapse
 
kp profile image
KP

@richardeschloss that makes sense, thanks for clarifying :) I'll try it out and let you know if any questions come up!

Thread Thread
 
richardeschloss profile image
Richard Schloss

@kp , If interested, here's a hosted demo. I just deployed it today.