Does BroadcastChannel work on the same page? - javascript

My web page has
var bc = new BroadcastChannel('Consumer');
bc.onmessage = function(event) {
alert("a");
}
bc.postMessage("hello");
It broadcasts a message, and the page is also required to receive the same message.
However it doesn't work. Did I miss anything?

You can create two instances of BroadcastChannel on your page. One can act as a broadcaster for messages, the other one for receiving messages.
var broadcaster = new BroadcastChannel('Consumer');
var messageReceiver= new BroadcastChannel('Consumer');
messageReceiver.onmessage = function(event) {
alert(event.data);
}
broadcaster.postMessage("hello");
See this in action: https://jsfiddle.net/h56d3y27/
Or wrapped in a reusable class:
(note: class is not supported by all browsers. See : https://caniuse.com/#search=class for browser compatibility)
class AllInclusiveBroadcaster {
constructor(listener, channelName) {
if (!channelName) channelName = "channel";
this.broadcaster = new BroadcastChannel(channelName);
this.messageReceiver = new BroadcastChannel(channelName);
this.messageReceiver.onmessage = (event) => {
listener(event.data);
}
}
postmessage(data) {
this.broadcaster.postMessage(data);
}
}
var broadcaster = new AllInclusiveBroadcaster((data) => alert(data));
broadcaster.postmessage("Hello BroadcastChannel");
See this also in action a JSFiddle

You could dispatch an event (call it what you like) to, say, document, with the same data ... then have a single handler that listens for BroadcastChannel messages and to the event name you created above
in the following, the code creates and listens for fakeBroadcastMessage
created a function to send both the bc message and the "local" message
var bc = new BroadcastChannel('Consumer');
function handleBroadcastMessage(event) {
// do things here
}
bc.addEventHandler('message', handleBroadcastMessage);
document.addEventListener('fakeBroadcastMessage', handleBroadcastMessage);
function sendMessage(data) {
bc.postMessage(data);
var ev = new Event('fakeBroadcastMessage');
ev.data = data;
document.dispatchEvent(ev);
}
sendMessage('hello');

Related

Service Worker onClick event - How to open url in window withing sw scope?

I have service worker which handles push notification click event:
self.addEventListener('notificationclick', function (e) {
e.notification.close();
e.waitUntil(
clients.openWindow(e.notification.data.url)
);
});
When notification comes it takes url from data and displays it in new window.
The code works, however, I want different behavior. When User clicks on the link, then it should check if there is any opened window within service worker scope. If yes, then it should focus on the window and navigate to the given url.
I have checked this answer but it is not exactly what I want.
Any idea how it can be done?
P.S. I wrote this code but it still doesn't work. The first two messages are however shown in the log.
self.addEventListener('notificationclick', function (e) {
e.notification.close();
var redirectUrl = e.notification.data.redirect_url.toString();
var scopeUrl = e.notification.data.scope_url.toString();
console.log(redirectUrl);
console.log(scopeUrl);
e.waitUntil(
clients.matchAll({type: 'window'}).then(function(clients) {
for (i = 0; i < clients.length; i++) {
console.log(clients[i].url);
if (clients[i].url.toString().indexOf(scopeUrl) !== -1) {
// Scope url is the part of main url
clients[i].navigate(givenUrl);
clients[i].focus();
break;
}
}
})
);
});
Ok, here is the piece of code which works as expected. Notice that I am passing scope_url together with redirect_url into the web notification. After that I am checking if scope_url is part of sw location. Only after that I navigate to redirect_url.
self.addEventListener('notificationclick', function (e) {
e.notification.close();
var redirectUrl = e.notification.data.redirect_url;
var scopeUrl = e.notification.data.scope_url;
e.waitUntil(
clients.matchAll({includeUncontrolled: true, type: 'window'}).then(function(clients) {
for (i = 0; i < clients.length; i++) {
if (clients[i].url.indexOf(scopeUrl) !== -1) {
// Scope url is the part of main url
clients[i].navigate(redirectUrl);
clients[i].focus();
break;
}
}
})
);
});
If I understand you correctly, most of the code you linked to works here.
First retrieve all the clients
If there are more than one, choose one of them
Navigate that to somewhere and focus
Else open a new window
Right?
event.waitUntil(
clients.matchAll({type: 'window'})
.then(clients => {
// clients is an array with all the clients
if (clients.length > 0) {
// if you have multiple clients, decide
// choose one of the clients here
const someClient = clients[..someindex..]
return someClient.navigate(navigationUrl)
.then(client => client.focus());
} else {
// if you don't have any clients
return clients.openWindow(navigationUrl);
}
})
);

SignalR changing hub subscription

I have a simple app, which displays a list of available signalR hubs. A user selects a hub and it connects to it, this subscribes an event to add messages to a table on the page. The user can then send messaged to that hub which will also fire the subscription adding that message to the table. This all works great.
Now if the user selects another hub, the app connects and sets up a new subscription, however the original subscription still fires causing duplicate messages to be added to the table. Each time the hub is changed further subscriptions get added causing one send to result in many messages in the table.
I have tried disconnecting the hub, disposing the hub and trying to remove the subscription with hubProxy.off(eventName), but nothing seems to work, other than a page reload.
Here is the code I have just added the onHub changed function as this is where everything is happening.
Any ideas appreciated. :)
function HubViewModel() {
var self = this;
self.hubConnection = '';
self.hub = '';
$.getScript("../signalR/hubs");
self.hubs = ko.observableArray();
self.selectedHub = ko.observable();
self.messageText = ko.observable();
self.messageCollection = ko.observableArray();
self.hubChanged = function () {
// Setup hub connection.
$.connection.hub.url = "../signalR";
self.hubConnection = $.hubConnection();
// Get the selected hub name.
var selectedHubName;
_.each(self.hubs(), function(item) {
if (item.hubId == self.selectedHub()) {
selectedHubName = item.hubName;
}
});
// Check for a selected connection
if (self.selectedHub()) {
// Create proxy.
self.hub = self.hubConnection.createHubProxy(selectedHubName);
// Remove any existing listener(s).
self.hub.off('addNewMessageToPage');
// Setup listener.
self.hub.On('addNewMessageToPage', function (sender, message) {
self.messageCollection().push({ hubName: selectedHubName, name: selectedHubName, message: message, dateTime: new Date().toLocaleString() });
$('#hubMessageGrid').dxDataGrid('instance').refresh();
});
// start connection.
self.hubConnection.start()
.done(function() {
toastr.success('hub connected');
$('#sendMessageButton').click(function() {
self.hub.invoke('sendAll', 'hub management page', self.messageText());
self.messageText('');
});
})
.fail(function(error) {
toastr.error('hub connection ' + error);
});
}
};
You can to disconnect the hub first by calling the self.hub.stop(); function
You need to pass the exact same handler instance when unsubscribing. Passing a different instance (even if the function body is the same) will not remove the handler.
See https://learn.microsoft.com/en-us/javascript/api/#microsoft/signalr/hubconnection?view=signalr-js-latest#off-string---args--any-------void-

Hide JS Notification Object By Tag Name

I'm using the notification API for my project to show browser notifications where each notification has a unique tag (ID), but I can't seem to find a way to close or hide the notification by the tag name, without calling the close function on the object, since it might be closed with other pages than where it was originated. Is this sort of thing possible?
You could save the notifications in localStorage and then retrieve it and close.
e.g.
// on create
var n = new Notification('Notification Title', {
tag: _this.attr('data-notification-id')
});
window.localStorage.setItem('data-notification-id', n);
and
// then later
var n = window.localStorage.getItem('data-notification-id');
n.close();
I've solved this now, but my solutions seems odd, so I'm still accepting other answers that follow a more "normal" approach.
Basically, a new notification object that is created with a tag while a notification that is currently already visible already has the same tag, the original notification is removed. So by creating a new notification object with the same tag and immediately removing it, I can "remove" the old notifications.
The link to view the notification
View this notification
And the jQuery
$('a[data-notification-id]').on('click', function(){
var _this = $(this);
var n = new Notification('Notification Title', {
tag: _this.attr('data-notification-id')
});
setTimeout(n.close.bind(n), 0);
});
You could stringify the notification options and save to session (or local) storage using the tag as the storage key. Then you can use the stored notification options to re-create/replace it and then call close.
Create the notification:
if (("Notification" in window)) {
if (Notification.permission === "granted") {
var options = {
body: sBody,
icon: sIcon,
title: sTitle, //used for re-create/close
requireInteraction: true,
tag: sTag
}
var n = new Notification(sTitle, options);
n.addEventListener("click", function (event) {
this.close();
sessionStorage.removeItem('notification-' + sTag);
}, false);
sessionStorage.setItem('notification-' + sTag, JSON.stringify(options));
}
}
Clear the notification:
function notificationClear(sTag) {
var options = JSON.parse(sessionStorage.getItem('notification-' + sTag));
if (options) {
options.requireInteraction = false;
if (("Notification" in window)) {
if (Notification.permission === "granted") {
var n = new Notification(options.title, options);
setTimeout(function () {
n.close();
sessionStorage.removeItem('notification-' + sTag);
}, 500); //can't close it immediately, so setTimeout is used
}
}
}
}

How to add custom event to Javascript "SDK"

I'm getting more into JS development on my own personal time and for this created a sort of SDK to log into openID connect implementation ( very basic, only implicit flow and code flow). This post is about the client side JS for the implicit flow.
I have successfully created a small SDK that allows me to launch the login request and monitor the pop up for the finished log in (or window close). In the end I now want to add a custom event so that other code can subscribe and be notified when this happens. I have been unable to get this to work. So my question is, how can I do it given the code posted, and of course if you have any suggestions on bettering my code, I'll gladly accept them.
Here is the code:
var mysystemID = {
authorizationURL:"MYRUL_TO_SERVER/oauth/v2/authorize",
client_id:"MYCLIENTID",
redirect_uri:window.location.protocol+"//"+window.location.host+"/callback",
response_type:"id_token token",
windowref : undefined,
intervalID : null,
access_token:"",
id_token:"null",
parser:document.createElement('a'),
checkLogin : function(){
if (this.windowref==undefined){
return false;
}
if (this.windowref.closed){
console.log("window closed")
clearInterval(this.intervalID)
this.intervalID=null
}
try{
this.parser.href = this.windowref.location.href;
if((this.parser.protocol + "//" +this.parser.host + this.parser.pathname) == this.redirect_uri)
{
console.log("login detected")
clearInterval(this.intervalID)
this.intervalID=null
this.windowref.close()
var obj = {}
var str = this.parser.hash.substring(1)
$.each(str.split("&"),function(index,value){
var pieces = value.split("=")
obj[pieces[0]] = pieces[1]
})
console.log(obj)
this.access_token = obj.token
this.id_token = JSON.parse(atob(obj.id_token.split(".")[1]))
// var event = new CustomEvent("my_login", {"detail":{"access_token":this.id_token}});
// this.dispatchEvent(event);
console.log(this.id_token)
}
}catch(e){
//do nothing
}
}
}
mysystemID.login_process = function(){
var scope = $('#scope').val() ||"openid"
var uri = this.authorizationURL+"?response_type="+encodeURIComponent(this.response_type)
+"&client_id=" + this.client_id +"&redirect_uri="+encodeURIComponent(this.redirect_uri)
+"&scope="+encodeURIComponent(scope)+"&nonce="+Math.floor( Math.random()*99999);
console.log(uri)
this.windowref = window.open(uri,
"login with mysystem
ID","width=400, height=600")
if (this.windowref!=null){
this.intervalID = setInterval(this.checkLogin.bind(mysystemID),500)
}
}.bind(mysystemID)
mysystemID.logout = function(){
this.access_token = ""
this.id_token = ""
}.bind(mysystemID)
mysystemID.isLoggedIn = function(){
return this.access_token!=""
}.bind(mysystemID)
A few notes, I rely on jQuery for some things,
and I instantiate it like so:
$(document).ready(function(){
$('a#login_push').on("click",mysystemID.login_process)
});
Within the code you can see my attempt at the event:
// var event = new CustomEvent("my_login", {"detail":{"access_token":this.id_token}});
// this.dispatchEvent(event);
the listener had been chained to the
$(document).ready(function(){
$('a#login_push').on("click",mysystemID.login_process).on("my_login",function(e,data){console.log('logged in fired')});
});

Add-on Builder: Multiple Workers Using port?

Referring to this question: Add-on Builder: ContentScript and back to Addon code?
Here is my addon code:
var widget = widgets.Widget({
id: "addon",
contentURL: data.url("icon.png"),
onClick: function() {
var workers = [];
for each (var tab in windows.activeWindow.tabs) {
var worker = tab.attach({contentScriptFile: [data.url("jquery.js"), data.url("myScript.js")]});
workers.push(worker);
}
}
});
And here is myScript.js:
var first = $(".avatar:first");
if (first.length !== 0) {
var url = first.attr("href");
self.port.emit('got-url', {url: url});
}
Now that I have multiple workers where do I put
worker.port.on('got-url', function(data) {
worker.tab.url = data.url;
});
Since in the other question I only had one worker but now I have an array of workers.
The code would be:
// main.js:
var data = require("self").data;
var windows = require("windows").browserWindows;
var widget = require("widget").Widget({
id: "addon",
label: "Some label",
contentURL: data.url("favicon.png"),
onClick: function() {
//var workers = [];
for each (var tab in windows.activeWindow.tabs) {
var worker = tab.attach({
contentScriptFile: [data.url("jquery.js"),
data.url("inject.js")]
});
worker.port.on('got-url', function(data) {
console.log(data.url);
// worker.tab.url = data.url;
});
worker.port.emit('init', true);
console.log("got here");
//workers.push(worker);
}
}
});
// inject.js
$(function() {
self.port.on('init', function() {
console.log('in init');
var first = $(".avatar:first");
if (first.length !== 0) {
var url = first.attr("href");
console.log('injected!');
self.port.emit('got-url', {url: url});
}
});
});
Edit: sorry, should have actually run the code, we had a timing issue there where the content script was injected before the worker listener was set up, so the listener was not yet created when the 'got-url' event was emitted. I work around this by deferring any action in the content script until the 'init' event is emitted into the content script.
Here's a working example on builder:
https://builder.addons.mozilla.org/addon/1045470/latest/
The remaining issue with this example is that there is no way to tell if a tab has been injected by our add-on, so we will 'leak' or use more memory every time the widget is clicked. A better approach might be to inject the content script using a page-mod when it is loaded, and only emit the 'init' event in the widget's onclick handler.

Categories

Resources