Calling a class function from within another class function - JS newb - javascript

This is probably more of a javascript question but I dont really know javascript in general, so im hoping its a simple fix.
So basically I am using two libraries that I want to work together. The beacon class listens for a bluetooth LE signal on a raspberry pi, at which point the onAdvertisement fucntion is called from the node-beacon-scanner library.
When this happens I want to publish a message to a message broker using MQTT (the other library), which should be a case of just calling client.publish(topic, message).
THe publish function works when I use it outside of the scanner functions, however if I call it inside one of the scanner functions such as onAdvertisement, it doesnt work, but also doesnt throw any errors.
I think it is an issue of me not understanding encapsualtion in node.js regarding modules.
Basically I want to know is there somthign im missing when calling the client.publish function from within the scanner.onadvertisement function? In the code below the publish call has been put in a function called pubBe.
Thankyou in advance!
const noble = require('#abandonware/noble');
const BeaconScanner = require("node-beacon-scanner");
const mqtt = require('mqtt');
var client = mqtt.connect('http://localhost:1883');
var scanner = new BeaconScanner();
var ledOn = false
client.on('connect', function () {
client.subscribe('led', function (err) {
if (!err) {
}
})
})
client.on('message', function (topic, message) {
// message is Buffer
console.log(message.toString())
client.end()
})
scanner.onadvertisement = (advertisement) => {
var beacon = advertisement["rssi"]
}
scanner.startScan().then(() => {
console.log("Scanning has started...")
pubBe(-10)
}).catch(error => {
console.error(error);
})
function pubBe(rssi){
if( rssi > -40 && !ledOn){
console.log('Turn LED on')
ledOn = true
payload = {"level": 100}
client.publish('led', JSON.stringify(payload))
}else if ( rssi < -40 && ledOn){
console.log('Turn LED off')
ledOn = false
payload = {"level": 0}
client.publish('led', JSON.stringify(payload))
}
}

Related

Angular 8 - handling SSE reconnect on error

I'm working on an Angular 8 (with Electron 6 and Ionic 4) project and right now we are having evaluation phase where we are deciding whether to replace polling with SSE (Server-sent events) or Web Sockets. My part of the job is to research SSE.
I created small express application which generates random numbers and it all works fine. The only thing that bugs me is correct way to reconnect on server error.
My implementation looks like this:
private createSseSource(): Observable<MessageEvent> {
return Observable.create(observer => {
this.eventSource = new EventSource(SSE_URL);
this.eventSource.onmessage = (event) => {
this.zone.run(() => observer.next(event));
};
this.eventSource.onopen = (event) => {
console.log('connection open');
};
this.eventSource.onerror = (error) => {
console.log('looks like the best thing to do is to do nothing');
// this.zone.run(() => observer.error(error));
// this.closeSseConnection();
// this.reconnectOnError();
};
});
}
I tried to implement reconnectOnError() function following this answer, but I just wasn't able to make it work. Then I ditched the reconnectOnError() function and it seems like it's a better thing to do. Do not try to close and reconnect nor propagate error to observable. Just sit and wait and when the server is running again it will reconnect automatically.
Question is, is this really the best thing to do? Important thing to mention is, that the FE application communicates with it's own server which can't be accessed by another instance of the app (built-in device).
I see that my question is getting some attention so I decided to post my solution. To answer my question: "Is this really the best thing to do, to omit reconnect function?" I don't know :). But this solution works for me and it was proven in production, that it offers way how to actually control SSE reconnect to some extent.
Here's what I did:
Rewritten createSseSource function so the return type is void
Instead of returning observable, data from SSE are fed to subjects/NgRx actions
Added public openSseChannel and private reconnectOnError functions for better control
Added private function processSseEvent to handle custom message types
Since I'm using NgRx on this project every SSE message dispatches corresponding action, but this can be replaced by ReplaySubject and exposed as observable.
// Public function, initializes connection, returns true if successful
openSseChannel(): boolean {
this.createSseEventSource();
return !!this.eventSource;
}
// Creates SSE event source, handles SSE events
protected createSseEventSource(): void {
// Close event source if current instance of SSE service has some
if (this.eventSource) {
this.closeSseConnection();
this.eventSource = null;
}
// Open new channel, create new EventSource
this.eventSource = new EventSource(this.sseChannelUrl);
// Process default event
this.eventSource.onmessage = (event: MessageEvent) => {
this.zone.run(() => this.processSseEvent(event));
};
// Add custom events
Object.keys(SSE_EVENTS).forEach(key => {
this.eventSource.addEventListener(SSE_EVENTS[key], event => {
this.zone.run(() => this.processSseEvent(event));
});
});
// Process connection opened
this.eventSource.onopen = () => {
this.reconnectFrequencySec = 1;
};
// Process error
this.eventSource.onerror = (error: any) => {
this.reconnectOnError();
};
}
// Processes custom event types
private processSseEvent(sseEvent: MessageEvent): void {
const parsed = sseEvent.data ? JSON.parse(sseEvent.data) : {};
switch (sseEvent.type) {
case SSE_EVENTS.STATUS: {
this.store.dispatch(StatusActions.setStatus({ status: parsed }));
// or
// this.someReplaySubject.next(parsed);
break;
}
// Add others if neccessary
default: {
console.error('Unknown event:', sseEvent.type);
break;
}
}
}
// Handles reconnect attempts when the connection fails for some reason.
// const SSE_RECONNECT_UPPER_LIMIT = 64;
private reconnectOnError(): void {
const self = this;
this.closeSseConnection();
clearTimeout(this.reconnectTimeout);
this.reconnectTimeout = setTimeout(() => {
self.openSseChannel();
self.reconnectFrequencySec *= 2;
if (self.reconnectFrequencySec >= SSE_RECONNECT_UPPER_LIMIT) {
self.reconnectFrequencySec = SSE_RECONNECT_UPPER_LIMIT;
}
}, this.reconnectFrequencySec * 1000);
}
Since the SSE events are fed to subject/actions it doesn't matter if the connection is lost since at least last event is preserved within subject or store. Attempts to reconnect can then happen silently and when new data are send, there are processed seamlessly.

Web worker Integration

I want to use web worker to handle my zipcode checker function, I haven't worked with web worker before so the concept is new to me
This is my zipcode function
``
function checkZipCode() {
event.preventDefault();
if(document.getElementById('zipcode').value < 20000) {
document.getElementById('zip-result').innerHTML = 'Sorry, we haven’t expanded to that area yet';
} else if (document.getElementById('zipcode').value >= 20000) {
document.getElementById('zip-result').innerHTML = 'We’ve got your area covered!'
} else {
return null
}
};
As per the docs workers are pretty easy to spin up:
//in a JS file
const myWorker = new Worker('./myWorker.js');//worker requested and top-level scope code executed
myWorker.postMessage('hello');
myWorker.addEventListener('message', e => {
//e.data will hold data sent from worker
const message = e.data;
console.log(message); // HELLO
//if it's just a one-time thing, you can kill the worker
myWorker.terminate();
}
myWorker.addEventListener('error', e => {//worker might throw an error
e.preventDefault();
console.log(e.message, `on line ${e.lineno}`);
});
//myWorker.js
//run whatever you need, just no DOM stuff, no window etc
console.log('this line runs when worker loads');
addEventListener('message', (e) => {
postMessage(e.data.toUpperCase());//up-case message and send it right back
});

Socket undefined, send don't send if undefined

Hello I wrote the following function to get someone his socket;
var getSocket = function(persId){
var socketid = users[persId].socket;
if(io.sockets.connected[socketid]){
return io.sockets.connected[socketid];
}
}
They are being emitted like so;
getSocket(372).emit({ ... });
Now if an user has disconnected before the socket is sent out it will result in a undefined socket error, which may cause issues.
is there any way by modifying the function (without having to check for if getSocket(372)) to make it not throw out an error? it's causing problems right now.
Thanks!
What you should do instead, is use socket.io function, that will check for that already.
io.to(room).emit()
On connection just join the user to a room named 372 (I believe that's the user id), and then emit to it:
io.on('connection', socket => {
const room = 372; // Get user id somehow
socket.join(room);
});
// Somewhere
io.to(372).emit(..)
Answering your specific question, you can return an object with an emit nop method. Also you have to check if the user exist before accessing to .socket. You can do that using destructuring.
const { socket: socketId } = users[persId] || {};
The full function should be:
const getSocket = function(persId){
const { socket: socketId } = users[persId] || {};
if(socketId && io.sockets.connected[socketId]){
return io.sockets.connected[socketId];
}
return { emit: () => {} }
}
And now you don't have to perform a check, since .emit will always exist.
getSocket(23123213).emit(); // Will no throw if 23123213 doesn't exist

Socket.io emitting values inside ES6 class

I wonder if any smart individuals could show me how to implement Socket.IO in an OOP environment with ES6 classes. The main problem I keep running into with Socket.io is passing around the server object, in my case called 'io'. Almost every example I've seen of socket.io has been pure spaghetti code, one file with many socket related events and logic. First I tried to pass the server object, io, to new class's constructor, but for some reason you end up with a nasty "RangeError: Maximum call stack size exceeded" error message. Then I've tried to wrap my classes in module.exports function which parameter should contain the io object. Which is fine for the first class. Let's say I pass the io object into my Game, great works as expected. But when I try to reference the io object down to the Round class(Game holds an array of Rounds) I can't. Because that is one hell of a bad practice in NodeJS, require should be global and not inside the modules/functions. So I'm once again back with the same issue.
app.js(where I require the main sockets file)
const io = socketio(server, { origins: '*:*' });
...
require('./sockets')(io);
sockets/index.js(where I initialize my game server, and handle incoming messages from client sockets)
const actions = require('../actions.js');
const chatSockets = require('./chat-sockets');
const climbServer = require('./climb-server');
const authFunctions = require('../auth-functions');
module.exports = (io) => {
io.on('connection', (client) => {
console.log('client connected...');
// Standard join, verify the requested room; if it exists let the client join it.
client.on('join', (data) => {
console.log(data);
console.log(`User ${data.username} tries to join ${data.room}`);
console.log(`Client joined ${data.room}`);
client.join(data.room);
});
client.on('disconnect', () => {
console.log('Client disconnected');
});
client.on(actions.CREATE_GAME, (hostParticipant) => {
console.log('CREATE_GAME', hostParticipant);
// Authorize socket sender by token?
// Create a new game, and set the host to the host participant
climbServer.createGame(io, hostParticipant);
});
client.on(actions.JOIN_GAME, (tokenizedGameId) => {
console.log('JOIN_GAME');
const user = authFunctions.getPayload(tokenizedGameId.token);
// Authorize socket sender by token?
// Create a new game, and set the host to the host participant
const game = climbServer.findGame(tokenizedGameId.content);
game.joinGame(user);
});
});
};
climbServer.js(My game server that keeps track of active games)
const actions = require('../actions.js');
const Game = require('../models/game');
const climbServer = { games: { }, gameCount: 0 };
climbServer.createGame = (io, hostParticipant) => {
// Create a new game instance
const newGame = new Game(hostParticipant);
console.log('New game object created', newGame);
// Store it in the list of game
climbServer.games[newGame.id] = newGame;
// Keep track
climbServer.gameCount += 1;
// Notify clients that a new game was created
io.sockets.in('climb').emit(actions.CLIMB_GAME_CREATED, newGame);
};
climbServer.findGame = gameId => climbServer.games[gameId];
module.exports = climbServer;
Game.js(ES6 class that SHOULD be able to emit to all connected sockets)
const UUID = require('uuid');
const Round = require('./round');
class Game {
// Constructor
constructor(hostParticipant) {
this.id = UUID();
this.playerHost = hostParticipant;
this.playerClient = null;
this.playerCount = 1;
this.rounds = [];
this.timestamp = Date.now();
}
joinGame(clientParticipant) {
console.log('Joining game', clientParticipant);
this.playerClient = clientParticipant;
this.playerCount += 1;
// Start the game by creating the first round
return this.createRound();
}
createRound() {
console.log('Creating new round at Game: ', this.id);
const newRound = new Round(this.id);
return this.rounds.push(newRound);
}
}
module.exports = Game;
Round.js(ES6 class that is used by the Game class(stored in a rounds array))
const actions = require('../actions.js');
class Round {
constructor(gameId) {
console.log('Initializing round of gameId', gameId);
this.timeLeft = 60;
this.gameId = gameId;
this.winner = null;
this.timestamp = Date.now();
// Start countdown when class is instantiated
this.startCountdown();
}
startCountdown() {
const countdown = setInterval(() => {
// broadcast to every client
io.sockets.in(this.gameId).emit(actions.ROUND_TIMER, { gameId: this.gameId, timeLeft: this.timeLeft });
if (this.timeLeft === 0) {
// when no time left, stop counting down
clearInterval(countdown);
this.onRoundEnd();
} else {
// Countdown
this.timeLeft -= 1;
console.log('Countdown', this.timeLeft);
}
}, 1000);
}
onRoundEnd() {
// Evaluate who won
console.log('onRoundEnd: ', this.gameId);
}
}
module.exports = Round;
TO SUMMARIZE with a question: How can I pass a reference of io to my classes so that I'm able to emit to connected sockets within these classes?
This doesn't necessarily have to be ES6 classes, it can be NodeJS objects using the .prototype property. I just want a mainatainable way to handle my game server with sockets... ANY HELP IS APPRECIATED!
After hours upon hours I figured out a solution. If anyone runs into the same thing check my solution out below. Not the best, but much better than putting all socket related code in one file...
Game.js(ES6 Class). Focus on the first line containing 'module.exports'.
const GameFactory = require('../models/game');
const climbServer = { games: { }, gameCount: 0 };
climbServer.createGame = (io, hostParticipant) => {
// Create a new game instance
const Game = GameFactory(io);
const newGame = new Game(hostParticipant);
console.log('New game object created', newGame);
// Store it in the list of game
climbServer.games[newGame.id] = newGame;
// Keep track
climbServer.gameCount += 1;
return newGame;
};
climbServer.findGame = gameId => climbServer.games[gameId];
module.exports = climbServer;
The trick is to use this factory pattern where you first declare:
const GameFactory = require('../models/game');
Then initialize the factory with passing in the Socket.io server object, in my case 'io'. IF YOU pass it in via the constructor you end up with a RangeError, therefore this is the only way. Once again not certain how this code performs in comparison to spaghetti code.
const Game = GameFactory(io);
Finally, you can now instantiate instances of your class:
const newGame = new Game(hostParticipant);
If anyone have improvements or thoughts, please leave me a comment. Still uncertain about the quality of this code.

How to Play Audio File Into Channel?

How do you play an audio file from a Discord bot? Needs to play a local file, be in JS, and upon a certain message being sent it will join the user who typed the message, and will play the file to that channel.
GitHub Project: LINK
In order to do this there are a few things you have to make sure of first.
Have FFMPEG installed & the environment path set for it in Windows [link]
Have Microsoft Visual Studio (VS) installed [link]
Have Node.js installed.[link]
Have Discord.js installed in VS.
From there the steps are quite simple. After making your project index.js you will start typing some code. Here are the steps:
Add the Discord.js dependency to the project;
var Discord = require('discord.js');
Create out client variable called bot;
var bot = new Discord.Client();
3. Create a Boolean variable to make sure that the system doesn't overload of requests;
var isReady = true;
Next make the function to intercept the correct message;
bot.on('message', message =>{ENTER CODE HERE});
Create an if statement to check if the message is correct & if the bot is ready;
if (isReady && message.content === 'MESSAGE'){ENTER CODE HERE}
Set the bot to unready so that it cannot process events until it finishes;
isReady = false;
Create a variable for the channel that the message-sender is currently in;
var voiceChannel = message.member.voice.channel;
Join that channel and keep track of all errors;
voiceChannel.join().then(connection =>{ENTER CODE HERE}).catch(err => console.log(err));
Create a refrence to and play the audio file;
const dispatcher = connection.play('./audiofile.mp3');
Slot to wait until the audio file is done playing;
dispatcher.on("end", end => {ENTER CODE HERE});
Leave channel after audio is done playing;
voiceChannel.leave();
Login to the application;
bot.login('CLIENT TOKEN HERE');
After you are all finished with this, make sure to check for any un-closed brackets or parentheses. i made this because it took my hours until I finally found a good solution so I just wanted to share it with anybody who is out there looking for something like this.
thanks so much!
One thing I will say to help anyone else, is things like where it says ENTER CODE HERE on step 10, you put the code from step 11 IE:
dispatcher.on("end", end => voiceChannel.leave());
As a complete example, this is how I have used it in my message command IF block:
if (command === "COMMAND") {
var VC = message.member.voiceChannel;
if (!VC)
return message.reply("MESSAGE IF NOT IN A VOICE CHANNEL")
VC.join()
.then(connection => {
const dispatcher = connection.playFile('c:/PAtH/TO/MP3/FILE.MP3');
dispatcher.on("end", end => {VC.leave()});
})
.catch(console.error);
};
I went ahead an included Nicholas Johnson's Github bot code here, but I made slight modifications.
He appears to be creating a lock; so I created a LockableClient that extends the Discord Client.
Never include an authorization token in the code
auth.json
{
"token" : "your-token-here"
}
lockable-client.js
const { Client } = require('discord.js')
/**
* A lockable client that can interact with the Discord API.
* #extends {Client}
*/
class LockableClient extends Client {
constructor(options) {
super(options)
this.locked = false
}
lock() {
this.setLocked(true)
}
unlock() {
this.setLocked(false)
}
setLocked(locked) {
return this.locked = locked
}
isLocked {
return this.locked
}
}
module.exports = LockableClient;
index.js
const auth = require('./auth.json')
const { LockableClient } = require('./lockable-client.js')
const bot = new LockableClient()
bot.on('message', message => {
if (!bot.isLocked() && message.content === 'Gotcha Bitch') {
bot.lock()
var voiceChannel = message.member.voiceChannel
voiceChannel.join().then(connection => {
const dispatcher = connection.playFile('./assets/audio/gab.mp3')
dispatcher.on('end', end => voiceChannel.leave());
}).catch(err => console.log(err))
bot.unlock()
}
})
bot.login(auth.token)
This is an semi old thread but I'm going to add code here that will hopefully help someone out and save them time. It took me way too long to figure this out but dispatcher.on('end') didn't work for me. I think in later versions of discord.js they changed it from end to finish
var voiceChannel = msg.member.voice.channel;
voiceChannel.join()
.then(connection => {
const dispatcher = connection.play(fileName);
dispatcher.on("finish", end => {
voiceChannel.leave();
deleteFile(fileName);
});
})
.catch(console.error);
Note that fileName is a string path for example: fileName = "/example.mp3". Hopefully that helps someone out there :)
Update: If you want to detect if the Audio has stopped, you must subscribe to the speaking event.
voiceChannel
.join()
.then((connection) => {
const dispatcher = connection.play("./audio_files/file.mp3");
dispatcher.on("speaking", (speaking) => {
if (!speaking) {
voiceChannel.leave();
}
});
})

Categories

Resources