how to combine multiple instances of navigator.serviceWorker.register('sw.js') - service-worker

for example in my index.html I have a code to detect an update for service worker and the code is like this:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js').then(reg => {
reg.addEventListener('updatefound', () => {
// A wild service worker has appeared in reg.installing!
newWorker = reg.installing;
newWorker.addEventListener('statechange', () => {
// Has network.state changed?
switch (newWorker.state) {
case 'installed':
if (navigator.serviceWorker.controller) {
// new update available
showUpdateBar();
}
// No update available
break;
}
});
});
});
let refreshing;
navigator.serviceWorker.addEventListener('controllerchange', function () {
if (refreshing) return;
window.location.reload();
refreshing = true;
});
}
then in pushManager.js the code is like:
navigator.serviceWorker.register('sw.js')
.then(function (registration) {
messaging.useServiceWorker(registration);
// Request for permission
messaging.requestPermission()
.then(function() {
console.log('Notification permission granted.');
// TODO(developer): Retrieve an Instance ID token for use with FCM.
messaging.getToken()
.then(function(currentToken) {
if (currentToken) {
console.log('Token: ' + currentToken)
sendTokenToServer(currentToken);
// updateSubscriptionOnServer(currentToken);
} else {
console.log('No Instance ID token available. Request permission to generate one.');
setTokenSentToServer(false);
}
})
......
The pushManagr.js is included in both login/index.html and index.html.
I think that calling navigator.serviceWorker.register at multiple places will have adverse effect.
so how I can combine the two instances into one.

If possible, remove the registration of the serviceworker from pushManager.
If you want the registration instance of the serviceworker,use the api getRegistration()
navigator.serviceWorker.getRegistration(/*scope*/).then(function(registration) {
if(registration){
// Move all your firebase messaging code to here
messaging.useServiceWorker(registration);
}
});

Related

Prevent a user going to a certain page until condition met in vue

I am using vue with rails. I am a agents table with fields name, address, verified. I want to prevent the logged in user to go to the homepage unless user is verified. verified is a boolean field in user tab. Below is how my router.js file looks. I am not able to figureout to do this.
import VueRouter from 'vue-router';
import Vue from 'vue/dist/vue.js';
import Axios from 'axios';
import MyProfile from 'views/profile.vue';
import HomePage from 'views/homepage.vue';
Vue.use(VueRouter);
const routes = [
{ 'path': '/homepage', component: HomePage },
{ 'path': '/views/profile', component: MyProfile, meta: { requiresAuth: true }},
]
router.beforeEach(function(to, from, next) {
if (to.matched.some(record => record.meta.requiresAuth)) {
axios.get('/verified.json')
.then(function() {
if (to.path === '/loginin' || to.path === '/changepassword') {
next('/');
return;
}
next();
})
.catch(function() {
if (to.path.match(/views/)) {
next('/');
return;
}
next();
});
}
else {
next();
}
});
Your logic appears to execute next no matter the outcome of the verification, i see some go to root but the one that matters runs next.
I assume catch is handling a 4xx forbidden response, if that's the case then the call to next(); is allowing unwanted continuation, either delete next() in cache to make the user stay where they are or change it to next('/') or some other path to redirect them to a permission issue or something.
I would delete next to make the user stay where they are and display a permission denied popup.
router.beforeEach(function(to, from, next) {
if (to.matched.some(record => record.meta.requiresAuth)) {
axios.get('/verified.json')
.then(function() {
// Not sure why you need this, login and change password would not
// have requiresAuth meta on them so this could would never run?
if (to.path === '/loginin' || to.path === '/changepassword') {
next('/');
return;
}
// User allowed to continue.
next();
})
.catch(function() {
// If viewing /views the user is allowed to continue bypass requiresAuth?
// again if views route does not have requiresAuth meta this will never
// get called
if (to.path.match(/views/)) {
next('/');
return;
}
// Use not permitted, omit call to next() user stays on current page...
alert('Permission Denied')
});
}
else {
// requiresAuth not set (public access)
next();
}
});
I think it is wiser to do this:
router.beforeResolve((to, from, next) => {
if (to.meta.hasOwnProperty('requiresAuth') && to.meta.requiresAuth === true) {
axios.get('/verified.json')
.then(function() {
// ALLOW TO CONTINUE
next();
})
.catch(function() {
// DISALLOW CONTINUE
// Use not permitted, omit call to next() user stays on current page...
alert('Permission Denied')
});
return
}
// ALLOW TO CONTINUE
next()
})
Instead of checking if user not verified in the catch block, move it to then. Catch block will meet the condition if the api call had some error. Example code would be:
.then(res => !res.data.verified && to.path.match(/views/)
? next('/')
: next()
)
.catch(e => console.log(e))

Client side functions doesn't call

I had lot of difficulties after upgrading signalR & .NET version.
Previously I had 1.XX version now I have 2.4.0 signal R version.
This question is directly connected with - https://github.com/SignalR/SignalR/issues/4339
But after upgrade signal R doesn't work.
Now the problem is client-side functions cannot call.
I just tried this: Signalr doesn't call client side functions
and fixed it according to the correct answer:
In your init prior to $.connection.hub.start call your _subscribe method.
Later I went through a bit deeper down on this issue, and added console.log below place in my signalr.js
connection.socket.onmessage = function (event) {
var data;
try {
console.log(event.data);
data = connection._parseResponse(event.data);
}
catch (error) {
transportLogic.handleParseFailure(connection, event.data, error, onFailed, event);
console.log("socket error" + event.data);
return;
}
if (data) {
transportLogic.processMessages(connection, data, onSuccess);
}
};
After every one joins meeting -> meeting start and ask for vote (this place we should call signalR)
From vote asking person side I see console log like this:
Normal user ( voting persons console log looks like this:
This is from Firefox - another user:
I think it already triggering - client hub event 'sendOnlineMeetingVoteRequest' on hub 'NotificationHub'.
It already hit server-side function too but the thing is it never hits this part of the code:
notificationHub.client.sendOnlineMeetingVoteRequest = function (token, meetingId, meetingVoteId) {
debugger;
if (token == '#Model.Organization' && '#Model.MeetingId' == meetingId) {
ShowMeetingOnlineMeetingVotePopup(meetingId, meetingVoteId);
}
};
I went through http://localhost:33852/signalr/hubs
/*!
* ASP.NET SignalR JavaScript Library v2.3.0-rtm
* http://signalr.net/
*
* Copyright (c) .NET Foundation. All rights reserved.
* Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
*
*/
/// <reference path="..\..\SignalR.Client.JS\Scripts\jquery-1.6.4.js" />
/// <reference path="jquery.signalR.js" />
(function ($, window, undefined) {
/// <param name="$" type="jQuery" />
"use strict";
if (typeof ($.signalR) !== "function") {
throw new Error("SignalR: SignalR is not loaded. Please ensure jquery.signalR-x.js is referenced before ~/signalr/js.");
}
var signalR = $.signalR;
function makeProxyCallback(hub, callback) {
return function () {
// Call the client hub method
callback.apply(hub, $.makeArray(arguments));
};
}
function registerHubProxies(instance, shouldSubscribe) {
var key, hub, memberKey, memberValue, subscriptionMethod;
for (key in instance) {
if (instance.hasOwnProperty(key)) {
hub = instance[key];
if (!(hub.hubName)) {
// Not a client hub
continue;
}
if (shouldSubscribe) {
// We want to subscribe to the hub events
subscriptionMethod = hub.on;
} else {
// We want to unsubscribe from the hub events
subscriptionMethod = hub.off;
}
// Loop through all members on the hub and find client hub functions to subscribe/unsubscribe
for (memberKey in hub.client) {
if (hub.client.hasOwnProperty(memberKey)) {
memberValue = hub.client[memberKey];
if (!$.isFunction(memberValue)) {
// Not a client hub function
continue;
}
// Use the actual user-provided callback as the "identity" value for the registration.
subscriptionMethod.call(hub, memberKey, makeProxyCallback(hub, memberValue), memberValue);
}
}
}
}
}
$.hubConnection.prototype.createHubProxies = function () {
var proxies = {};
this.starting(function () {
// Register the hub proxies as subscribed
// (instance, shouldSubscribe)
registerHubProxies(proxies, true);
this._registerSubscribedHubs();
}).disconnected(function () {
// Unsubscribe all hub proxies when we "disconnect". This is to ensure that we do not re-add functional call backs.
// (instance, shouldSubscribe)
registerHubProxies(proxies, false);
});
proxies['NotificationHub'] = this.createHubProxy('NotificationHub');
proxies['NotificationHub'].client = { };
proxies['NotificationHub'].server = {
sendMeetingStartMessage: function (token, meetingId) {
return proxies['NotificationHub'].invoke.apply(proxies['NotificationHub'], $.merge(["sendMeetingStartMessage"], $.makeArray(arguments)));
},
sendMeetingStopMessage: function (token, meetingId) {
return proxies['NotificationHub'].invoke.apply(proxies['NotificationHub'], $.merge(["sendMeetingStopMessage"], $.makeArray(arguments)));
},
sendMeetingTreeRefreshRequest: function (token, meetingId) {
return proxies['NotificationHub'].invoke.apply(proxies['NotificationHub'], $.merge(["sendMeetingTreeRefreshRequest"], $.makeArray(arguments)));
},
sendMessage: function (token, meetingId, agendaGroupItemId, motionId) {
return proxies['NotificationHub'].invoke.apply(proxies['NotificationHub'], $.merge(["sendMessage"], $.makeArray(arguments)));
},
sendOnlineMeetingVoteCloseRequest: function (token, meetingId, meetingVoteId) {
return proxies['NotificationHub'].invoke.apply(proxies['NotificationHub'], $.merge(["sendOnlineMeetingVoteCloseRequest"], $.makeArray(arguments)));
},
sendOnlineMeetingVoteRequest: function (token, meetingId, meetingVoteId) {
return proxies['NotificationHub'].invoke.apply(proxies['NotificationHub'], $.merge(["sendOnlineMeetingVoteRequest"], $.makeArray(arguments)));
},
sendOnlineVoteCloseRequest: function (token, meetingId, agendaGroupItemId, motionId) {
return proxies['NotificationHub'].invoke.apply(proxies['NotificationHub'], $.merge(["sendOnlineVoteCloseRequest"], $.makeArray(arguments)));
},
sendOnlineVoteRequest: function (token, meetingId, agendaGroupItemId, motionId) {
return proxies['NotificationHub'].invoke.apply(proxies['NotificationHub'], $.merge(["sendOnlineVoteRequest"], $.makeArray(arguments)));
},
sendOnlineVoteResult: function (token, meetingId, agendaGroupItemId, motionId, selectedVotingOptionId) {
return proxies['NotificationHub'].invoke.apply(proxies['NotificationHub'], $.merge(["sendOnlineVoteResult"], $.makeArray(arguments)));
}
};
return proxies;
};
signalR.hub = $.hubConnection("/signalr", { useDefaultPath: false });
$.extend(signalR, signalR.hub.createHubProxies());
}(window.jQuery, window));
So I didn't found any error on it too.
Still, I cannot figure it out why this is happening but I went through the sample project that uses signalR 2.0.3.0 version
I went to reference & just noted that this reference - Microsoft.AspNet.SignalR.Owin is not included in that sample project that I downloaded.
I did some investigation furthermore & find out this:
'The call is ambiguous between the following methods or properties'
This error will occur if a reference to Microsoft.AspNet.SignalR.Owin
is not removed. This package is deprecated; the reference must be
removed and the 1.x version of the SelfHost package must be
uninstalled.
(https://learn.microsoft.com/en-us/aspnet/signalr/overview/releases/upgrading-signalr-1x-projects-to-20)
Do I need to remove that?
In my web config, there is no code like this.
I just call this function purely from another place - about us page. only add relevant script and function.
from that place, it worked.
after that, I changed some scripts placing and fix this issue.
the main thing is I didn't get any error or anything. thing looks working all the time. but because of some script placement, it doesn't work.

What's the right way to implement offline fallback with workbox

I am implementing PWA into my project, I have setted up the serviceworker.js, and I am using workbox.js for cache routing and strategies.
1- I add the offline page to cache on install event, when a user first visit the site:
/**
* Add on install
*/
self.addEventListener('install', (event) => {
const urls = ['/offline/'];
const cacheName = workbox.core.cacheNames.runtime;
event.waitUntil(caches.open(cacheName).then((cache) => cache.addAll(urls)))
});
2- Catch & cache pages with a specific regex, like these:
https://website.com/posts/the-first-post
https://website.com/posts/
https://website.com/articles/
workbox.routing.registerRoute(
new RegExp('/posts|/articles'),
workbox.strategies.staleWhileRevalidate({
cacheName: 'pages-cache'
})
);
3- Catch errors and display the offline page, when there's no internet connection.
/**
* Handling Offline Page fallback
*/
this.addEventListener('fetch', event => {
if (event.request.mode === 'navigate' || (event.request.method === 'GET' && event.request.headers.get('accept').includes('text/html'))) {
event.respondWith(
fetch(event.request.url).catch(error => {
// Return the offline page
return caches.match('/offline/');
})
);
}
else{
// Respond with everything else if we can
event.respondWith(caches.match(event.request)
.then(function (response) {
return response || fetch(event.request);
})
);
}
});
Now this is working for me so far if I visit for example: https://website.com/contact-us/ but if I visit any url within the scope I defined earlier for "pages-cache" like https://website.com/articles/231/ this would not return the /offline page since it's not in the user cache, and I would get a regular browser error.
There's an issue in how errors are handled, when there's a specific caching route by workbox.
Is this the best method to apply for offline fallback? how can I catch errors from these paths: '/articles' & '/posts' and display an offline page?
Please refer as well to this answer where there's a different
approach to applying the fallack with workbox, I tried it as well same
results. Not sure which is the accurate approach for this.
I found a way to do it right with workbox.
For each route I would add a fallback method like this:
const offlinePage = '/offline/';
/**
* Pages to cache
*/
workbox.routing.registerRoute(/\/posts.|\/articles/,
async ({event}) => {
try {
return await workbox.strategies.staleWhileRevalidate({
cacheName: 'cache-pages'
}).handle({event});
} catch (error) {
return caches.match(offlinePage);
}
}
);
In case of using network first strategy this is the method:
/**
* Pages to cache (networkFirst)
*/
var networkFirst = workbox.strategies.networkFirst({
cacheName: 'cache-pages'
});
const customHandler = async (args) => {
try {
const response = await networkFirst.handle(args);
return response || await caches.match(offlinePage);
} catch (error) {
return await caches.match(offlinePage);
}
};
workbox.routing.registerRoute(
/\/posts.|\/articles/,
customHandler
);
More details at workbox documentation here: Provide a fallback response to a route

TFS Rest API: Extension Data Service unreliable?

My TFS hub extension (on-premise 2015.3) does not load correctly because of unexpected extension data service behaviour and not getting its needed preferences. The extension users store - after installation once during first-start/loading the hub page - extension preferences on the collection-level, as key-value pairs (getValue/setValue from extension data service API), and if the hub page gets reloaded the preferences are stored already. It's like an Application Wizard/First start Dialog in my hub page.
But when I install the extension on another collection of the same TFS and want to store (=setValue) preferences for that collection it comes back with OK (can see it in F12->network capture of Internet Explorer), but cannot find these previously entered/stored key-value pairs when refreshing (=getValue on the key) my hub. It delivers an empty value for the key, and the "first-start" dialog reappears again, what shouldnt happen if there was a value for the key. Already debugged, it always comes back empty (empty value) in that collection. No error from the service, nothing to capture, nothing to debug.
Can I check somewhere else (on the TFS logs, event viewer, or database) for deeper debugging?
I also tried with Powershell and restcalls manually by putting and getting the json on the Rest urls for the extension data service. In one collection it works (manually and per hub extension) but for the other collection the data service does not work.
Is there a known issue in 2015.3 in the extension data service? I really have a problem if I cannot store the preferences of the extension anywhere - storing it to an default source control path would be an alternative, but I do not want to force the projects to check-in preferences for my extension...
EDIT:
Adding relevant code snippet
function showSourceControlDialog(project: string/*TFS_Core_Contracts.TeamProjectReference*/) {
return Q.Promise(function (resolve, reject) {
//setTimeout(function () {
var thatProjectIDclean = project/*.id*/.replace(/-/g, "_");
var enterSourceControlPathDialog = VSS_Controls_Dialogs.show(VSS_Controls_Dialogs.ModalDialog, {
title: "Please now enter source control path for " + thisProjectName /*project.name*/,
content: $("<p/>").addClass("confirmation-text").html("<label><b>Source Control Path</b> to preferences file, e.g. '$/" + thisProjectName /*thisCollectionName + "/" + project.name*/ + "/.../...xml:</label><input id='enterSourceControlPathInput' size='40'/>" /*+ projectName + ".xml"*/),
useBowtieStyle: true,
buttons: {
"Cancel": function () {
enterSourceControlPathDialog.close();
enterSourceControlPathDialog.dispose();
reject("cancelled");
},
"OK": function () {
sourceControlPath = $("input#enterSourceControlPathInput").val();
if (sourceControlPath) {
setConfiguration(thatProjectIDclean, sourceControlPath).then(function (setToThisPath) {
console.log(setToThisPath);
enterSourceControlPathDialog.close();
enterSourceControlPathDialog.dispose();
$(".bss-button").show();
$(".bss-tvc").show();
resolve(sourceControlPath);
}).catch(function (error) {
reject(error);
})
}
}
}
});
//}, 10000);
});
}
function setConfiguration(key: string, value: string) {
return Q.Promise(function (resolve, reject) {
// Get data service
VSS.getService(VSS.ServiceIds.ExtensionData).then(function (dataService: IExtensionDataService) {
// Set value in collection scope
dataService.setValue(pssVersion + "_" + key, value/*, { scopeType: "Project Collection" }*/).then(function (setToThis: string) {
console.log(pssVersion + "_" + key + " is now " + setToThis );
resolve(setToThis);
}, function (error) {
reject(error);
console.log(error);
}, function (error) {
reject(error);
console.log(error);
});
});
}
function getConfiguration(key: string) {
return Q.Promise(function (resolve, reject) {
// Get data service
VSS.getService(VSS.ServiceIds.ExtensionData).then(function (dataService: IExtensionDataService) {
// Get value in collection scope
dataService.getValue(pssVersion + "_" + key/*, { scopeType: "Project Collection" }*/).then(function (gotThis: string) {
sourceControlPath = gotThis;
console.log(pssVersion + "_" + key + " is " + gotThis );
resolve(gotThis);
}, function (error) {
reject(error);
console.log(error);
});
}, function (error) {
reject(error);
console.log(error);
});
});
}
try {
console.log(thisProjectIDclean);
getConfiguration(thisProjectIDclean).then(function (resultPath: string) {
console.log(resultPath);
console.log(sourceControlPath);
if (!resultPath) {
//getProjects().then(function (resultProjects: TFS_Core_Contracts.TeamProjectReference[]) {
// resultProjects.forEach(function (resultProject: TFS_Core_Contracts.TeamProjectReference) {
showSourceControlDialog(thisProjectID/*resultProject*/).then(function () {
getXMLTree();
}, function (error) {
console.log(error);
});
// }, function (error) {
// console.log(error);
// });
//}, function (error) {
// console.log(error);
//});
} else {
getXMLTree();
}
});
} catch (error) {
console.log(error);
}
Unfortunately what you are trying to do is not possible. Referring to the example the "maximum" scope you can store extension data in is the "Project Collection".
From my experience while fiddling with ExtensionDataService it is not possible to query data from a "foreign" collection.

Pass custom data to service worker sync?

I need to make a POST request and send some data. I'm using the service worker sync to handle offline situation.
But is there a way to pass the POST data to the service worker, so it makes the same request again?
Cause apparently the current solution is to store requests in some client side storage and after client gets connection - get the requests info from the storage and then send them.
Any more elegant way?
PS: I thought about just making the service worker send message to the application code so it does the request again ... but unfortunately it doesn't know the exact client that registered the service worker :(
You can use fetch-sync
or i use postmessage to fix this problem, which i agree that indexedDB looks trouble.
first of all, i send the message from html.
// send message to serviceWorker
function sync (url, options) {
navigator.serviceWorker.controller.postMessage({type: 'sync', url, options})
}
i got this message in serviceworker, and then i store it.
const syncStore = {}
self.addEventListener('message', event => {
if(event.data.type === 'sync') {
// get a unique id to save the data
const id = uuid()
syncStore[id] = event.data
// register a sync and pass the id as tag for it to get the data
self.registration.sync.register(id)
}
console.log(event.data)
})
in the sync event, i got the data and fetch
self.addEventListener('sync', event => {
// get the data by tag
const {url, options} = syncStore[event.tag]
event.waitUntil(fetch(url, options))
})
it works well in my test, what's more you can delete the memory store after the fetch
what's more, you may want to send back the result to the page. i will do this in the same way by postmessage.
as now i have to communicate between each other, i will change the fucnction sync into this way
// use messagechannel to communicate
sendMessageToSw (msg) {
return new Promise((resolve, reject) => {
// Create a Message Channel
const msg_chan = new MessageChannel()
// Handler for recieving message reply from service worker
msg_chan.port1.onmessage = event => {
if(event.data.error) {
reject(event.data.error)
} else {
resolve(event.data)
}
}
navigator.serviceWorker.controller.postMessage(msg, [msg_chan.port2])
})
}
// send message to serviceWorker
// you can see that i add a parse argument
// this is use to tell the serviceworker how to parse our data
function sync (url, options, parse) {
return sendMessageToSw({type: 'sync', url, options, parse})
}
i also have to change the message event, so that i can pass the port to sync event
self.addEventListener('message', event => {
if(isObject(event.data)) {
if(event.data.type === 'sync') {
// in this way, you can decide your tag
const id = event.data.id || uuid()
// pass the port into the memory stor
syncStore[id] = Object.assign({port: event.ports[0]}, event.data)
self.registration.sync.register(id)
}
}
})
up to now, we can handle the sync event
self.addEventListener('sync', event => {
const {url, options, port, parse} = syncStore[event.tag] || {}
// delete the memory
delete syncStore[event.tag]
event.waitUntil(fetch(url, options)
.then(response => {
// clone response because it will fail to parse if it parse again
const copy = response.clone()
if(response.ok) {
// parse it as you like
copy[parse]()
.then(data => {
// when success postmessage back
port.postMessage(data)
})
} else {
port.postMessage({error: response.status})
}
})
.catch(error => {
port.postMessage({error: error.message})
})
)
})
At the end. you cannot use postmessage to send response directly.Because it's illegal.So you need to parse it, such as text, json, blob, etc. i think that's enough.
As you have mention that, you may want to open the window.
i advice that you can use serviceworker to send a notification.
self.addEventListener('push', function (event) {
const title = 'i am a fucking test'
const options = {
body: 'Yay it works.',
}
event.waitUntil(self.registration.showNotification(title, options))
})
self.addEventListener('notificationclick', function (event) {
event.notification.close()
event.waitUntil(
clients.openWindow('https://yoursite.com')
)
})
when the client click we can open the window.
To comunicate with the serviceworker I use a trick:
in the fetch eventlistener I put this:
self.addEventListener('fetch', event => {
if (event.request.url.includes("sw_messages.js")) {
var zib = "some data";
event.respondWith(new Response("window.msg=" + JSON.stringify(zib) + ";", {
headers: {
'Content-Type': 'application/javascript'
}
}));
}
return;
});
then, in the main html I just add:
<script src="sw_messages.js"></script>
as the page loads, global variable msg will contain (in this example) "some data".

Resources