Background sync taking too much time after getting internet connection, workbox - service-worker

I am using service worker to achieve background sync functionality. Following is my code:
importScripts( 'https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox-sw.js' )
const queue = new workbox.backgroundSync.Queue('registerQueue', { callbacks: {
queueDidReplay: function(requestArray) {
let requestSynced = 0
requestArray.forEach(item => {
if (!item.error) {
requestSynced++
}
})
if (requestSynced > 0) {
new BroadcastChannel('backgroundSynBroadCastChannel').postMessage(
requestSynced
)
}
} } }) const GraphQLMatch = /graphql(\S+)?/
self.addEventListener('fetch', event => { if (
null !== event.request.url.match(GraphQLMatch) &&
navigator.onLine === false ) {
const promiseChain = fetch(event.request.clone()).catch(err => {
return queue.addRequest(event.request)
})
event.waitUntil(promiseChain) } })
self.addEventListener('message', event => { if (!event.data) {
return }
switch (event.data) {
case 'skipWaiting':
self.skipWaiting()
break
default:
break } })
workbox.precaching.precacheAndRoute([])
/* * Alternate for navigateFallback & navigateFallbackBlacklist */ workbox.routing.registerNavigationRoute('/index.html', { blacklist: [/^\/__.*$/] })
On internet disconnect, the requests are queued on the indexed DB. But the problem is After acquiring the connection back, the background sync is made at least 5-10 mins later. Is there any way to do the background sync immediately upon internet re-connection or at least reduce the time for syncing.
Thanks in advance.

You could manually trigger a replay of a queue as soon as your connection is back by sending an event to the service worker.
In your service worker:
self.addEventListener('message', (event) => {
if (event.data.type === 'replayQueue') {
queue.replayRequests();
}
});
In your app (using workbox-window):
if ('serviceWorker' in navigator) this.wb = new Workbox('/service-worker.js');
window.addEventListener(‘online’, function(event){
this.wb.messageSW({type: 'replayQueue'});
});

Unfortunately, doesn't look like it's possible right now to change the timing for the sync. According to Google's Workbox documentation:
Browsers that support the BackgroundSync API will automatically replay
failed requests on your behalf at an interval managed by the browser,
likely using exponential backoff between replay attempts.
If Google's documentation is correct (at least for Chrome) that also means that the longer the user has been offline, the probability of a longer wait for the sync event increases.
#cyril-hanquez's idea is good as long as the user is still utilizing your site when they come back online. You might also want to add a "fetchDidFail" callback to handle more network outage edge cases. Along those lines: you might want to avoid relying on the status of "navigator.onLine", since it doesn't always do what one would expect.

Related

Background sync doesn't refresh page when back online

recently I started learning about service workers, background syncs... I implemented service worker and in install step I cached some files I want to show when offline.
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE)
.then((cache) => {
return cache.addAll([navigationIcon, offlineImage, offlineFallbackPage]);
})
);
});
I am listening to fetch event to catch when there is no internet connection so I can show offline page when then.
self.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(() => {
// Return the offline page
return caches.match(offlineFallbackPage);
})
);
} else {
event.respondWith(caches.match(event.request)
.then((response) => {
return response || fetch(event.request);
}));
}
});
I also added background sync, so I can go back online when there is internet connection.
After registering service worker I added:
.then(swRegistration => swRegistration.sync.register('backOnline'))
And I listen to sync event in my service worker.
When I'm offline and go back online nothing happens. BUT when I delete my fetch event (don't show previously cached offline page) then page goes back online by itself (which I want to do when I have fetch event)
Does anyone know what should I add so my page can go back online by itself?
You can use navigator, Include it in your main js file that is cached or in your service-worker js file, just ensure it's cached
let onlineStatus = locate => {
if(navigator.onLine){
location.replace(locate)
}
}
let isOfflinePage = window.location.pathname == offlineFallbackPage ? true : false;
// kindly edit isOfflinePage to return true if it's offline page
if(isOfflinePage){
onlineStatus('index.html')
}
You can simply use location.reload() instead

Flutter Blue Writing Automatically

I have used Flutter Blue for a college work, where I need to create an application to fetch and pass information to an equipment. The passing of this data must be automatic, as in any application (after all the end user should not look for the services and characteristics necessary to carry out the process). The problem is that I am not being able to perform the data passing soon after connecting with the device.
I'm using the App example I downloaded at https://github.com/pauldemarco/flutter_blue, so the basic idea is that as soon as I connect to my bluetooth device I send a message to a certain device. There is already an answered question that has the interest of setting notifications when connecting at Flutter Blue Setting Notifications
I followed the same example but instead of using _setNotification (c) I used the _writeCharacteristic (c), but it does not work.
_connect(BluetoothDevice d) async {
device = d;
// Connect to device
deviceConnection = _flutterBlue
.connect(device, timeout: const Duration(seconds: 4))
.listen(
null,
onDone: _disconnect,
);
// Update the connection state immediately
device.state.then((s) {
setState(() {
deviceState = s;
});
});
// Subscribe to connection changes
deviceStateSubscription = device.onStateChanged().listen((s) {
setState(() {
deviceState = s;
});
if (s == BluetoothDeviceState.connected) {
device.discoverServices().then((s) {
services = s;
for(BluetoothService service in services) {
for(BluetoothCharacteristic c in service.characteristics) {
if(c.uuid == new Guid("06d1e5e7-79ad-4a71-8faa-373789f7d93c")) {
_writeCharacteristic(c);
} else {
print("Nope");
}
}
}
setState(() {
services = s;
});
});
}
});
}
I have changed the original code so that it prints me the notifications as soon as I perform the writing method. The notifications should show me a standard message that is in the firmware of the device, but instead it is printing me the Local Name of the bluetooth chip, being that if I select the service and characteristic manually the return is the correct message.
You'd need to elaborate how you're executing writes on the descriptor - inside _writeCharacteristic(c).
BluetoothDescriptor.write() is a Future per docs, you should be able to catch any errors thrown during write.

How to subscribe to and see events from Hyperledger Composer transactions

I am running an NodeJS server with the following code to connect to the Hyperledger Runtime:
const BusinessNetworkConnection = require("composer-client")
.BusinessNetworkConnection;
this.businessNetworkConnection = new BusinessNetworkConnection();
this.CONNECTION_PROFILE_NAME = "hlfv1";
this.businessNetworkIdentifier = "testNetwork";
this.businessNetworkConnection
.connect(
this.CONNECTION_PROFILE_NAME,
this.businessNetworkIdentifier,
"admin",
"adminpwd"
)
.then(result => {
this.businessNetworkDefinition = result;
console.log("BusinessNetworkConnection: ", result);
})
.then(() => {
// Subscribe to events.
this.businessNetworkConnection.on("events", events => {
console.log("**********business event received**********", events);
});
})
// and catch any exceptions that are triggered
.catch(function(error) {
throw error;
});
I see data returned after the connection has been made in the result object and it is the correct network data that has been deployed.
However, when I submit transactions and made request VIA my generated REST APIs no events are seen by my server. In the Historian, I can see that events are emitted. Is there something else that I should be doing to see those events emitted by my transactions?
I tried same kind of test and I could receive events. I compared my test code and yours, and I found following difference:
this.bizNetworkConnection.on('events'
this.bizNetworkConnection.on('event'
I hope it helps.

ServiceWorker not receiving fetch requests

I am installing a service worker for the first time, and following the tutorial at: https://developers.google.com/web/fundamentals/getting-started/primers/service-workers
My service worker behaves as expected when installing and updating, but fetch requests are not triggered as expected.
var CACHE_NAME = 'test-cache-v1'
var urlsToCache = [
'/',
'/public/scripts/app.js'
]
self.addEventListener('install', function (event) {
console.log('Installing new service worker', event)
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(function (cache) {
return cache.addAll(urlsToCache)
})
.catch(err => console.log('Error Caching', err))
)
})
self.addEventListener('fetch', function (event) {
console.log('Fetch req', event)
event.respondWith(
caches.match(event.request)
.then(function (response) {
console.log('Cache hit', response)
// Cache hit - return response
if (response) {
return response
}
return fetch(event.request)
.catch(e => console.log('Error matching cache', e))
}
)
)
})
I see 'Installing new service worker' outputted to the console when expected, but not 'Fetch req'. I am using Chrome devtools and have accessed the "Inspect" option next to the ServiceWorker under the Application tab.
If you listen for the activate event, and add in a call to clients.claim() inside that event, then your newly active service worker will take control over existing web pages in its scope, including the page that registered it. There's more information in this article on the service worker lifecycle. The following code is sufficient:
self.addEventListener('activate', () => self.clients.claim());
If you don't call clients.claim(), then the service worker will activate, but not control any of the currently open pages. It won't be until you navigate to the next page under its scope (or reload a current page) that the service worker will take control, and start intercepting network requests via its fetch handler.
On dynamic websites, be careful!
If service worker has scope: example.com/weather/
It does not have scope: example.com/weather
Especially on firebase which by default removes trailing slash
In this case, service worker will install, activate, and even cache files, but not receive ‘fetch’ events! Very hard to debug.
Add “trailingSlash”: true to firebase.json under ‘hosting’. This will solve the problem. Make sure to modify rewrite from:
{
"source": "/weather", "function": "weather"
}
To :
{
"source": "/weather/", "function": "weather"
}
As well as manifest.json
I found that Jeff Posnick's "clients.claim()" in the activate event handler was useful, but it was not enough to cache resources the first time the JS app runs. That is because on the first run the service worker has not finished activating when the JS starts loading its resources.
The following function lets the main app register the SW and then waits for it to activate before continuing to load resources:
/**
* Registers service worker and waits until it is activated or failed.
* #param js URI of service worker JS
* #param onReady function to call when service worker is activated or failed
* #param maxWait maximum time to wait in milliseconds
*/
function registerServiceWorkerAndWaitForActivated(js, onReady, maxWait) {
let bReady = false;
function setReady() {
if (!bReady) {
bReady = true;
onReady();
}
}
if ('serviceWorker' in navigator) {
setTimeout(setReady, maxWait || 1000);
navigator.serviceWorker.register(js).then((reg) => {
let serviceWorker = reg.installing || reg.waiting;
if (serviceWorker) {
serviceWorker.addEventListener("statechange", (e) => {
if (serviceWorker.state == "activated")
setReady();
});
} else {
if (!reg.active)
console.log("Unknown service worker state");
setReady();
}
}, () => setReady());
} else {
let msg = "ServiceWorker not available. App will not run offline."
if (document.location.protocol != "https:")
msg = "Please use HTTPS so app can run offline later.";
console.warn(msg);
alert(msg);
setReady();
}
}

WinJS.xhr Timeout Loses Requests?

What I'm trying to do (though I fully suspect there's a better way to do it) is to send HTTP requests to a range of hosts on my network. I can hit every host by calling WinJS.xhr in a loop. However, it takes too long to complete the range.
Inspecting in Fiddler shows that a dozen or so requests are sent at a time, wait to time out, and then move on to the next dozen or so. So I figured I'd try to reduce the timeout for each request. For my needs, if the host doesn't respond in 500 ms, it's not going to respond.
Following the documentation, I tried wrapping the call to WinJS.xhr in a call to WinJS.Promise.timeout with a small enough setting, but there was no change. Changing the promise timeout didn't really affect the actual request.
A little more searching led me to a suggestion whereby I could modify the XMLHttpRequest object that WinJS.xhr uses and set the timeout on that. This worked like a charm in terms of blasting out requests at a faster rate. However, there seems to be a side-effect.
Watching the requests in Fiddler, about a dozen or so fire off very quickly and then the whole thing ends. The "next dozen or so" never come. Sometimes (based on the semi-randomness of asynchronous calls) the first dozen or so that shows up in fiddler includes 9-10 from the low and of the range and 2-3 from the top end of the range, or close to it.
Is there something else I can try, or some other way to accomplish the end goal here? (Within the scope of this question the end goal is to send a large number of requests in a reasonable amount of time, but any suggestions on a better overall way to scan for a particular service on a network is also welcome.)
Can you write out the code you're using for timeout, i wrote something like this but it wasn't working, so I'm curious as to how you're doing it:
var timeoutFired = function () {
console.log("derp");
};
var options = {
url: "http://somesite.com",
responseType: "document",
customRequestInitializer: function (req) {
req.timeout = 1;
req.ontimeout = timeoutFired;
//do something with the XmlHttpRequest object req
}
};
WinJS.xhr(options).
....
Here are some alternatives that you may find helpful, not sure how/why timeout wasn't working but I tried to write out a custom timeout function:
(function (global) {
var options = {
url: "http://something.com",
responseType: "document",
};
var request = WinJS.xhr(options).then(
function (xmlHttpRequest) {
console.log("completed");
},
function (xmlHttpRequest) {
//error or cancel() will throw err
console.log("error"+ xmlHttpRequest.message);
},
function (xmlHttpRequest) {
console.log("progress")
});
function waitTime() {
return new WinJS.Promise(
function (complete, error, progress) {
var seconds = 0;
var interval = window.setInterval(
function () {
seconds++;
progress(seconds);
//prob should be called milliseconds
if (seconds > 5) {
window.clearInterval(interval);
complete();
}
}, 100);
});
};
waitTime().done(
function () {
console.log("complete");
request.cancel();
},
function () {
console.log("error")
},
function (seconds) {
console.log("progress:" + seconds)
});
});
Another cool little trick is using promise.any (vs .join) which fires off when one OR the other finishes first, so taking that into account you can write something like this:
(function (global) {
var options = {
url: "http://url.com",
responseType: "document",
};
var request = {
runRequest: function () {
return WinJS.xhr(options).then(
function (xmlHttpRequest) {
console.log("completed");
},
function (xmlHttpRequest) {
//error or cancel() will throw err
console.log("error" + xmlHttpRequest.message);
},
function (xmlHttpRequest) {
console.log("progress")
});
}
};
WinJS.Promise.any([WinJS.Promise.timeout(500), request.runRequest()]).done(
function () {
console.log("any complete");
});
})();

Resources