Error getting PushManager Subscription - JavaScript - javascript

I've got the following code that registers a service worker and asks the user allow notifications. I'm getting an error after the user allows the push notifications where the promise returned by serviceWorkerRegistration.pushManager.getSubscription() is null. When I close the browser and force this function call again, it works without errors. I don't understand why. Here is my code:
window.vapidPublicKey = new Uint8Array([4, 45, ...]);
if (navigator.serviceWorker) {
navigator.serviceWorker.register('/serviceworker.js')
.then(function(reg) {
console.log('Service worker change, registered the service worker');
});
}
// Otherwise, no push notifications :(
else {
console.error('Service worker is not supported in this browser');
}
navigator.serviceWorker.ready.then((serviceWorkerRegistration) => {
console.log("ready");
serviceWorkerRegistration.pushManager
.subscribe({
userVisibleOnly: true,
applicationServerKey: window.vapidPublicKey
});
});
// application.js
// Let's check if the browser supports notifications
if (!("Notification" in window)) {
console.error("This browser does not support notifications.");
}
// Let's check whether notification permissions have already been granted
else if (Notification.permission === "granted") {
console.log("Permission to receive notifications has been granted");
}
// Otherwise, we need to ask the user for permission
else if (Notification.permission !== 'denied') {
Notification.requestPermission(function (permission) {
// If the user accepts, let's create a notification
if (permission === "granted") {
console.log("Permission to receive notifications has been granted");
saveSubscriptionToDatabase();
}
});
}
function saveSubscriptionToDatabase(){
navigator.serviceWorker.ready
.then((serviceWorkerRegistration) => {
console.log(serviceWorkerRegistration.pushManager.getSubscription());
serviceWorkerRegistration.pushManager.getSubscription()
.then((subscription) => {
$.post("/users/save_subscription", { subscription: subscription.toJSON() });
});
});
}
The error:
Uncaught (in promise) TypeError: Cannot read property 'toJSON' of null
at serviceWorkerRegistration.pushManager.getSubscription.then
UPDATE:
So I added the following button:
<input type="button" onclick="saveSubscriptionToDatabase();" value="test">
All of the JS and post request works as expected as long as I call the function from the button click but when I call it from the conditional that checks the permission state, it still fails just like before.
UPDATE 2:
I've tried writing the code again but from a different angle.
function registerServiceWorker() {
return navigator.serviceWorker.register('serviceworker.js')
.then(function(registration) {
console.log('Service worker successfully registered.');
return registration;
})
.catch(function(err) {
console.error('Unable to register service worker.', err);
});
}
function askPermission() {
return new Promise(function(resolve, reject) {
const permissionResult = Notification.requestPermission(function(result) {
resolve(result);
});
if (permissionResult) {
permissionResult.then(resolve, reject);
}
})
.then(function(permissionResult) {
if (permissionResult !== 'granted') {
throw new Error('We weren\'t granted permission.');
}
});
}
function subscribeUserToPush() {
return navigator.serviceWorker.register('serviceworker.js')
.then(function(registration) {
const subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: new Uint8Array("key goes here")
};
return registration.pushManager.subscribe(subscribeOptions);
})
.then(function(pushSubscription) {
console.log("never called");
console.log('Received PushSubscription: ', JSON.stringify(pushSubscription));
return pushSubscription;
});
}
registerServiceWorker();
askPermission();
subscribeUserToPush();
The last part of the command chain in subscribeUserToPush() where I have console.log("never called"); never executes and has no errors. I'm testing this in Chrome 67. I am positive that registration.pushManager.subscribe(subscribeOptions); is called.

So after hours of trying different things I finally figured out the problem and it was annoyingly simple. So each time you call navigator.serviceWorker.register('serviceworker.js') you must put a / before the file name. The service worker will register just fine without the / but grabbing the subscription will fail. The examples that you find in the documentation do not include the /.
So, the code should've looked like this:
navigator.serviceWorker.register('/serviceworker.js')
Tada now it works....

Related

Service worker activate and push event don't get triggered even though the registration is successful and it's activated

I have built a React progressive web application that makes use of service workers.
The service worker gets registered and is activated:
I have been trying to detect the "activate" event using this:
service-worker.js
navigator.serviceWorker.addEventListener("activate", function (event) {
console.log("service worker activated");
});
I added that at the end of the service-worker file. But, this event never gets triggered and I have no idea why.
I also tried to implement push notifications and trigger the from the backend. For this, I needed a "push" event listener that would listen to these events from the server:
navigator.serviceWorker.addEventListener("push", async function (event) {
const message = await event.data.json();
let { title, description, image } = message;
await event.waitUntil(showPushNotification(title, description, image));
});
This is how showPushNotification is defined:
export function showPushNotification(title, description, image) {
if (!("serviceWorker" in navigator)) {
console.log("Service Worker is not supported in this browser");
return;
}
navigator.serviceWorker.ready.then(function (registration) {
registration.showNotification(title, {
body: description,
icon: image,
actions: [
{
title: "Say hi",
action: "Say hi",
},
],
});
});
}
I tested calling that function manually and it successfully triggerss a push notification.
This is the server code that triggers the push notification:
const sendPushNotification = async (user_id, title, description, image) => {
const search_option = { user: user_id };
const users_subscriptions = await PushNotificationSubscription.find(
search_option
);
const number_of_users_subscriptions = users_subscriptions.length;
const options = {
vapidDetails: {
subject: "mailto:xxxx#xxxx.com",
publicKey: VAPID_PUBLIC_KEY,
privateKey: VAPID_PRIVATE_KEY,
},
};
let push_notif_sending_results = {};
for (let i = 0; i < number_of_users_subscriptions; i++) {
const user_subscription = users_subscriptions[i];
await webPush
.sendNotification(
user_subscription,
JSON.stringify({
title,
description,
image,
}),
options
)
.then((notif_send_result) => {
push_notif_sending_results[i] = { success: notif_send_result };
})
.catch((error) => {
push_notif_sending_results[i] = { error: error };
});
}
return push_notif_sending_results;
};
This is the part responsible for sending the push notification:
webPush
.sendNotification(
user_subscription,
JSON.stringify({
title,
description,
image,
}),
options
)
And it's successfully executed as it returns a 201 HTTP response.
So the "push" event listener is supposed to detect it and trigger a push notification.
I think everything regarding the push notification has been successfully implementing and the problem is how the "push" event listener is added since the "activate" event listener also doesn't work.
So I tried moving the two event listeners here right after the registration of the service worker is successful:
function registerValidSW(swUrl, config) {
navigator.serviceWorker.register(swUrl).then((registration) => {
registration.addEventListener("activate", (event) => {
console.log(
"🚀 ~ file: serviceWorker.js:159 ~ navigator.serviceWorker.register ~ event",
event
);
});
registration.addEventListener("push", async function (event) {
const message = await event.data.json();
let { title, description, image } = message;
await event.waitUntil(
showPushNotification(title, description, image)
);
});
});
}
But, it's still the same result.
Neither the "push" nor the "activate" event listeners get triggered.
Any idea what's going on?
Here's the whole service-worker file:
service-worker.js
import axios from "axios";
const isLocalhost = Boolean(
window.location.hostname === "localhost" ||
// [::1] is the IPv6 localhost address.
window.location.hostname === "[::1]" ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets;
return;
}
window.addEventListener("load", () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
} else {
// Is not localhost. Just register service worker
console.log(
"Is not localhost. Just register a service worker, by calling registerValidSW"
);
registerValidSW(swUrl, config);
}
});
}
}
async function subscribeToPushNotifications(serviceWorkerReg) {
let subscription = await serviceWorkerReg.pushManager.getSubscription();
if (subscription === null) {
const dev_public_vapid_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const prod_public_vapid_key =
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const public_vapid_key = isLocalhost
? dev_public_vapid_key
: prod_public_vapid_key;
subscription = await serviceWorkerReg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: public_vapid_key,
});
axios
.post("/api/push_notif_subscription/subscribe", subscription)
.then((response) => {})
.catch((error) => {});
}
}
export function showPushNotification(title, description, image) {
if (!("serviceWorker" in navigator)) {
console.log("Service Worker is not supported in this browser");
return;
}
navigator.serviceWorker.ready.then(function (registration) {
registration.showNotification(title, {
body: description,
icon: image,
actions: [
{
title: "Say hi",
action: "Say hi",
},
],
});
});
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker.register(swUrl).then((registration) => {
subscribeToPushNotifications(registration);
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (!installingWorker) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === "installed") {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been preached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { "Service-Worker": "script" },
}).then((response) => {
// Ensure the service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get("content-type");
if (
response.status === 404 ||
(!!contentType && contentType.indexOf("javascript") === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then((registration) => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
console.log("Service worker found, calling registerValidSW");
registerValidSW(swUrl, config);
}
});
}
export function unregister() {
if ("serviceWorker" in navigator) {
navigator.serviceWorker.ready.then((registration) => {
registration.unregister();
});
}
}
navigator.serviceWorker.addEventListener("activate", function (event) {
console.log("service worker activated");
});
navigator.serviceWorker.addEventListener("push", async function (event) {
const message = await event.data.json();
let { title, description, image } = message;
await event.waitUntil(showPushNotification(title, description, image));
});
The events "push" and "activate" are part of the ServiceWorkerGlobalScope as within the Service Worker API.
Push notifications must be handled within the service worker itself.
Therefore only the service worker can register an "activate" event listener.
The same applies for a "push" listener.
Specially in terms of the "push" listener this makes sense.
The idea of push events is to receive them, even if the main app (in this case the website) has been closed.
The service worker is an exception, as it even runs without the page being loaded.
Therefore move the "push" event into your service worker.
Your code (within the service worker) may look like this:
this.addEventListener("push", async function (event) {
const message = await event.data.json();
let { title, description, image } = message;
await event.waitUntil(showPushNotification(title, description, image));
});
function showPushNotification(title, description, image) {
registration.showNotification(title, {
body: description,
icon: image,
actions: [
{
title: "Say hi",
action: "Say hi",
},
],
});
}
The rest seems fine to me.
Update (Some more explanation)
I took a more careful look at your service-worker.js and it seems it contains general methods for registering the service worker.
As mentioned above the main app and the service worker are two completely separate chunks of code, running in different spaces. So this means everything which is not supposed to run in the service worker itself must be put outside of the service-worker.js. The service worker (in your case) should only contain the code for handling push notifications. It's important that you do not include the "service-worker.js" within your application.
In your case, you may seperate these functions into service-worker-register.js which contain all functions which are for managing the service worker registration but should not be executed within the service worker itself (isLocalhost, register, subscribeToPushNotifications, registerValidSW, checkValidServiceWorker, and unregister). Please note the code snippet from above and make changes accordingly to the code left within the service worker.
MDN has a pretty in depth tutorial on service workers (and there are a lot more) I recommend having a look at:
developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers

Using pwa-builder from Microsoft: Uncaught (in promise) TypeError: Request failed

I would like to use a service worker to cache files and improve user experience by providing offline pages. I used the pwabuilder.com to create the files for the website. Unfortunately, even when using the code without any elements to cache it issues the error "Uncaught (in promise) TypeError: Request failed"
I double checked the code, I tried the different bug fixes shown on Google Developper and Stackoverflow, but none of these helped me fix the issue.
I have this in the HTML file:
if ("serviceWorker" in navigator) {
if (navigator.serviceWorker.controller) {
console.log("[PWA Builder] active service worker found, no need to register");
} else {
// Register the service worker
navigator.serviceWorker
.register("pwabuilder-sw.js", {
scope: "./"
})
.then(function (reg) {
console.log("[PWA Builder] Service worker has been registered for scope: " + reg.scope);
});
}
}
The service worker registers sucessfully.
The pwabuilder-sw.js is this one:
//This is the service worker with the Advanced caching
const CACHE = "pwabuilder-adv-cache";
const precacheFiles = [
/* Add an array of files to precache for your app */
'/cms/stylesheets/bootstrap.css',
'/cms/stylesheets/ifpayroll.css',
'/cms/stylesheets/animate.css',
'/cms/stylesheets/fontawesome-webfont.css',
'/cms/javascript/main.js',
'/cms/javascript/aos.js',
'/cms/images/logo#3x.png',
];
// TODO: replace the following with the correct offline fallback page i.e.: const offlineFallbackPage = "offline.html";
const offlineFallbackPage = "ToDo-replace-this-name.html";
const networkFirstPaths = [
/* Add an array of regex of paths that should go network first */
// Example: /\/api\/.*/
];
const avoidCachingPaths = [
/* Add an array of regex of paths that shouldn't be cached */
// Example: /\/api\/.*/
];
function pathComparer(requestUrl, pathRegEx) {
return requestUrl.match(new RegExp(pathRegEx));
}
function comparePaths(requestUrl, pathsArray) {
if (requestUrl) {
for (let index = 0; index < pathsArray.length; index++) {
const pathRegEx = pathsArray[index];
if (pathComparer(requestUrl, pathRegEx)) {
return true;
}
}
}
return false;
}
self.addEventListener("install", function (event) {
console.log("[PWA Builder] Install Event processing");
console.log("[PWA Builder] Skip waiting on install");
self.skipWaiting();
event.waitUntil(
caches.open(CACHE).then(function (cache) {
console.log("[PWA Builder] Caching pages during install");
return cache.addAll(precacheFiles).then(function () {
if (offlineFallbackPage === "ToDo-replace-this-name.html") {
return cache.add(new Response("TODO: Update the value of the offlineFallbackPage constant in the serviceworker."));
}
return cache.add(offlineFallbackPage);
});
})
);
});
// Allow sw to control of current page
self.addEventListener("activate", function (event) {
console.log("[PWA Builder] Claiming clients for current page");
event.waitUntil(self.clients.claim());
});
// If any fetch fails, it will look for the request in the cache and serve it from there first
self.addEventListener("fetch", function (event) {
if (event.request.method !== "GET") return;
if (comparePaths(event.request.url, networkFirstPaths)) {
networkFirstFetch(event);
} else {
cacheFirstFetch(event);
}
});
function cacheFirstFetch(event) {
event.respondWith(
fromCache(event.request).then(
function (response) {
// The response was found in the cache so we responde with it and update the entry
// This is where we call the server to get the newest version of the
// file to use the next time we show view
event.waitUntil(
fetch(event.request).then(function (response) {
return updateCache(event.request, response);
})
);
return response;
},
function () {
// The response was not found in the cache so we look for it on the server
return fetch(event.request)
.then(function (response) {
// If request was success, add or update it in the cache
event.waitUntil(updateCache(event.request, response.clone()));
return response;
})
.catch(function (error) {
// The following validates that the request was for a navigation to a new document
if (event.request.destination !== "document" || event.request.mode !== "navigate") {
return;
}
console.log("[PWA Builder] Network request failed and no cache." + error);
// Use the precached offline page as fallback
return caches.open(CACHE).then(function (cache) {
cache.match(offlineFallbackPage);
});
});
}
)
);
}
function networkFirstFetch(event) {
event.respondWith(
fetch(event.request)
.then(function (response) {
// If request was success, add or update it in the cache
event.waitUntil(updateCache(event.request, response.clone()));
return response;
})
.catch(function (error) {
console.log("[PWA Builder] Network request Failed. Serving content from cache: " + error);
return fromCache(event.request);
})
);
}
function fromCache(request) {
// Check to see if you have it in the cache
// Return response
// If not in the cache, then return error page
return caches.open(CACHE).then(function (cache) {
return cache.match(request).then(function (matching) {
if (!matching || matching.status === 404) {
return Promise.reject("no-match");
}
return matching;
});
});
}
function updateCache(request, response) {
if (!comparePaths(request.url, avoidCachingPaths)) {
return caches.open(CACHE).then(function (cache) {
return cache.put(request, response);
});
}
return Promise.resolve();
}
It is 100% the same as the one provided on pwabuilder.com except for the cached files that have been added.
Manifest: unknown 'orientation' value ignored.
pwabuilder-sw.js:83 [PWA Builder] Install Event processing
pwabuilder-sw.js:87 [PWA Builder] Skip waiting on install
pwabuilder-sw.js:97 [PWA Builder] Caching pages during install
legal.html:63 [PWA] Service worker has been registered for scope: https://www.ifpayroll.lu/
pwabuilder-sw.js:1 Uncaught (in promise) TypeError: Request failed
This is what I get.
You have to change this line to reflect your real filename, and make sure that file exists:
const offlineFallbackPage = "ToDo-replace-this-name.html";
Then you can also delete these lines:
if (offlineFallbackPage === "ToDo-replace-this-name.html") {
return cache.add(new Response("TODO: Update the value of the offlineFallbackPage constant in the serviceworker."));
}
The offline fallback page instruction is badly documented IMO/annoying.
These lines can be removed:
if (offlineFallbackPage === "ToDo-replace-this-name.html") {
return cache.add(new Response("TODO: Update the value of the offlineFallbackPage constant in the serviceworker."));
}

multiple_matching_tokens_detected with MSAL and JS

I am building SPA with react and I run into a problem with code directly from Azure Portal - Quickstart for Javascript. (you can download full code there)
If I create-react-app and I use the code it works just fine and I can authenticate, get token and use it in the post request.
But If I use the SAME code in my app (which is already styled and with all the functionality I need) it gives me Multiple authorities found in the cache. Pass authority in the API overload.|multiple_matching_tokens_detected` error when I authenticate.
Just to clarify authentication goes through and I see I am authenticated, just this error is bugging me and I have no idea how to debug it.
function signIn() {
myMSALObj.loginPopup(applicationConfig.graphScopes).then(function (idToken) {
//Login Success
console.log(idToken); //note that I can get here!
showWelcomeMessage();
acquireTokenPopupAndCallMSGraph();
}, function (error) {
console.log(error);
});
}
function acquireTokenPopupAndCallMSGraph() {
//Call acquireTokenSilent (iframe) to obtain a token for Microsoft Graph
myMSALObj.acquireTokenSilent(applicationConfig.graphScopes).then(function (accessToken) {
callMSGraph(applicationConfig.graphEndpoint, accessToken, graphAPICallback);
}, function (error) {
console.log(error); //this is where error comes from
// Call acquireTokenPopup (popup window) in case of acquireTokenSilent failure due to consent or interaction required ONLY
if (error.indexOf("consent_required") !== -1 || error.indexOf("interaction_required") !== -1 || error.indexOf("login_required") !== -1) {
myMSALObj.acquireTokenPopup(applicationConfig.graphScopes).then(function (accessToken) {
callMSGraph(applicationConfig.graphEndpoint, accessToken, graphAPICallback);
}, function (error) {
console.log(error);
});
}
});
}
The main thing I don`t understand is that the same code works just fine in the fresh create-react-app project, but as I use it in an already existing project (just without authentication) it breaks with mentioned error.
Full code
import React, { Component } from 'react'
import * as Msal from 'msal'
export class test extends Component {
render() {
var applicationConfig = {
clientID: '30998aad-bc60-41d4-a602-7d4c14d95624', //This is your client ID
authority: "https://login.microsoftonline.com/35ca21eb-2f85-4b43-b1e7-6a9f5a6c0ff6", //Default authority is https://login.microsoftonline.com/common
graphScopes: ["30998aad-bc60-41d4-a602-7d4c14d95624/user_impersonation"],
graphEndpoint: "https://visblueiotfunctionapptest.azurewebsites.net/api/GetDeviceList"
};
var myMSALObj = new Msal.UserAgentApplication(applicationConfig.clientID, applicationConfig.authority, acquireTokenRedirectCallBack,
{storeAuthStateInCookie: true, cacheLocation: "localStorage"});
function signIn() {
myMSALObj.loginPopup(applicationConfig.graphScopes).then(function (idToken) {
//Login Success
console.log(idToken);
showWelcomeMessage();
acquireTokenPopupAndCallMSGraph();
}, function (error) {
console.log(error);
});
}
function signOut() {
myMSALObj.logout();
}
function acquireTokenPopupAndCallMSGraph() {
//Call acquireTokenSilent (iframe) to obtain a token for Microsoft Graph
myMSALObj.acquireTokenSilent(applicationConfig.graphScopes).then(function (accessToken) {
callMSGraph(applicationConfig.graphEndpoint, accessToken, graphAPICallback);
}, function (error) {
console.log(error);
// Call acquireTokenPopup (popup window) in case of acquireTokenSilent failure due to consent or interaction required ONLY
if (error.indexOf("consent_required") !== -1 || error.indexOf("interaction_required") !== -1 || error.indexOf("login_required") !== -1) {
myMSALObj.acquireTokenPopup(applicationConfig.graphScopes).then(function (accessToken) {
callMSGraph(applicationConfig.graphEndpoint, accessToken, graphAPICallback);
}, function (error) {
console.log(error);
});
}
});
}
function callMSGraph(theUrl, accessToken, callback) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200)
callback(JSON.parse(this.responseText));
console.log(this.response);
}
xmlHttp.open("POST", theUrl, true); // true for asynchronous
xmlHttp.setRequestHeader('Authorization', 'Bearer ' + accessToken);
var dataJSON = JSON.stringify({ userEmail: null, FromDataUTC: "2012-04-23T18:25:43.511Z" })
xmlHttp.send(dataJSON);
}
function graphAPICallback(data) {
//Display user data on DOM
// var divWelcome = document.getElementById('WelcomeMessage');
// divWelcome.innerHTML += " to Microsoft Graph API!!";
// document.getElementById("json").innerHTML = JSON.stringify(data, null, 2);
}
function showWelcomeMessage() {
console.log("You are looged: " + myMSALObj.getUser().name);
// var divWelcome = document.getElementById('WelcomeMessage');
// divWelcome.innerHTML += 'Welcome ' + myMSALObj.getUser().name;
// var loginbutton = document.getElementById('SignIn');
// loginbutton.innerHTML = 'Sign Out';
// loginbutton.setAttribute('onclick', 'signOut();');
}
// This function can be removed if you do not need to support IE
function acquireTokenRedirectAndCallMSGraph() {
//Call acquireTokenSilent (iframe) to obtain a token for Microsoft Graph
myMSALObj.acquireTokenSilent(applicationConfig.graphScopes).then(function (accessToken) {
callMSGraph(applicationConfig.graphEndpoint, accessToken, graphAPICallback);
}, function (error) {
console.log(error);
//Call acquireTokenRedirect in case of acquireToken Failure
if (error.indexOf("consent_required") !== -1 || error.indexOf("interaction_required") !== -1 || error.indexOf("login_required") !== -1) {
myMSALObj.acquireTokenRedirect(applicationConfig.graphScopes);
}
});
}
function acquireTokenRedirectCallBack(errorDesc, token, error, tokenType)
{
if(tokenType === "access_token")
{
callMSGraph(applicationConfig.graphEndpoint, token, graphAPICallback);
} else {
console.log("token type is:"+tokenType);
}
}
// Browser check variables
var ua = window.navigator.userAgent;
var msie = ua.indexOf('MSIE ');
var msie11 = ua.indexOf('Trident/');
var msedge = ua.indexOf('Edge/');
var isIE = msie > 0 || msie11 > 0;
var isEdge = msedge > 0;
//If you support IE, our recommendation is that you sign-in using Redirect APIs
//If you as a developer are testing using Edge InPrivate mode, please add "isEdge" to the if check
if (!isIE) {
if (myMSALObj.getUser()) {// avoid duplicate code execution on page load in case of iframe and popup window.
showWelcomeMessage();
acquireTokenPopupAndCallMSGraph();
}
}
else {
document.getElementById("SignIn").onclick = function () {
myMSALObj.loginRedirect(applicationConfig.graphScopes);
};
if (myMSALObj.getUser() && !myMSALObj.isCallback(window.location.hash)) {// avoid duplicate code execution on page load in case of iframe and popup window.
showWelcomeMessage();
acquireTokenRedirectAndCallMSGraph();
}
}
return (
<div>
<h2>Please log in from VisBlue app</h2>
<button id="SignIn" onClick={signIn}>Sign In</button>
<button id="SignOut" onClick={signOut}>Sign Out</button>
<h4 id="WelcomeMessage"></h4>
<br/><br/>
<pre id="json"></pre>
</div>
)
}
}
export default test
it gives me Multiple authorities found in the cache. Pass authority in
the API overload.|multiple_matching_tokens_detected` error when I
authenticate
This error is caused because the auth SDK finds multiple matching tokens in cache for the input given to acquireTokenSilent.
Try adding the authority, and user if necessary:
myMSALObj
.acquireTokenSilent(
applicationConfig.graphScopes,
applicationConfig.authority
)
.then(
...
Just to get back to it. I solve it by moving the whole project into fresh create-react-app. It looks like there was more than 1 instance of MSAL object thus more than one call/token at the same time.
Weird but solved my problem.
I know this is an old question, but I'll answer it anyway since I had the same issue.
My workaround for this problem was to just clear the cache whenever this error happened. It worked in my case since this wan't a regularly occurring error for my use case.
In my project's configuration, the site is also set up to refresh and retry when an error like this occurs. So after clearing the cache, the site would reload and would work as expected since there would be no conflicting tokens in the cache.
import { AuthCache } from 'msal/lib-commonjs/cache/AuthCache';
...
const authProvider = new MsalAuthProvider(
configuration,
authenticationParameters,
msalProviderConfig,
);
authProvider.registerErrorHandler((authError: AuthError | null) => {
if (!authError) {
return;
}
console.error('Error initializing authProvider', authError);
// This shouldn't happen frequently. The issue I'm fixing is that when upgrading from 1.3.0
// to 1.4.3, it seems that the new version creates and stores a new set of auth credentials.
// This results in the "multiple_matching_tokens" error.
if (authError.errorCode === 'multiple_matching_tokens') {
const authCache = new AuthCache(
configuration.auth.clientId,
configuration.cache.cacheLocation,
configuration.cache.storeAuthStateInCookie,
);
authCache.clear();
console.log(
'Auth cache was cleared due to incompatible access tokens existing in the cache.',
);
}

Where to put eventlisteners for serviceworker

Basicly I'm trying to accomplish to show a dialog when a new serviceworker version has been detected, and then to have user decide to reload to fetch it. To accomplish this we need to actively set skipWaiting before we reload window.
Here's my action:
onClickHandler = () => {
console.log('on click', 'posting skipWaiting');
navigator.serviceWorker.controller.postMessage('skipWaiting');
};
Here's my attempt to create the eventListener:
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
console.log('worker state', installingWorker.state);
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed.'
);
navigator.serviceWorker.addEventListener('message', event => {
console.log('skip waiting');
if (event.data === 'skipWaiting') {
self.skipWaiting();
}
});
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
The issue is that navigator.serviceWorker.addEventListener('message', event => does not get triggered. Am I declaring the listener wrong?
You are close. In your installed block you can make a check for
navigator.serviceWorker.controller
If this exists it means that the old content will have been purged and the fresh content will have been added to the cache. Its a perfect time to display a message or to force a refresh.
navigator.serviceWorker.register('service-worker.js').then(function (registration) {
$log.debug('The service worker has been registered ', registration);
if(navigator.online) {
toastr.warning('Offline Mode', 'Application Status');
}
// updatefound is fired if service-worker.js changes.
registration.onupdatefound = function () {
// The updatefound event implies that reg.installing is set; see
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#service-worker-container-updatefound-event
var installingWorker = registration.installing;
installingWorker.onstatechange = function () {
switch (installingWorker.state) {
case 'installed':
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and the fresh content will
// have been added to the cache.
// It's the perfect time to display a "New content is available; please refresh."
// message in the page's interface.
toastr.info('Application has been updated, please refresh this page for the most recent version');
window.location.reload();
});
caches.delete('scope-dynamic').then(function () {
$log.debug('Killed the dynamic cache!');
})
$log.debug('New or updated content is available.');
} else {
// At this point, everything has been precached.
// It's the perfect time to display a "Content is cached for offline use." message.
toastr.success('Content is cached for offline use.', 'Application Status');
$log.debug('Content is now available offline!');
}
break;
case 'redundant':
$log.error('The installing service worker became redundant.');
break;
}
};
};
}).catch(function (e) {
$log.error('Error during service worker registration:', e);
});
There is some angular stuff sprinkled in there but that should help you get to where you wanna be.
The problem here is that the code you provided has only defined receiving & sending messages from your client to the service worker.
The below method has defined a message to be sent to your controller (active) service worker.
onClickHandler = () => {
console.log('on click', 'posting skipWaiting');
navigator.serviceWorker.controller.postMessage('skipWaiting');
};
The definition below has added an event listener on your ServiceWorker container to receive any messages sent from the service worker.
navigator.serviceWorker.addEventListener('message', event => {
console.log('skip waiting');
if (event.data === 'skipWaiting') {
self.skipWaiting();
}
});
You now need to define the event handlers from the service worker file to receive & send messages.
In the service worker file, to receive messages from the client:
self.addEventListener('message', function handler (event) {
console.log('skip waiting');
if (event.data === 'skipWaiting') {
self.skipWaiting();
}
});
To send messages from serviceworker to your client:
self.addEventListener('fetch', function(event) {
self.clients.matchAll().then(all => all.map(client => client.postMessage('data from webworker')));
});
You could also send data back from the serviceworker using a MessageChannel. On your client you would have to define something like below:
navigator.serviceWorker
.register(swUrl)
.then(registration => {
var messageChannel = new MessageChannel();
// listener for messages from the ServiceWorker
messageChannel.port1.addEventListener('message', (event) => console.log(event.data));
function sendMessage (message) {
//send message to the active service worker
navigator.serviceWorker.controller.postMessage(message, [messageChannel.port2]);
}
onClickHandler = () => {
sendMessage('skipWaiting');
};
});
I found the below article to be quite helpful.
ServiceWorker, MessageChannel & postMessage by Nicolás Bevacqua

Google Chrome Push Notification

I am implementing chrome push notification for my website users. Which I am able to do successfully.
I have two question ?
1) how to get the previous subscription id whenever i block the notification from browser setting. I have to remove the subscription id from my backend server
2) whenever i reload the website pushManager.subscribe method is running every time in which i am sending subscription id to server due to which the API is hitting every time with same subscription id
push.js
'use strict';
if ('serviceWorker' in navigator) {
console.log('Service Worker is supported');
navigator.serviceWorker.register('service_worker.js').then(function() {
return navigator.serviceWorker.ready;
}).then(function(reg) {
console.log('Service Worker is ready :^)', reg);
reg.pushManager.subscribe({userVisibleOnly: true}).then(function(sub) {
console.log('endpoint:',JSON.stringify(sub.endpoint));
console.log(sub.endpoint.substring('https://android.googleapis.com/gcm/send/'.length));
});
}).catch(function(error) {
console.log('Service Worker error :^(', error);
});
}
service-worker.js
'use strict';
var myurl;
console.log('Started', self);
self.addEventListener('install', function(event) {
self.skipWaiting();
console.log('Installed', event);
});
self.addEventListener('activate', function(event) {
console.log('Activated', event);
});
self.addEventListener('push', function(event) {
console.log('Push message', event);
event.waitUntil(
fetch('/notify.json').then(function(response) {
return response.json().then(function(data) {
console.log(JSON.stringify(data));
var title = data.title;
var body = data.body;
myurl=data.myurl;
return self.registration.showNotification(title, {
body: body,
icon: 'profile.png',
tag: 'notificationTag'
});
});
}).catch(function(err) {
console.error('Unable to retrieve data', err);
var title = 'An error occurred';
var body = 'We were unable to get the information for this push message';
return self.registration.showNotification(title, {
body: body,
icon: 'profile.png',
tag: 'notificationTag'
});
})
);
});
// var title = 'Vcona';
// event.waitUntil(
// self.registration.showNotification(title, {
// 'body': 'School Management',
// 'icon': 'profile.png'
// }));
self.addEventListener('notificationclick', function(event) {
console.log('Notification click: tag', event.notification.tag);
// Android doesn't close the notification when you click it
// See http://crbug.com/463146
event.notification.close();
var url = 'https://demo.innotical.com';
// Check if there's already a tab open with this URL.
// If yes: focus on the tab.
// If no: open a tab with the URL.
event.waitUntil(
clients.matchAll({
type: 'window'
})
.then(function(windowClients) {
console.log('WindowClients', windowClients);
for (var i = 0; i < windowClients.length; i++) {
var client = windowClients[i];
console.log('WindowClient', client);
if (client.url === url && 'focus' in client) {
return client.focus();
}
}
if (clients.openWindow) {
return clients.openWindow(myurl);
}
})
);
});
Best pieces of advice I can give:
Keep track of your subscription (especially what you send to your server) in indexDB. Why IndexDB?
You can update indexDB in the window and in the serviceworker. This is important as you'll first get a PushSubscription in the window, but serviceworker will dispatch pushsubscriptionchange events which you should listen for and attempt to get a new PushSubscription, if you can.
When the page loads, check indexDB for an old subscription, if it exists, compare it to getSubscription() (i.e. your current subscription). This check should include any values you need server side, for example, when browsers go from not supporting payloads, to supporting them, they go from having no keys, to suddenly having keys - so you should check if you server has these keys or not.
DO NOT USE any of the API's for GCM, this will NOT work on other browsers (Firefox, Opera, Samsung Browser + others in the future) and aren't needed.
1) You can't get previous reg id. There are to ways:
Every time you subscribe for notifications you can save it to a local chrome db(for example indexdb) and when you subscribe another time you just restore you previous reg id from this db.
When you send a notification to GCM it responds you with canonical ids and another information about correctness of reg ids, so you can remove invalid one
2) You have to check first if subscription id already exists, then subscribe if not:
if ('serviceWorker' in navigator) {
console.log('Service Worker is supported');
navigator.serviceWorker.register('service_worker.js').then(function() {
return navigator.serviceWorker.ready;
}).then(function(reg) {
console.log('Service Worker is ready :^)', reg);
reg.pushManager.getSubscription().then(function(subscription) {
if(!subscription) {
reg.pushManager.subscribe({userVisibleOnly: true}).then(function(sub) {
console.log('endpoint:',JSON.stringify(sub.endpoint));
console.log(sub.endpoint.substring('https://android.googleapis.com/gcm/send /'.length));
//send new subscription id to server
return;
});
}
});
}).catch(function(error) {
console.log('Service Worker error :^(', error);
});
}

Categories