I'm playing around with WebWorkers. Somehow I had the idea to let the different instances of a page know when another one is closed. Therefore I wrote a Shared Worker and it works fine.
But now I want a Dedicated Worker to act as an interface to the Shared Worker. So that expensive actions in the UI won't affect the continous communication with the Shared Worker.
But I get the error, SharedWorker was not defined. An idea would be to use MessageChannel, but I want it to run at least in Firefox and Chrome and as far I know, Firefox still doesn't have a working implementation of MessageChannel.
So - are there any workarounds for this problem?
You can't create a shared worker object in the dedicated worker. However, you can create a shared worker in the main UI thread and pass its port to the dedicated worker, so they can communicate directly.
As an example, in main thread create both workers, and transfer the port object of the shared to the dedicated:
var sharedWorker = new SharedWorker("worker-shared.js");
sharedWorker.port.start();
var dedicatedWorker = new Worker("worker-dedicated.js");
dedicatedWorker.postMessage({sharedWorkerPort: sharedWorker.port}, [sharedWorker.port]);
In the shared worker you can post messages on this port:
self.onconnect = function(e) {
var port = e.ports[0];
self.setInterval(function() {
port.postMessage('sent from shared worker');
}, 1000);
};
And in the dedicated you can react to them
self.onmessage = function(e) {
var sharedWorkerPort = e.data.sharedWorkerPort;
sharedWorkerPort.onmessage = function(e) {
console.log('received in dedicated worker', e.data);
};
};
You can see this working at http://plnkr.co/edit/ljWnL7iMiCMtL92lPIAm?p=preview
Related
My project works as intended except that I have to refresh the browser every time my keyword list sends something to it to display. I assume it's my inexperience with Expressjs and not creating the route correctly within my websocket? Any help would be appreciated.
Browser
let socket = new WebSocket("ws://localhost:3000");
socket.addEventListener('open', function (event) {
console.log('Connected to WS server')
socket.send('Hello Server!');
});
socket.addEventListener('message', function (e) {
const keywordsList = JSON.parse(e.data);
console.log("Received: '" + e.data + "'");
document.getElementById("keywordsList").innerHTML = e.data;
});
socket.onclose = function(code, reason) {
console.log(code, reason, 'disconnected');
}
socket.onerror = error => {
console.error('failed to connect', error);
};
Server
const ws = require('ws');
const express = require('express');
const keywordsList = require('./app');
const app = express();
const port = 3000;
const wsServer = new ws.Server({ noServer: true });
wsServer.on('connection', function connection(socket) {
socket.send(JSON.stringify(keywordsList));
socket.on('message', message => console.log(message));
});
// `server` is a vanilla Node.js HTTP server, so use
// the same ws upgrade process described here:
// https://www.npmjs.com/package/ws#multiple-servers-sharing-a-single-https-server
const server = app.listen(3000);
server.on('upgrade', (request, socket, head) => {
wsServer.handleUpgrade(request, socket, head, socket => {
wsServer.emit('connection', socket, request);
});
});
In answer to "How to Send and/or Stream array data that is being continually updated to a client" as arrived at in comment.
A possible solution using WebSockets may be to
Create an interface on the server for array updates (if you haven't already) that isolates the array object from arbitrary outside modification and supports a callback when updates are made.
Determine the latency allowed for multiple updates to occur without being pushed. The latency should allow reasonable time for previous network traffic to complete without overloading bandwidth unnecessarily.
When an array update occurs, start a timer if not already running for the latency period .
On timer expiry JSON.stringify the array (to take a snapshot), clear the timer running status, and message the client with the JSON text.
A slightly more complicated method to avoid delaying all push operations would be to immediately push single updates unless they occur within a guard period after the most recent push operation. A timer could then push modifications made during the guard period at the end of the guard period.
Broadcasting
The WebSockets API does not directly support broadcasting the same data to multiple clients. Refer to Server Broadcast in ws documentation for an example of sending data to all connected clients using a forEach loop.
Client side listener
In the client-side message listener
document.getElementById("keywordsList").innerHTML = e.data;
would be better as
document.getElementById("keywordsList").textContent = keywordList;
to both present keywords after decoding from JSON and prevent them ever being treated as HTML.
So I finally figured out what I wanted to accomplish. It sounds straight forward after I learned enough and thought about how to structure the back end of my project.
If you have two websockets running and one needs information from the other, you cannot run them side by side. You need to have one encapsulate the other and then call the websocket INSIDE of the other websocket. This can easily cause problems down the road for other projects since now you have one websocket that won't fire until the other is run but for my project it makes perfect sense since it is locally run and needs all the parts working 100 percent in order to be effective. It took me a long time to understand how to structure the code as such.
I am trying to create an app that uses websockets, but am running into the dreaded "multiple connection" issue where everytime the page is reloaded rather than closing and reopening a new websocket, another connection is simply added to the list. My question is if anyone knows the best/proper way to close a Socket.io websocket from Angular 2/4+.
Here's what I have as far as code:
service.ts
getUserSocket(userID: string): Observable<any> {
return new Observable(_Observer => {
// Setup the socket namespace subscription
this.UserSocket = io(NTC_API_URL + `/user/${userID}`, { secure: true });
this.UserSocket.on('message', _Message => {
console.log(_Message);
})
})
}
closeUserSocket() {
this.UserSocket.disconnect();
this.UserSocket.close();
}
component.ts
ngOninit() {
// Setup the User Socket
this.UserSocket = this._UsersService.getUserSocket(this.currentUser.userID)
.subscribe(_UserSocketMessage => {
console.log(_UserSocketMessage);
})
}
ngOnDestroy() {
this.UserSocket.unsubscribe();
this._UsersService.closeUserSocket();
}
This doesn't seem to be working as I can still watch the connections pile up just by logging in an out of the application which I have confirmed calls the component to be destroyed.
Another option I tried was using the once listener instead of the on listener, which worked, but I am unsure about the side effects and have read several places that it's not necessarily a fix for not closing the connection, which I can understand.
After quite a bit of tinkering and research it occurred to me that web sockets are made to be resilient. In other words, since they were engineered to reconnect if possible, focusing on disconnect and close was the wrong approach. It was far easier to enforce policies with regard to the Creation of web socket connections.
For starters, I had to refactor a few things on the server side to make sure particular namespaces were only initialized once. This wasn't achieved so much by altering initialization code as it was by making sure that web socket initialization only occurred in places that either only happened on startup, or during processes that are only highly likely to happen once such as a particular user being created.
On the angular side it was also pretty straightforward in that all I had to do was assign the socket to a property within the service, then check if it existed before making the socket connection. Hence only creating the connection if no connection existed previously.
service.ts
// Setup the socket namespace subscription
if (!this.UserSocket) {
// Initialize user permissions socket
this.UserSocket = io(NTC_API_URL + `/user/${this.LocalUser.userID}`, { secure: true });
}
The MDN Documentation on SharedWorkers states:
The SharedWorker interface represents a specific kind of worker that can be accessed from several browsing contexts, such as several windows, iframes or even workers.
To me this sounds as if SharedWorkers should be able to directly exchange messages. However, if I try to access a SharedWorker from within another SharedWorker, namely with
var worker = new SharedWorker("path/to/file.js");
I get
ReferenceError: SharedWorker is not defined
Did I just misread the documentation, or is there another way to do this?
Although you don't seem to be able to create a shared worker from a shared worker, you can communicate between them by creating them in the main thread, and passing the MessagePort object of one to the other. Note you have to include the port in the transfer list argument to postMessage: it can't be copied.
For example, in the main thread create the workers, and send the port of one to the other:
var myWorker1 = new SharedWorker("worker1.js");
myWorker1.port.start();
var myWorker2 = new SharedWorker("worker2.js");
myWorker2.port.start();
myWorker2.port.postMessage({worker1Port: myWorker1.port}, [myWorker1.port]);
In the first worker you can send messages on a port:
self.onconnect = function(e) {
var port = e.ports[0];
self.setInterval(function() {
port.postMessage('sent from worker 1');
}, 1000);
};
and then in the second worker you can save the incoming port object, and respond to messages received on it.
self.onconnect = function(e) {
var port = e.ports[0];
port.onmessage = function(e) {
var worker1Port = e.data.worker1Port;
worker1Port.onmessage = function(e) {
console.log('received in worker 2', e.data);
};
};
};
You can see this working at http://plnkr.co/edit/XTOej1b1PHfWuC9LHeZc?p=preview
I'm able to send socket.io connections from my extension to my server, but I cannot hear emits from my server inside my extension. I've found conflicting answers regarding this question:
Opening a Socket.IO connection in a google chrome extension says this can't be done; and
Cross-domain connection in Socket.IO says it can.
Is there any special configuration I must change in order to accept emits from my socket server?
EDIT:
(Note: I'm using AngularJS, but it shouldn't be relevant to this question)
socketFactory.js:
myApp.factory('socketFactory', function($rootScope) {
var socket = io.connect('//dev.mydomain.com', {'path': '/api/socket'});
return socket;
}
inject.js:
var packetData = { 'some':'data', 'roomId':'123abc' };
socketFactory.emit('room:join', packetData);
...
socketFactory.on('room:update', function (data) {
console.log('Received data from socket server');
console.log(data)
}
socket.js (server-side):
socket.on('room:join', function ( data ) {
// Setting socketId to detect disconnect
data.user.socketId = socket.id;
socket.join( data.roomId, function() {
// Some code ...
io.sockets.to(data.roomId).emit('room:update', {'some':'data', 'roomId': '123abc'});
}
});
That's the basic setup of my connection. This system works perfectly when I launch the app in non-extension mode (we're making an extension to emulate our webapp), but when in the extension, room:update is never triggered.
EDIT 2:
We did a console.log on the socket object (generated on connect) in socket.js. Inside the headers, it appears the host is dev.mydomain.com, while the referrer is www.othersite.com. Could this be the problem? What does "host" refer to? Host of the socket server, or host of the socket listener? In the latter case, it would make sense it's not reaching www.othersite.com over which we have the extension running.
EDIT 3: ...And it started working out of nowhere. Must be a race condition somewhere. Closing the question as no longer relevant.
It suddenly works. Probably a race condition.
I am working on Backbone based application (It is also an tablet application), which uses websockets, .
Websockets are working well, but Application doesn't have any logic for error/network handing.
App should show message to user that he is disconnected, and app should retry to connect again, and once its connected back, things starts working again, like gmail.
I haven't written any server-side websocket code, and I am new to Websockets.
Is there any good article on how to handle network disconnection, reconnection for Websockets?
I am unable to find how to set timeout on Websockets, Or how to reconnect again etc.
As its an tablet app, so there will be frequent network disconnections, also App will be getting into sleep mode. Is there any special considerations or practices ?
According to this article, you can use try/catch to treat erroneous cases, like not being able to connect:
function connect(){
try{
var socket;
var host = "ws://localhost:8000/socket/server/startDaemon.php";
var socket = new WebSocket(host);
message('<p class="event">Socket Status: '+socket.readyState);
socket.onopen = function(){
message('<p class="event">Socket Status: '+socket.readyState+' (open)');
}
socket.onmessage = function(msg){
message('<p class="message">Received: '+msg.data);
}
socket.onclose = function(){
message('<p class="event">Socket Status: '+socket.readyState+' (Closed)');
}
} catch(exception){
message('<p>Error'+exception);
}
}
I couldn't find any mention of setting timeout duration even in the WebSocket spec, that might not be possible.