Can we access the Workbox background sync queue object in outside service worker file, in any of my application files say index.php?
I believe you can use the BroadcastChannel API, something like this:
// From Service Worker, connect to the channel named "my_bus".
const channel = new BroadcastChannel('my_bus');
// Send a message on "my_bus".
channel.postMessage('This is a test message.');
// From client, listen for messages on "my_bus".
const channel = new BroadcastChannel('my_bus');
channel.onmessage = function(e) {
console.log('Received', e.data);
};
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 have an application and I would like prevent message handling from another tab.
//client
var msg_chan = new MessageChannel();
// Handler for recieving message reply from service worker
msg_chan.port1.onmessage = function(event){
if(event.data.error){
reject(event.data.error);
}else{
resolve(event.data);
}
};
const id = (new Date().getTime() / 1000).toFixed()
// Send message to service worker along with port for reply
navigator.serviceWorker.controller.postMessage({ message, id }, [msg_chan.port2]);
In this way, if multiple tabs are opened service worker handles the message in both tabs.
// service-worker.js
self.addEventListener('message', event => {
console.log(event.data) // This prints two times if two tabs are open
})
Is it possible to handle message for once?
You can assign each tab a clientId and send it on the messages.
I'm trying to figure out what happens if I have a service worker registered on a live site called sw.js and then I rename the service worker to service-worker.js. Now the old one isn't found but it is still showing the old cached version.
How long does it take for it to register the new renamed service worker or how does this work at all?
Edit
This is how I have register the service worker in a react application:
componentDidMount() {
if ("serviceWorker" in navigator) {
navigator.serviceWorker
.register("/service-worker.js")
.then(registration => {
console.log("service worker registration successful: ", registration);
})
.catch(err => {
console.warn("service worker registration failed", err.message);
});
}
}
The newly created service worker (renamed) cannot take over the old one because the old one is still active and controlling the client.
the new service worker(renamed one) will wait until the existing worker is controlling zero clients.
Now imagine a service worker sw.js installed and active (controlling the client),
Chrome will visualize the process for you like this
1. The service worker is registered and active
2. Now let's rename the service worker file to sw2.js
You can see that chrome is telling you that something has changed about the service worker. but the current one will keep controlling the client until you force the new one to take control by clicking on the skipWaitting button or by flushing your cache. clicking on the button will cause the sw2.js to take controll over the sw1.js
Now if you need to do this programmatically, you can do it in the install event inside your service worker by calling self.skipWaiting().
self.addEventListener('install', (e) => {
let cache = caches.open(cacheName).then((c) => {
c.addAll([
// my files
]);
});
self.skipWaiting();
e.waitUntil(cache);
});
The following animated image from Jake Archibald's article The Service Worker Lifecycle can make the idea more clear.
You have to update the instance creation code to reflect this change where your shared worker is being initialized and used, for example your current code would look like
var worker = new SharedWorker("ws.js");
That will need to be updated to
var worker = new SharedWorker("service-worker.js");
I was able to solve it by setting up a server which listens to both /service-worker.js and /sw.js get requests.
Since the service worker was renamed from sw.js to service-worker.js it was not finding the old service worker at http://example.com/sw.js so what I did was the following:
createServer((req, res) => {
const parsedUrl = parse(req.url, true);
const { pathname } = parsedUrl;
// new service worker
if (pathname === "/service-worker.js") {
const filePath = join(__dirname, "..", pathname);
app.serveStatic(req, res, filePath);
// added new endpoint to fetch the new service worker but with the
// old path
} else if (pathname === "/sw.js") {
const filePath = join(__dirname, "..", "/service-worker.js");
app.serveStatic(req, res, filePath);
} else {
handle(req, res, parsedUrl);
}
}).listen(port, err => {
if (err) throw err;
console.log(`> Ready on http://localhost:${port}`);
});
As you can see, I added a second path to serve the same service worker but with the /sw.js endpoint, one for the old sw.js and the other one for the newer service-worker.js.
Now when old visitors that have the old active sw.js will download the newer one and upon revisit, they will automatically fetch the newer renamed service-worker.js service worker.
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
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