What is the best way to make socket.io to a singleton? here i have three files i need to use socket.io method in user.mjs
socket.mjs
class socketBusiness extends baseBusiness {
//io = null;
//connectedUsers = {}
constructor(io) {
super(io);
this.io = io;
this.connectedUsers = {};
this.addUserRef= {};
this.bindEvents();
}
bindEvents() {
this.io.on("connection", this.onConnection);
this.io.use(this.onBeginConnection);
}
unBindEvents() {
this.io.off("connection", this.onConnection);
}
onConnection(socket) {
let _io = this.io;
let socketId = socket.id;
socket.on("disconnect", reason => {
});
socket.on("chat message", function(msg) {
});
}
addUserRef(userId, cstId) {
let arr = this.addUserRef[cstId] || [];
if (arr.indexOf(cstId) < 0) {
arr.push(cstId);
}
this.addUserRef[userId] = arr;
}
}
export default socketBusiness;
user.mjs
const socket = require("socket.mjs)
export async function addCst(req, res) {
socket.addUserRef(req.id,req.cstId)
}
How i can access the socket.io method ? any help will be highly appreciated
www.mjs
import socket from '../socket.mjs';
var server = createServer(app);
var io = new SocketServer(server, {})
var sb = new socketBusiness(io);
Export an instance:
export default new socketBusiness;
If you export a singleton, you can't pass io in the constructor. Move the initialization logic to a method:
constructor() { }
init(io) {
this.io = io;
this.bindEvents();
}
Then, initialize the singleton when you got the socket:
var io = new SocketServer(server, {})
socket.init(io);
Related
I'm following a tutorial which is using jest to test the javascript. The instructor created a static function called genesis() on a class called Block and it worked for him just fine, but when I tried to do it I got TypeError: block.genesis is not a function. If I remove the static keyword it recognises the function and the test passes.
Here is the class:
const { GENESIS_DATA } = require('./config');
class Block {
constructor({ timestamp, lastHash, hash, data }) {
this.timestamp = timestamp;
this.lastHash = lastHash;
this.hash = hash;
this.data = data;
}
static genesis() {
return new Block(GENESIS_DATA);
}
}
module.exports = Block;
And the test:
const Block = require('./block');
const { GENESIS_DATA } = require('./config');
describe('Block', () => {
const timestamp = 'a-date';
const lastHash = 'a-hash';
const hash = 'another-hash';
const data = ['blockchain', 'data'];
const block = new Block({ timestamp, lastHash, hash, data });
describe('genesis()', () => {
const genesisBlock = block.genesis();
it('returns a block instance', () => {
expect(genesisBlock instanceof Block).toBe(true);
});
it('returns the genesis data', () => {
expect(genesisBlock).toEqual(GENESIS_DATA);
});
});
});
The genesis method is part of the class, not the instance. You want to call Block.genesis() instead of block.genesis()
So I'm creating a singleton class and when I require it from my server.js file it works fine, but when I require it from another file it returns as undefined. I'll try to post relevant code but some will have to be cut out due to work.
server.js
const express = require('express');
const bodyParser = require('body-parser');
const path = require('path');
const http = require('http');
const app = express();
const config = require('config');
const FBConfigsListener = require('./server/amq_listeners/fb_configs.listener');
const FBConfigs = require('./server/models/FBConfigs');
//Api file for interacting with mongodb
const api = require('./server/routes/api.routes');
//Parsers
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
//Angular Dist output folder
app.use(express.static(path.join(__dirname, 'dist')));
//Api location
app.use('/api', api);
//Send all other requests to angular
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist/index.html'));
});
//set port
var port = config.get('webserver.port');
app.set('port', port);
const server = http.createServer(app);
server.listen(port, () => console.log(`Running on localhost:${port}`));
models/FBConfigs.js
var ConfigModel = require('./config');
var config = require('config');
var _ = require('lodash');
var FBConfigsListener = require('../amq_listeners/fb_configs.listener');
var AMQAdapter = require('../adapters/amq.adapter');
var uniqid = require('uniqid');
const connectionOptions = config.get('activemq.connectionOptions');
class FBConfigs {
constructor() {
console.log(config.get('environments'));
this.listener = FBConfigsListener;
this.configs = {};
this.unique_keys = ['id'];
this.update_topic = '/topic/fusebuilder.update.config.';
console.log(FBConfigsListener);
//AMQ Client
this.amq_client = AMQAdapter.getInstance(connectionOptions.host, connectionOptions.port);
}
add(key, config) {
if (!(key in this.configs)) {
this.configs[key] = new ConfigModel(this.unique_keys);
}
this.configs[key].add(config);
}
get(key) {
let configs_json = {};
if (key) {
configs_json[key] = JSON.parse(this.configs[key].toString());
} else {
for (let key in this.configs) {
configs_json[key] = JSON.parse(this.configs[key].toString());
}
}
return configs_json;
}
updateByID(key, id, input_config) {
let configs = this.configs[key].get();
for (let config of configs) {
if (input_config.id === config.id) {
this.update(key, _.merge(config, input_config));
}
}
}
//Send update to config topic
update(key, config) {
let topic = this.update_topic + key;
var update_object = {};
if (Array.isArray(config)) {
update_object[key] = [...config];
} else {
update_object[key] = [config];
}
console.log(`Sending ${key} update:${JSON.stringify(update_object)}`);
this.amq_client.sendMessage(topic, update_object);
}
copyTo(key, id, env) {
let selected_env = config.get('environments.' + env);
// let tmp_amq_client = new AMQAdapter(selected_env.host, selected_env.port);
let selected_config = this.configs[key].getByID(id);
console.log(this);
if (key === 'fuses') {
console.log('In FBConfig Copy to for fuses');
const get_fuse_topic = '/topic/fusebuilder.get_fuse';
const tran_id = uniqid();
const sendObj = { fuseName: id, tran_id };
this.amq_client.sendMessage(get_fuse_topic, sendObj);
var startTime = process.hrtime()[0];
var timeout = false;
while (!this.listener.get_copy_fuse_data(tran_id)) {
console.log('Waiting for config');
sleep(100);
if (process.hrtime()[0] - startTime > 3) {
console.log('Timed out');
timeout = true;
break;
}
}
console.log(JSON.stringify(FBConfigsListener.get_copy_fuse_data(tran_id)));
} else {
tmp_amq_client.sendMessage(this.update_topic, selected_config);
}
console.log(`Copy ${key} id:${id} to ${env}`);
}
}
module.exports = new FBConfigs();
amq_listener/fb_configs.listener.js
const config = require('config');
var AMQAdapter = require('../adapters/amq.adapter');
var FBConfigs = require('../models/FBConfigs');
**removed for work**
class FBConfigsListener {
constructor() {
this.instance;
this.copy_fuse_data = {};
//AMQ Client
this.amq_client = AMQAdapter.getInstance(connectionOptions.host, connectionOptions.port);
//Subscribe to configs
this.amq_client.subscribe(config_subscribe_topic, this.config_topic_callback.bind(this));
//Request Tables
this.amq_client.sendMessage(config_request_topic, { tables: config_tables });
//Subscribe to Copy Fuse topic
this.amq_client.subscribe(subscribe_fuse_copy_topic, this.copy_fuse_callback.bind(this));
}
config_topic_callback(err, message) {
let dest = this.amq_client.getDestination(message);
let key = this.get_key_from_topic(dest);
this.amq_client.readMessage(message, body => {
let configs = JSON.parse(body);
if (key in configs) {
for (let config of configs[key]) {
FBConfigs.add(key, config);
}
}
});
}
copy_fuse_callback(err, message) {
this.amq_client.readMessage(message, body => {
const config = JSON.parse(body);
this.copy_fuse_data[config.tran_id] = config;
});
}
//Get Key from the topic and convert using key map if needed
get_key_from_topic(topic) {
let key = topic.split('.')[topic.split('.').length - 1];
key = key in key_map ? key_map[key] : key;
return key;
}
get_copy_fuse_data(id) {
if (id in this.copy_fuse_data) {
return this.copy_fuse_data[id];
} else {
return false;
}
}
}
module.exports = new FBConfigsListener();
Error happens in FBConfigs. FBConfigsListener returns {} so all functions in there are undefined. Even if I do console.log(require('../amq_listeners/fb_configs.listener')) it prints {} But doing the same thing in server.js (with updated path) it prints the module.
Also tips on how to improve my coding style would be appreciated too.
Edit
So I found out that I have a circular dependency between these classes. How can this be fixed while allowing me to call one from the other.
I would suggest you to instantiate your dependencies firstly and store them in some object which you can pass then to your dependent classes. The structure can be
factories/services.js
/*
* Instantiates passed services and passes injector object to them
*/
module.exports = function createServices(injector, services) {
return Object.entries(services)
.reduce((aggregator, [name, serv]) => {
const name_ = camelCase(name);
aggregator.set(name_, new serv(injector));
return aggregator;
}, new Map());
};
lib/service.js
/**
* Base class for classes need any injections
*/
module.exports = class Service {
constructor(injector) {
this.injector = injector;
}
get dependencies() {
return this.injector.dependencies;
}
/*
* Background jobs can be ran here
*/
async startService() {}
/*
* Background jobs can be stopped here
*/
async stopService() {}
};
lib/injector.js
const Service = require('./service');
/*
* Contains all dependencies
*/
module.exports = class Injector {
constructor() {
this.services = new Map();
this._dependencies = {};
}
has(name) {
return this.services.has(name);
}
register(name, service) {
if (this.has(name)) {
throw new Error(`Service ${name} already exists`);
}
if (service instanceof Service === false) {
throw new Error('Argument #2 should be an instance of Service');
}
this.services.set(name, service);
this._dependencies[name] = service;
}
unregister(name) {
if (! this.has(name)) {
throw new Error(`Service ${name} not found`);
}
this.services.delete(name);
delete this._dependencies[name];
}
get dependencies() {
return { ...this._dependencies };
}
/*
* Starts all registered services
*/
async start() {
for (let service of this.services.values()) {
await service.startService();
}
}
/*
* Stops all registered services
*/
async stop() {
for (let service of this.services.values()) {
await service.stopService();
}
}
};
Then import, initialize and bind your services in the main file (don't forget to export just a class, not an object like you do it now).
server.js
const createServices = require('./factories/services.js');
const injector = require('./lib/injector');
const Injector = new injector();
const services = createServices(Injector, [require('./server/amq_listeners/fb_configs.listener'), require('./server/models/FBConfigs')]);
services.forEach((service, name) => {
Injector.register(name, service);
});
// Start services
Injector.start();
Inherit required classes to Service class and you will get an access to all dependencies there (don't forget to call super() from constructor). Like
models/FBConfigs.js
const Service = require('../lib/service');
class FBConfigs extends Service {
constructor(injector) {
super(injector);
const { FBConfigsListener } = this.dependencies;
...your code here
}
async startService() {
...run bg job or init some connection
}
async stopService() {
...stop bg job or close some connection
}
}
module.exports = FBConfigs;
Also you can pass some config object to createServices (I didn't include it here) with keys equal to service names and values containing config object and pass config to appropriate service.
It is caused by that circular dependency. You should avoid it or used very carefully.
In your case the fix is probably pretty simple, move the line var FBConfigs = require('../models/FBConfigs'); from listener at the end of the file as the last line (yes, even after the module.exports).
Edit: Actually it maybe is not enough as I checked the code more in detail. As you are not using Listener in FBConfig constructor, you can create method assignListener, remove this.listener from that constructor and call it later in server.js which will do the this.listener
Or the last solution, which is also "best practice". Do not export the instances. Export the classes only. Then in server.js create these instances after both are required.
I am trying to insert data from an API call to the database. The API call is done from the global services. When I tried to do it within the constructor method it is working. But if I try it from an other outside method from service I am getting an error “ERROR TypeError: Cannot read property 'setMetadataKey' of undefined(…)”.
import { Database } from './../providers/database/database';
import {Injectable} from '#angular/core';
#Injectable()
export class mAppService{
showValue: string;
public database;
userLoggedIn: boolean = false;
constructor(database: Database) {
//If i call the database method from here its working**
}
getConnect(ipaddress,portnumber){
try {
var t = ipaddress;
var p = portnumber;
client = new linear.client({transports: [
{type: 'websocket',
host: t, port: p}
]});
client.connect();
client.transport.onopen=this.onopen;
client.transport.onclose=this.onclose;
client.onconnect = client.ondisconnect = this.getStatus;
client.onnotify = this.recvNotify;
let stream$ = new Observable(observer => {
let interval = setInterval(() =>{
observer.next(client.transport.state);
/*observer.error(client.transport.onclose);
observer.complete();*/
},1000);
return() =>{
clearInterval(interval);
}
}).share();
return stream$;
} catch (error) {
console.log("catch.."+error);
}
}
recvNotify(m) {
var method = m.name;
var value = m.data;
var buttonType = "EFT_PAN";
this.database.setMetadataKey(value,buttonType).then((result) => {
console.log("Successfully saved the setMetadataKey.")
}, (error) => {
alert("ERROR: "+ error);
});
}
}
Can anyone help me to solve this please
How can I stub the redis publish method?
// module ipc
const redis = require('redis');
module.exports = class IPC {
constructor() {
this.pub = redis.createClient();
}
publish(data) {
this.pub.publish('hello', JSON.stringify(data));
}
}
and another module
// module service
module.exports = class Service {
constructor(ipc) {
this.ipc = ipc;
}
sendData() {
this.ipc.publish({ data: 'hello' })
}
}
How could I stub the private variable pub in IPC class?
I could stub the redis.createClient by using proxyquire, if I do that it will complain publish undefined
My current test code
let ipcStub;
before(() => {
ipcStub = proxyquire('../ipc', {
redis: {
createClient: sinon.stub(redis, 'createClient'),
}
})
});
it('should return true', () => {
const ipc = new ipcStub();
const ipcPublishSpy = sinon.spy(ipc, 'publish')
const service = new Service(ipc);
service.sendData();
assert.strictEqual(true, ipcPublishSpy.calledOnce);
})
You just need to set the spy on the publish method, no need for the proxyquire.
e.g.
import {expect} from 'chai';
import sinon from 'sinon';
class IPC {
constructor() {
this.pub = {
publish:() => {} //here your redis requirement
};
}
publish(data) {
this.pub.publish('hello', JSON.stringify(data));
}
}
class Service {
constructor(ipc) {
this.ipc = ipc;
}
sendData() {
this.ipc.publish({ data: 'hello' })
}
}
describe('Test Service', () => {
it('should call publish ', () => {
const ipc = new IPC;
sinon.spy(ipc.pub,'publish');
const service = new Service(ipc);
service.sendData();
expect(ipc.pub.publish.calledOnce).to.be.true;
});
});
I found a way to do it just by using sinon
Just need to create a stub instance using sinon.createStubInstance,
then this stub will have all the functionalities from sinon without the implementation of the object (only the class method name)
let ipcStub;
before(() => {
ipcStub = sinon.createStubInstance(IPC)
});
it('should return true', () => {
const ipc = new ipcStub();
const service = new Service(ipc);
service.sendData();
assert.strictEqual(true, ipc.publishSystem.calledOnce);
})
I have an angular2 service and I want it to do the following:
Ftp to remote server
Find a file read some lines from it
Build a 'results' json object and return to the calling component
So - actually I have steps 1 / 2 working - but of course its all 'async'. So what is happening is in my component I am doing this call to the service where this.ftp is the instance of my service:
this.servers = this.ftp.lookForServers();
Now this correctly calls the lookForServers method of my FTP service , which looks like this:
lookForServers(){
var servers = [];
var whereAreWe = 0;
var possibles = ["/path/to/servers/"];
for(var i=0;i<possibles.length;i++){
whereAreWe = i;
this.c.list(possibles[i],false,(err,list)=>{
for(var p=0;p<list.length;p++){
console.log(list[p]);
var server_version = this.grabLog(possibles[whereAreWe]+list[p].name);
servers.push({
name: list[p].name,
path: possibles[whereAreWe]+list[p].name,
version: server_version
});
}
});
}
return servers;
}
Now - the this.grabLog(possibles[whereAreWe]+list[p].name); function call ends up making further calls to the this.c - the FTP client, which of course is async, so this method returns almost immediately - whilst the callbacks continue to run. Those callbacks download a file, and then another callback function processes this file - again line by line, asynchronously picking out various details i want to store.
By the end of this chain - I have all my details in the final :
lineReader.on('close', () => { function - but of course my `this.ftp.lookForServers();` function call has long gone....and the component is none the wiser.
So how can I let this work happen asynchronously, and still pass back to the component my results JSON object once the work is complete? This is probably quite a simple question about how do I make a service call a component callback...?
You don't need it to run syncronously. You should make lookForServers (and the other function it's using) use observables, then subscribe to the result like this:
this.ftp.lookForServers().subscribe((data) => { this.servers = data });
Here are the implementations:
const Client = require('ftp');
const fs = require('fs');
const readline = require('readline');
import { NextObserver } from 'rxjs/Observer';
import { Observable } from 'rxjs/Rx';
interface server {
name: string;
path: string;
version: string;
java_version: string;
}
export class FTPClient {
username: string;
password: string;
host: string;
port: number;
c: any;
constructor() {
}
init(username, password, host, port) {
console.log("initiating FTP connection to:" + host + "on port:" + port);
this.username = username;
this.password = password;
this.host = host;
this.port = port;
this.c = new Client();
console.log("Client created");
}
connect() {
console.log("About to start connection");
this.c.on('ready', () => {
this.c.list((err: any, list: any) => {
if (err) throw err;
console.dir(list);
this.c.end();
});
});
// connect to localhost:21 as anonymous
var connectProps = {
host : this.host,
port : this.port,
user : this.username,
password : this.password
};
console.log("Connecting now...");
this.c.connect(connectProps);
}
public lookForServers(name: string): Observable<any[]> {
return Observable.create((observer: NextObserver <any[]>) => {
let servers = [];
let whereAreWe = 0;
let possibles = [ "/path/to/servers/" ];
for (var i = 0; i < possibles.length; i++) {
whereAreWe = i;
this.c.list(possibles[ i ], false, (err: any, list: any) => {
for (var p = 0; p < list.length; p++) {
this.grabMessagesLog(possibles[ whereAreWe ] + list[ p ].name)
.subscribe((data: any) => {
let server_version = data;
servers.push({
name : list[ p ].name,
path : possibles[ whereAreWe ] + list[ p ].name,
version : server_version
});
observer.next(servers);
observer.complete();
}
);
}
});
}
});
}
grabMessagesLog(path): Observable<any> {
return Observable.create((observer: NextObserver <any>) => {
let result = '';
let unix = Math.round(+new Date() / 1000);
this.c.binary(function(err) {
console.log(err);
});
this.c.get(path + "/logs/messages.log", (err, stream) => {
if (err) throw err;
stream.once('close', () => {
this.c.end();
this.getServerMetadataFromMessagesLog(unix + "_messages.log")
.subscribe((data) => {
stream.pipe(fs.createWriteStream(unix + "_messages.log"));
observer.next(data);
observer.complete();
});
});
});
});
}
getServerMetadataFromMessagesLog(path): Observable<any> {
return Observable.create((observer: NextObserver <any>) => {
let lineReader = readline.createInterface({
input : fs.createReadStream(path)
});
let server_version = "";
let java_version = "";
let line_no = 0;
lineReader.on('line', function(line) {
line_no++;
console.log("line number is:" + line_no);
if (line.includes("STUFF") && line.includes("FLAG2") && line_no == 2) {
var first = line.split("FLAG2")[ 1 ];
var last = first.split(" (")[ 0 ];
var version = "FLAG2" + last;
this.server_version = version;
console.log("version is:" + version);
}
if (line.includes("java.version =")) {
var javav = line.split("java.version =")[ 1 ];
this.java_version = javav;
lineReader.close();
}
console.log('Line from file:', line);
});
lineReader.on('close', () => {
var res = {
version : server_version,
java_version : java_version
};
alert("RES IS:" + JSON.stringify(res));
observer.next(res);
observer.complete();
});
});
}
}
Try using a recursive function with the $timeout function of Angular
function recursiveWait(server_version){
if(server_version != null){
return;
}
$timeout(function(){recursiveWait()}, 500);
}
And place it here:
console.log(list[p]);
var server_version = this.grabLog(possibles[whereAreWe]+list[p].name);
recursiveWait(server_version);
servers.push({
name: list[p].name,
This will ask the var if it's != null If it's equal it will call the function again in 500ms, if it's not it will return and exit the function, letting the code continue.