Module structure/passing parameters to modules (sockets.io and nodejs)

So I’m pretty stumped by this, I must be missing a design pattern or something obvious when creating modules.

I create a socket in my app.js file like so:

// app.js

sequelize.sync()
    .then(result => {
        const server = app.listen(8080);
        const io = require('./util/socket').init(server);
        io.on('connection', socket => {
            socket.on('chatbox', (data) => {
                twilio.sendSMS(data); // Although this works for sending 
                 // msgs, this isn't (?) the way to handle 2 way comms.
                // I probably need to require the twilio module here
               // and pass the socket as a parameter

            });
        });
        
    })

This allows me to create a socket connection to any frontend that connects, and send an sms when they type in a chat box. That part works great, but the way I’m structuring seems wrong.

The problem is that I need access to this specific socket instance in the twilio module so that I can send the SMS reply back to the frontend in the handleReply function:

// twillio.js

exports.sendSMS = (msg) => {
    client.messages.create({
        to: process.env.MY_PHONE_NUMBER,
        from: process.env.TWILIO_PHONE_NUMBER,
        body: msg
    })
    .then(message => console.log(message.sid));
}

exports.handleReply = (req, res, next) => {    
    const twiml = new MessagingResponse();
    twiml.message(`You said ${req.body.Body}`);

    //  ***I'd like to reply using the socket here***

    res.setHeader('Content-Type', 'text/xml');
    res.status(200).send(twiml.toString());
};

But how can I pass the socket to the twilio module?

:thinking:

Any help would be amazing :grinning:

Thanks,

Nick

Hello there,

I am not quite sure I understand the problem. It looks like you need to pass a callback to handleReply which accepts socket as an argument. Where are you using handleReply?

Otherwise, why not just reuse your io session within hanldeReply?

I wrote this code a while ago for the curriculum, and the nice bit is mounting the session handler to the io instance. That way, io can be required anywhere within the app, and still use the same session.

Hope this helps

Hi Sky020!

Yeah sorry, I wasn’t that clear because the problem wasn’t entirely clear in my head, I’ve been working on it :smile:

So you’re right, I need to pass the socket to handleReply, so I reworked the twilio module to accept the argument:

module.exports = function(socket) {
    return {
        sendSMS: function(msg) {
                 // Use the socket here
        },
        handleReply: function(req, res) {
                // Use the socket in here
        }
    }
}

And then I use the module in app.js

        const server = app.listen(8080);
        const io = require('./util/socket').init(server);
        io.on('connection', socket => {
            socket.on('chatbox', (data) => {
                    const twilio = require('./twilio')(socket);
                    twilio.sendSMS();
                    twilio.handleReply(); 
                    // etc....
            });
        });

But now my problem is that handleReply is called from a route, and I need the socket instance there too:

const twilioController = require('../util/twilio')( // socket? );

router.post('/', twilioController.handleReply);

module.exports = router;

So you’re definitely right, I need a way of accessing the socket anywhere within the app - I’ll take a look at the code you linked, but it feels like it goes over my head… esp when you talk about mounting the session handler to the io instance :smile:

Yeah, it’s definitely over my head! :sweat_smile:

What about just emitting the data through:

// twilio.js
const io = require('socket.io');
// Maybe require('socket.io-client');

const socket = io();

// Export func setup...
handleReply: function(req, res) {
  socket.emit('send-stuff', req.body);
  // I believe Socket.io works by stringifying the data
}

I hope that is clear

But surely if the a socket is created when someone connects to the server

// app.js

const io = require('socket.io');
io.on('connection',  socket => {
        // this socket would be different to the one 
       // you're referring to in twilio.js, no?
} )

it wouldn’t refer to the same socket?

Maybe I’m misunderstanding the bigger picture :thinking:

btw, really appreciate the help

Correct. But, do you need the same socket? Are you not just wanting to emit some data (a message)?

I have never tested this kind of implementation, but it would essentially be emitting a message from the server for the server to listen to - an internal socket connection. Although, I would imagine this would be problematic if you plan on having more than 1 client listening, as you would have to set up some sort of authentication logic so the server only sends the correct information to the correct client… Probably not the best approach.


I will give this some more thought. It is confusing my brain about what needs to be where, right now. :sweat_smile:

Yeah I do need the same socket unfortunately:

handleReply(req, res) {}

is a webhook. So the route: router.post('/sms', twilio.handleReply); is called by an outside server with data that I need to forward to a specific user on the frontend.

so I think I need the specific connection made in app.js to be available in handleReply which I can do:

app.use('/sms', exampleRoutes);
.
.
.
io.on('connection',  specificSocket => {
            socket.on('chatbox', (data) => {
                const twilio = require('./util/twilio')(specificSocket);
                twilio.handleReply();
            });
        });

but that’s no good to me, because handleReply is passed in the exampleRoutes file, where there is no specific connection to pass:

// exampleRoutes.js:
const express = require('express');
const router = express.Router();
const twilioController = require('../util/twilio')(//no socket);

router.post('/', twilioController.handleReply);

module.exports = router;

I get the feeling that this is the answer to my problems, as it looks like it passes both the io and the socket, but I’m not intelligent enough to work out what the hell is going on… yet :sweat_smile:

Sorry for confusing your brain, I know how you feel!

What about changing direction - pass app around, and only define the .post('/') route within an io connection?

This does depend on if you are ok with just 404ing any POST requests made before connection.

Is there any chance you could demonstrate that with a small code example if you get a chance? I’m not entirely clear what you mean

I think 404’ing post requests would be fine, the logic is like this:

frontend user types in chatbox => sends sms to me => I reply => twilio posts my reply to /sms => server forwards message on to frontend user.

So if the connection doesn’t exist that means the user has left. Although this feels like it’s going to be a headache in the future if I ever want to persist chats…

I don’t suppose you understood or got a chance to look at the last link I posted did you? https://socket.io/docs/v4/server-application-structure/. It might be the brickwall I need to bang my head against?

Sure. At its simplest, I was thinking:

// One file, for simplicity, and maybe testing
const app = require('express')(); // I have just assumed you are using Express

io.on('connection', socket => {
  app.post('/', (req, res, next) => {
    const twiml = new MessagingResponse();
    twiml.message(`You said ${req.body.Body}`);
    // Send reply to client
    io.emit('reply', { username: "nickwoodward", message: req.body.message});

    res.setHeader('Content-Type', 'text/xml');
    res.status(200).send(twiml.toString());
  });
}

Now, when there is a connection, the route is defined, and the socket should remain set.

I realise that code does not use socket, but I think you only need that to send yourself the message, and not to send the reply.


I just had another thought… You could just extend the app object with a new property socket, and attach/define this property, once there is a connection - still need to pass the app around, but does not require the definition to be within io.on.

Something like:

io.on('connection', socket => {
  app.socket = socket;
}

Then, in routes:

app.post('/', function (req, res) { // Cannot use () =>
  const sock = this.socket; // Or, maybe 'app.socket' still??
});

I did read it, but you still need to decide what you want where. That is, you could take this:

module.exports = (io) => {
  const sendMeAnSMS= function (payload) {
    const socket = this;
    // Do the twilio.sendSMS(payload) stuff
  };

  // Handle io.emit('sms') on app

  return {
    sendMeAnSMS
  }
}

I hope this sparks something.

amazing stuff, thanks.

am going to look it over properly tomorrow as it’s pretty late here now, but I think the idea of passing the app might be a good work around if I can’t work out the rest :slight_smile:

thanks again

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.