1

I'll preface this by saying I am new to nodejs in general. Coming from the world of C#, it's a completely different way of thinking for me. I've gone through a few courses and I'm setting up a little website as sort of a test for myself. And I'm failing!

I'm using socket.io with node, and I'm trying to broadcast a message with the emitter once in a while. I don't care about specific socket points right now (although I will in the future), so the emitter for this should go out to everyone.

I am having trouble accessing the io object from other modules. I'll post my server.js file, as well as app/index.js, socket/index.js, helpers/index.js and api/index.js. I hope that posting these will show how it's supposed to work.

Ideally, I'd like to keep all socket-related items in the socket module, for consistency. Right now, I'm trying to get a method to run in the helpers module, but ideally the socket module would be better.

Anyway, server.js:

'use strict';
const express = require('express');
const app = express();
const cryptometers = require('./app');
const api = require('./app/api');
const fs = require('fs');
const sources = require('./app/api/sources.json');

app.set('port', process.env.PORT || 3000);
app.set('view engine', 'ejs');

app.use(express.static('public'));
app.use(cryptometers.session);
app.use('/', cryptometers.router);

cryptometers.ioServer(app).listen(app.get('port'), () =>{
    console.log('app listening on port ' + app.get('port'));

api.getData(sources[0].source, sources[0].url, app);
setInterval(function(){api.getData(sources[0].source, sources[0].url, app)}, 60000);

});

Standard fare here. I just added a data retriever that calls to an api once every minute, which updates the database.

app/index.js:

'use strict';

const config = require('./config');

// create an IO server instance
let ioServer = app => {
    app.locals.globalMarketCap = [];

    const server = require('http').Server(app);
    const io = require('socket.io')(server);
    io.set('transports', ['websocket']);

    io.use((socket, next) => {
        require('./session')(socket.request, {}, next);
    });
    require('./socket')(io,app);
    return server;
}


// turns router into a module
module.exports = {
    router: require('./routes')(),
    session: require('./session'),
    ioServer,

}

Here I'm initializing socket.io, binding it to the app. It's also where I initialize a local storage array of data. (Is this a good spot to do this??)

socket/index.js:

'use strict';
const h = require('../helpers');

module.exports = (io, app) => {

    io.of('/').on('connection', socket =>{
        console.log('socket.io connected to client');

        if(app.locals.globalMarketCap){
            socket.emit('globalMarketCap', JSON.stringify(app.locals.globalMarketCap));
        }
    })
}

Here I'm responding to connection events, and pushing out the array of data that I defined in the last file above. Again, ideally I'd like all socket type stuff to stay in here.

helpers/index.js:

'use strict';
const router = require('express').Router();
const db = require('../db');

// iterate through the routes object and mount the routes
let _registerRoutes = (routes, method) => {
    for(let key in routes){
        if(typeof routes[key] === 'object' && routes[key] !== null && !(routes[key] instanceof Array)){
            _registerRoutes(routes[key], key);
        } else {
            // Register the routes
            if(method === 'get'){
                router.get(key, routes[key]);
            } else if(method === 'post'){
                router.post(key, routes[key]);
            } else {
                router.use(routes[key]);
            }
        }
    }
}

let route = routes => {
    _registerRoutes(routes);
    return router;
}

let updateGlobalMarketCap = (app) =>{
   //app.io.socket.emit('globalMarketCap', JSON.stringify(app.locals.globalMarketCap))
}

module.exports = {
    route,
    updateGlobalMarketCap
}

The commented out line for updateGlobalMarketCap is where my pain is. Trying to get access to the io object there.

api/index.js

'use strict';

const axios = require("axios");
const db = require("../db");
const h = require("../helpers");

let getData = (source, url, app, cryptoMeters) => {
    axios
        .get(url)
        .then(response => {
            //console.log(response.data);
            response.data["source"] = source;
            var data = new db.globalMarketCapModel(response.data);
            app.locals.globalMarketCap = response.data;
            var query = { source: source};

            db.globalMarketCapModel.findOne({
                "source":source
            }, 'source old_total_market_cap_usd total_market_cap_usd', function(err, market) {
                if (market) {
                    if(market.old_total_market_cap_usd != response.data.total_market_cap_usd
                    && market.total_market_cap_usd != response.data.total_market_cap_usd){
                        response.data["old_total_market_cap_usd"] = market.total_market_cap_usd;
                        h.updateGlobalMarketCap(app);
                    }

                    db.globalMarketCapModel.update(query, response.data, function (err) {
                        if (err) {
                            console.log("uhoh")
                        } else {
                            return true;
                        }
                    });
                } else {
                    data.save(function (err) {
                        if (err) {
                            console.log("uhoh")
                        } else {
                            return true;
                        }
                    })
                }
            })

            return true;
        })
        .catch(error => {
            console.log(error);
            return false;
        });
}


module.exports = {
    getData

}

The getData function here is where a call to the update emitter would take place.

I've considered using standard node event emitters as a solution to my problem, but that might be gumming up the works and there's a simpler answer. Anyway, thanks for reading, and I'm interested in any commentary on what i've written so far. pitfalls, mistakes, etc. Learning here! :)

Salx
  • 579
  • 5
  • 21
  • you need a ref to the object. you can pass it around, tack it on to/as a global, or wrap your module with something that enables persistence on future require()s by modifying the cache. – dandavis Feb 06 '18 at 01:31
  • Here is the your solution: https://stackoverflow.com/a/68167653/6482248 – Prathamesh More Jun 29 '21 at 05:07

1 Answers1

2

There are many different ways to organize your code to accomplish sharing of the io object. Here's one such scheme. You break out your socket.io initialization code into its own module. You give that module two main features:

  1. A constructor function (that you pass the server to) that allows socket.io to initialize itself on your server.

  2. A method to get the io instance after it's been initialized.

This will allow any other code in your project that wants to get access to the io object to do something like this:

const io = require('./io.js').getIO();

Here's how that io module could be structured:

// io.js

// singleton instance of socket.io that is stored here after the 
// constructor function is called
let ioInstance;

modules.exports = function(server) {
    const io = require('socket.io')(server);
    io.set('transports', ['websocket']);

    io.use((socket, next) => {
        require('./session')(socket.request, {}, next);
    });
    // save in higher scope so it can be obtained later
    ioInstance = io;
    return io;
}

// this getIO method is designed for subsequent 
// sharing of the io instance with other modules once the module has been initialized
// other modules can do: let io = require("./io.js").getIO();
module.exports.getIO = function() {
    if (!ioInstance) {
        throw new Error("Must call module constructor function before you can get the IO instance");
    }
    return ioInstance;
}

And, this module would be initialized like this:

const io = require('./io.js')(server);

Where you pass it your web server so it can hook to that. It has to be initialized like this before anyone can use .getIO() on it. The storage in the module of the ioInstance makes use of the module caching. The module initialization code is only run once. After that, the same exports are just returned each time which have access to the saved ioInstance inside the module.

jfriend00
  • 683,504
  • 96
  • 985
  • 979