I'm trying to use background periodic sync for my website. I'm using localhost and registering the periodicsync event at 1*1000 ms, but that doesn't fire at all.
I had a look at this demo, but even if I install the website as an app, it won't fire at all.
Using chrome 87.0.4280.66.
It works however if I manually trigger the periodic sync event from Application developer window.
The periodicsync event will only register correctly when the app is installed as a PWA in a 'most' webkit based browsers only
https://developer.mozilla.org/en-US/docs/Web/API/Web_Periodic_Background_Synchronization_API
The conditions as to whe this will actually fire is unclear and is dependent on some wooly factors such as the users engagement with the website.
That is why the perameter that can be set when registering for the periodic sync is minInterval
This block will help you to register for it successfully, I am, unfortunatly unclear on what the 'real world' scenarios in which the peridic sync will fire:
const status = await navigator.permissions.query({
// #ts-ignore
name: 'periodic-background-sync',
});
if (status.state === 'granted') {
navigator.serviceWorker.ready.then(async (sw: any) => {
await sw.periodicSync.register('periodicsync', {
minInterval: 1000,
});
})
.catch(error => {
console.error('[BackgroundSync] Error: ' + JSON.stringify(error, null, 2));
});
}
else {
console.error('[BackgroundSync] Does not have permission');
}
}
Related
I am developing a PWA that requires Push-Notifications. Sadly IOS/Safari does not support https://w3c.github.io/push-api/#pushmanager-interface for now, so I think i might have to wrap a native APP around in some way.
In Android (before their "Trusted Web Activities" was a thing) you could use a WebView to basically display a headless Chrome-View in your App. Whats the equivalent in IOS and how does the interaction between push-notifications and the Webapp (the browser need to jump to a specific page) work?
One more thing I need is integration with our companys Mobile Device Management, which is Microsoft Intune. Having integrated MDMs in Android in the past i Know that this might be a major pain in the a**, so i'm considering to build the wrapper myself, for maximum flexibility. Another option would be something like Ionic, not sure now.
This may not necessarily work in your situation, but I had the exact same issue with a PWA for Safari and I solved it by just using long polling. It will allow you to get around all of the limitations with Safari and I was able to redirect and load sections within our SPA.
async function subscribe() {
let response = await fetch("/subscribe");
if (response.status == 502) {
// Status 502 is a connection timeout error,
// may happen when the connection was pending for too long,
// and the remote server or a proxy closed it
// let's reconnect
await subscribe();
} else if (response.status != 200) {
// An error - let's show it
showMessage(response.statusText);
// Reconnect in one second
await new Promise(resolve => setTimeout(resolve, 1000));
await subscribe();
} else {
// Get and show the message
let message = await response.text();
showMessage(message);
// Call subscribe() again to get the next message
await subscribe();
}
}
subscribe();
https://javascript.info/long-polling
I'm currently using the latest Workbox version 4.3.1 with workbox-window, I'm offering a page reload for users by listening to the waiting event, I'm using the code provided on the Advanced Recipes page of the Workbox documentation.
The code on the page:
if ('serviceWorker' in navigator) {
const wb = new Workbox('/sw.js');
// Add an event listener to detect when the registered
// service worker has installed but is waiting to activate.
wb.addEventListener('waiting', (event) => {
// `event.wasWaitingBeforeRegister` will be false if this is
// the first time the updated service worker is waiting.
// When `event.wasWaitingBeforeRegister` is true, a previously
// updated same service worker is still waiting.
// You may want to customize the UI prompt accordingly.
// Assumes your app has some sort of prompt UI element
// that a user can either accept or reject.
const prompt = createUIPrompt({
onAccept: async () => {
// Assuming the user accepted the update, set up a listener
// that will reload the page as soon as the previously waiting
// service worker has taken control.
wb.addEventListener('controlling', (event) => {
window.location.reload();
});
// Send a message telling the service worker to skip waiting.
// This will trigger the `controlling` event handler above.
// Note: for this to work, you have to add a message
// listener in your service worker. See below.
wb.messageSW({type: 'SKIP_WAITING'});
},
onReject: () => {
prompt.dismiss();
}
})
});
wb.register();
}
The code in the service worker file:
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
I have tested it and it's working just fine, I can show a snackbar to the user and let them know that there's an update and when they accept, a SKIP_WAITING message is sent to the service worker to call skipWaiting().
Let's assume that the user did not accept the reload prompt and refreshed the page or navigated to another page, the new service worker will be kept waiting and won't activate and that's the normal behavior, but my question is how can I show this reload prompt to the user if they refreshed or navigated to another page? It seems that the waiting event is only fired once.
On button click I successfully register for sync and it fires the functionality I've described in the service-worker.
If I'm offline - it waits for the browser to get connection and then it fires.
BUT - when I click the button the first time and all is fine - from then on clicking the button again successfully registers for sync, but the sync event in the service-worker never triggers:
self.addEventListener('sync', function(event) {
console.log('EVENT in SYNC', event);
}
I see the console logging only the first time I click the button.
Am I missing something?
I figured it out :) ... and the problem was pretty lame: the sync handler in the service worker was returning a promise, but this promise never got resolved. Once I added the resolve() part in the handler returned promise - it all worked fine.
PS: In the Jake Archibald's demo the sync handler was doing self.registration.showNotification, which returns a promise, and this promise resolves once the notification is shown. On other examples they make simple fetch, which also returns promise, and that promise resolves on success. But on my example - I was pulling data from indexedDB and then making fetch.
So - just wrapped everything in
var p = new Promise(function(resolve, reject) {
// get data from indexedDB ..
fetch(...).then(function(response) {
return response;
})
.then(function() {
// fetch was successful ..
resolve();
});
};
return p;
This way it worked correctly.
These API's are very experimental and they are most likely to change, so don't take my word for it. I don't have any piece of documentation to support my experience.
In my case, 'sync' event is triggered only once, by design. So I made it work as I wanted by registering to the SyncManager after enqueuing every must-send request:
self.addEventListener('fetch', evt=> {
if(isSuperImportant(evt.request)) {
evt.respondWith(Promise.resolve(new Response({status: 201})));
evt.waitUntil(
myEnqueue(evt.request).then(()=> {
return self.registration.sync.register('sync-tag');
})
);
}
else {
// process the non-important requests
}
});
self.addEventListener('sync', evt=> {
evt.waitUntil(tryToFlushMyQueue());
});
I register the service worker with this code:
// If the browser supports serviceWorker, and we haven't registered any - we'll register our: sw.js ..
if ('serviceWorker' in navigator && !navigator.serviceWorker.controller) {
navigator.serviceWorker.register('/sw.js').then(function(registrationObj) {
console.log('Registration object: ', registrationObj);
}).catch(function(error) {
// serviceWorker registration failed ..
console.log('Registration failed with ' + error);
});
} else {
console.log('Service worker already registered. Skip registration.')
};
I see my assets appear in the app cache. Then I go to the Application tab in Chrome, choose Service Workers, click offline and refresh the page.
The page opens fine, but I get this in browser console:
http://www.screencast.com/t/1uodUTHM5ig
and this in the Service Worker debugger:
http://www.screencast.com/t/zmqHMi9RJ
Probably because you do not have service worker in cache (And quite naturally. It is not supposed to be cached in the first place.) so it falls back to standard http fetch process, and it fails as app is offline.
That error does not hinder anything and does not effect how your app works. It is just telling you that it has failed to fetch the script. And it was not suppose to success when the app is offline anyway.
Your service worker script probably structured like this;
event.respondWith(
caches.match(event.request).then(function (response) {
if (response) {
return response;
}
// request for service worker .js file falls here as it is not in the cache
// this fails of course since the app is offline
return fetch(event.request).then(function (response) {
return response;
});
})
);
Is there some way to reconnect to Pusher if any error or non-connected state is found?
Here's our connection code:
var pusher = new Pusher('<apikey>', {encrypted: true});
var state = pusher.connection.state;
pusher.connection.bind( 'error', function( err ) {
console.log(err);
});
pusher.connection.bind('state_change', function(states) {
// states = {previous: 'oldState', current: 'newState'}
console.log(states);
});
The Pusher JavaScript library automatically attempts reconnection. You don't need to add any code to support this.
I can't find this anywhere in the Pusher docs, but I know this for a fact as I worked for Pusher for 2 years.
You can test by going to http://test.pusher.com/ and disconnecting from the Internet and then reconnecting again. The logging will show it is auto-reconnecting.