Trigger a Client function with a service worker - javascript

I have a chat platform I'm building for fun. I have built notifications into the platform. On a browser it looks like this when you click the notification, with noticeSource being the user the new message is from. So it knows, to focus the page and then use the loadChat() function to load the chat associated with that user.
note.onclick=function(){
parent.focus();
console.log(this);
loadChat(noticeSource,el);
this.close()
};
However, I am also running this as PWA for android, so when it detects its on a phone it uses the Service worker showNotification method. I use this function to detect the click and to bring the app into focus.
self.addEventListener('notificationclick', function (event)
{
console.log("EVENT!",event.notification.data);
var target=event.notification.data;
const rootUrl = new URL('./index.php', location).href;
event.notification.close();
event.waitUntil(
clients.matchAll().then(matchedClients => {
for (let client of matchedClients){
console.log(client.url,rootUrl);
if (client.url.indexOf(rootUrl) >= 0){
console.log("Focus1");
return client.focus();
}
}
return clients.openWindow(rootUrl).then(
function (client) {
console.log("Focus2");
client.focus();
}
);
})
);
});
What I can't figure out is how to communicate between the SW and the client that the client should run the loadChat() function and pass along the user it should run it for. In general if someone could point me toward a resource that explains how to communicate between the SW and the client that would be appreciated. I've looked but haven't found anything and I am assuming it's because I'm not really clear on how service workers are suppose to work.

As is often the case, I found an answer after I posted the question. The client object has a method postMessage(), so once I have my client found I can use that to post a message with the userName, on the client side I can use eventListener navigator.serviceWorker.onmessage to catch messages from the sw and execute functions.
self.addEventListener('notificationclick', function (event)
{
console.log("EVENT!",event.notification.data);
var target=event.notification.data;
const rootUrl = new URL('./index.php', location).href;
event.notification.close();
console.log(clients);
event.waitUntil(
clients.matchAll().then(matchedClients => {
for (let client of matchedClients){
console.log(client.url,rootUrl);
if (client.url.indexOf(rootUrl) >= 0){
console.log(client);
client.focus();
client.postMessage(event.notification.data);
return ;
}
}
return clients.openWindow(rootUrl).then(
function (client) {
console.log("Focus2");
client.focus();
}
);
})
);
});
and on the client side
sw=navigator.serviceWorker;
sw.register('sw.js').then(function(registration){console.log("Scope:",registration.scope)});
sw.onmessage=function(event){
loadChat(event.data,document.getElementById(event.data));
}

Related

Creating a websocket ping in vanilla browser JS

I've searched a lot but cannot find a specific example of how to send - using javascript - a ping over a websocket (https://www.rfc-editor.org/rfc/rfc6455#section-5.5.2) from the browser. I'm told that the server I'm using will reply to a ping, but I've been trying variations on the following theme without success.
var sockets= {};
// ... code that attaches sockets to this object
for (let key in sockets) {
console.log("about to send pings to", key)
sockets[key].send(0x9)
}
const ws = new WebSocket('URL goes here');
ws.onopen = () => { ws.send('ping') };
ws.onmessage = (data) => { console.log(data); } // this should be pong
EDIT the script that you'll need,
<script src="https://cdnjs.cloudflare.com/ajax/libs/web-socket-js/1.0.0/web_socket.min.js"></script>

How can I do Ping/Pong between JavaScript and NodeJS WebSocket?

I'm currently developing a NodeJS WebSocket server. To detect broken connections I've followed this guide here:
https://github.com/websockets/ws#how-to-detect-and-close-broken-connections
The server side works really good but the client makes problems because I can't find a ping function.
Does anyone has an idea how I can get the client part done without the library?
const WebSocket = require('ws');
function heartbeat() {
clearTimeout(this.pingTimeout);
// Use `WebSocket#terminate()`, which immediately destroys the connection,
// instead of `WebSocket#close()`, which waits for the close timer.
// Delay should be equal to the interval at which your server
// sends out pings plus a conservative assumption of the latency.
this.pingTimeout = setTimeout(() => {
this.terminate();
}, 30000 + 1000);
}
const client = new WebSocket('wss://echo.websocket.org/');
client.on('open', heartbeat);
client.on('ping', heartbeat);
client.on('close', function clear() {
clearTimeout(this.pingTimeout);
});
One main problem is that there is no ping method I think:
client.on('open') -> client.onopen available in JavaScript
client.on('close') -> client.onclose available in JavaScript
client.on('ping') -> How? Just how?
There is no Javascript API to send ping frames or receive pong frames. This is either supported by your browser, or not. There is also no API to enable, configure or detect whether the browser supports and is using ping/pong frames.
https://stackoverflow.com/a/10586583/7377682
Sad but true, in case of the ping frame, the API does not support it as mentioned in previous answers.
The most popular workaround is to listen to the close event and try to reconnect to the server using an interval.
This tutorial is easy to understand and contains most use-cases to begin with WS:
var ws = new WebSocket("ws://localhost:3000/ws");
let that = this; // cache the this
var connectInterval;
var check = () => {
const { ws } = this.state;
if (!ws || ws.readyState == WebSocket.CLOSED) this.connect(); //check if websocket instance is closed, if so call `connect` function.
};
// websocket onopen event listener
ws.onopen = () => {
console.log("connected websocket main component");
this.setState({ ws: ws });
that.timeout = 250; // reset timer to 250 on open of websocket connection
clearTimeout(connectInterval); // clear Interval on on open of websocket connection
};
// websocket onclose event listener
ws.onclose = e => {
console.log(
`Socket is closed. Reconnect will be attempted in ${Math.min(
10000 / 1000,
(that.timeout + that.timeout) / 1000
)} second.`,
e.reason
);
that.timeout = that.timeout + that.timeout; //increment retry interval
connectInterval = setTimeout(this.check, Math.min(10000, that.timeout)); //call check function after timeout
};
// websocket onerror event listener
ws.onerror = err => {
console.error(
"Socket encountered error: ",
err.message,
"Closing socket"
);
ws.close();
};
I think what you are look for on the client is onmessage:
client.onmessage = function (event) {
console.log(event.data);
}
All messages sent from the server can be listened to this way. See https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications

SOCKJS client - Detect server is unreachable

I'm using vertxbus that internally built upon sockjs and I have a basic question.
When I call 'onopen' for the first time in order to establish a connection, How can I know that server is down?
At this point when I call 'onopen' and pass a callback function - if server is down the method is stuck and doesn't return at all.
Thanks!
You can check this code , where I'm using EventBus.
Here is the Reference code
this.eventBus = new EventBus(this.URL);
this.eventBus.onopen = (e) => {
this._opened = true;
console.log("open connection");
this.callHandlers('open', e);
this.eventBus.publish("http://localhost:8082", "USER LOGIN INFO");
this.eventBus.registerHandler("http://localhost:8081/pushNotification", function (error, message) {
console.log(message.body);
//$("<div title='Basic dialog'>Test message</div>").dialog();
});
}
this.eventBus.onclose = (e) => {
this.callHandlers('close', e);
}
}

Service Worker Respond To Fetch after getting data from another worker

I am using service workers to intercept requests for me and provide the responses to the fetch requests by communicating with a Web worker (also created from the same parent page).
I have used message channels for direct communication between the worker and service worker. Here is a simple POC I have written:
var otherPort, parentPort;
var dummyObj;
var DummyHandler = function()
{
this.onmessage = null;
var selfRef = this;
this.callHandler = function(arg)
{
if (typeof selfRef.onmessage === "function")
{
selfRef.onmessage(arg);
}
else
{
console.error("Message Handler not set");
}
};
};
function msgFromW(evt)
{
console.log(evt.data);
dummyObj.callHandler(evt);
}
self.addEventListener("message", function(evt) {
var data = evt.data;
if(data.msg === "connect")
{
otherPort = evt.ports[1];
otherPort.onmessage = msgFromW;
parentPort = evt.ports[0];
parentPort.postMessage({"msg": "connect"});
}
});
self.addEventListener("fetch", function(event)
{
var url = event.request.url;
var urlObj = new URL(url);
if(!isToBeIntercepted(url))
{
return fetch(event.request);
}
url = decodeURI(url);
var key = processURL(url).toLowerCase();
console.log("Fetch For: " + key);
event.respondWith(new Promise(function(resolve, reject){
dummyObj = new DummyHandler();
dummyObj.onmessage = function(e)
{
if(e.data.error)
{
reject(e.data.error);
}
else
{
var content = e.data.data;
var blob = new Blob([content]);
resolve(new Response(blob));
}
};
otherPort.postMessage({"msg": "content", param: key});
}));
});
Roles of the ports:
otherPort: Communication with worker
parentPort: Communication with parent page
In the worker, I have a database say this:
var dataBase = {
"file1.txt": "This is File1",
"file2.txt": "This is File2"
};
The worker just serves the correct data according to the key sent by the service worker. In reality these will be very large files.
The problem I am facing with this is the following:
Since I am using a global dummyObj, the older dummyObj and hence the older onmessage is lost and only the latest resource is responded with the received data.
In fact, file2 gets This is File1, because the latest dummyObj is for file2.txt but the worker first sends data for file1.txt.
I tried by creating an iframe directly and all the requests inside it are intercepted:
<html>
<head></head>
<body><iframe src="tointercept/file1.txt" ></iframe><iframe src="tointercept/file2.txt"></iframe>
</body>
</html>
Here is what I get as output:
One approach could be to write all the files that could be fetched into IndexedDB in the worker before creating the iframe. Then in the Service Worker fetch those from indexed DB. But I don't want to save all the resources in IDB. So this approach is not what I want.
Does anybody know a way to accomplish what I am trying to do in some other way? Or is there a fix to what I am doing.
Please Help!
UPDATE
I have got this to work by queuing the dummyObjs in a global queue instead of having a global object. And on receiving the response from the worker in msgFromW I pop an element from the queue and call its callHandler function.
But I am not sure if this is a reliable solution. As it assumes that everything will occur in order. Is this assumption correct?
I'd recommend wrapping your message passing between the service worker and the web worker in promises, and then pass a promise that resolves with the data from the web worker to fetchEvent.respondWith().
The promise-worker library can automate this promise-wrapping for you, or you could do it by hand, using this example as a guide.
If you were using promise-worker, your code would look something like:
var promiseWorker = new PromiseWorker(/* your web worker */);
self.addEventListener('fetch', function(fetchEvent) {
if (/* some optional check to see if you want to handle this event */) {
fetchEvent.respondWith(promiseWorker.postMessage(/* file name */));
}
});

webrtc data channel not workinig

I am trying to setup a text chat using webrtc data channel.
my network his a private network so i can't use any dependencies or frameworks like peerjs or similar.
I published my project on java play server so
i have one webrtsPeerConnection object that user can choose to initiate connection or to accept connection from someone else.
the problem : data channel is setup and active for the user who initiate the call.
but for the user who joined the call data channel don't activate and onDataChannel event never fires.
any suggestions??
Thanks in advance!
my code java script:
// init peer connection and data channel objects
var pc = new RTCpeerConnection(null,null);
var DC,DCnam;
function InitConnection(){
//created RTCpeerConnection
createDataChannel();
pc.createOffer(function(desc){
pc.setLocalDescripyion(desc,function(){},function(){})
enter code here
})
}
//create data channel
function createDataChannel(){
DC = pc.createDataChannel(DCname,{
reliable:true
});
}
//when user A call user B set remote description and create answer
function CheckCalls(){
&http.get("/checkCslls").success(function(data){
if(data[0])
{
//get offer and offerer
offerer = data[0].offerer;
pc.odataChannel function(e){
console.log(e);
}
pc.setRemoteDescription(new sessionDescription()data[0].offer));
pc.createAnswer(function(answerDesc){
pc.setLocalDescripyion(answerDesc);
})
}
})
}
//when user B send answer
(onDataChannel event fires on user A object).
function checkAnswers(){
$http.get("/checkAnswers").success(function(data){
if(data.answer){
pc.setRemoteDescription(new sessionDescription(data.answer));
}
})
It can be that you misspelled the callback:
pc.odataChannel function(e){
console.log(e);
}
it is ondatachannel with an "n" and lower case "c" and a "=" to define the function and callbacks to do something when messages are delivered;
something like:
var receiveChannel;
pc.ondatachannel = function (event) {
console.log('Receive Channel Callback');
receiveChannel = event.channel;
receiveChannel.onmessage = gotCMessage;
receiveChannel.onopen = dcOpen;
receiveChannel.onclose = dcClose;
console.log(event);
}

Categories