I have a kiosk that processes portraits of people. It is running in chrome.
One out of 200 people who use the kiosk end up with it freezing during the process. When it freezes, I never get the "ah snap" and the page just kinda hangs.
I was wondering if it is possible to make an extension to monitor the page and check for a heartbeat - If it is not seen after 30 seconds reload the window.
Any help would be great.
Have a content script in the kiosk page send a message every X seconds back to the background page
In the content script:
var port = chrome.extension.connect({ name: "keep-alive" });
port.postMessage('is-alive', { alive: true });
setInterval(function () {
port.postMessage('is-alive', { alive: true });
}, 1000 * 15);
If the background page detects the message hasn't come back after a certain time then reload the tab
In the background page:
var last = Date.now();
var interval = 1000 * 15;
chrome.extension.onConnect.addListener(function (port) {
if (port.name === 'keep-alive') {
port.onMessage.addListener(function (data) {
if (data.type === 'is-alive' && data.payload.alive === true) {
last = Date.now();
}
});
}
});
setInterval(function () {
if (Date.now() - last > interval) {
// Reload the tab...
}
}, interval);
For information about reloading the tab, see the chrome.tabs documentation. You will need to add tabs to your permission list in the manifest.
If you'd like to know more about message passing, see the Messaging docs.
Related
I build an html/js application (a progressive web app) with Polymer and polymer-cli and the well generated service-worker for caching and offline.
I wonder how to notify the user when a new version of the application is available and invite him to restart browser.
any ideas ?
Edit
a talk at IO2016 where Eric Bidel talk about service worker and notify user about new version of an application :
https://youtu.be/__KvYxcIIm8?list=PLOU2XLYxmsILe6_eGvDN3GyiodoV3qNSC&t=1510
Need to check the google IO Web source code
References:
https://developers.google.com/web/fundamentals/instant-and-offline/service-worker/lifecycle
https://classroom.udacity.com/courses/ud899
// page script
document.addEventListener('DOMContentLoaded', function(){
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/sw.js')
.then(function(registration) {
console.info('ServiceWorker registration successful with scope:', registration.scope);
// if there's no controller, this page wasn't loaded
// via a service worker, so they're looking at the latest version.
// In that case, exit early
if (!navigator.serviceWorker.controller) return;
// if there's an updated worker already waiting, update
if (registration.waiting) {
console.info('show toast and upon click update...');
registration.waiting.postMessage({ updateSw: true });
return;
}
// if there's an updated worker installing, track its
// progress. If it becomes "installed", update
if (registration.installing) {
registration.addEventListener('statechange', function(){
if (registration.installing.state == 'installed'){
console.info('show toast and upon click update...');
registration.installing.postMessage({ updateSw: true });
return;
}
});
}
// otherwise, listen for new installing workers arriving.
// If one arrives, track its progress.
// If it becomes "installed", update
registration.addEventListener('updatefound', function(){
let newServiceWorker = registration.installing;
newServiceWorker.addEventListener('statechange', function() {
if (newServiceWorker.state == 'installed') {
console.info('show toast and upon click update...');
newServiceWorker.postMessage({ updateSw: true });
}
});
});
})
.catch(function(error) {
console.info('ServiceWorker registration failed:', error);
});
navigator.serviceWorker.addEventListener('controllerchange', function() {
window.location.reload();
});
}
});
// sw script
self.addEventListener('message', function(e) {
if (e.data.updateSw){
self.skipWaiting();
}
});
Thanks to IO team .. we need to check if the current service-worker becomes redundant
// Check to see if the service worker controlling the page at initial load
// has become redundant, since this implies there's a new service worker with fresh content.
if (navigator.serviceWorker && navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.onstatechange = function(event) {
if (event.target.state === 'redundant') {
// Define a handler that will be used for the next io-toast tap, at which point it
// be automatically removed.
const tapHandler = function() {
window.location.reload();
};
if (IOWA.Elements && IOWA.Elements.Toast &&
IOWA.Elements.Toast.showMessage) {
IOWA.Elements.Toast.showMessage(
'A new version of this app is available.', tapHandler, 'Refresh',
null, 0); // duration 0 indications shows the toast indefinitely.
} else {
tapHandler(); // Force reload if user never was shown the toast.
}
}
};
}
I built an offline first app using the appcache a while ago and wanted to convert it to using the service-worker (my clients all use the latest chrome so I don't have any browser compatibility issues).
I'm using sw-precache to generate a service-worker that caches my local assets (specifically, my html/css/fonts and also some js) and it looks like when the service-worker installs, it does successfully add all the assets to cache storage and it does successfully start (install and activate both fire and complete successfully. And I have the self.skipWaiting() at the end of the install event to start the service-worker (which it does successfully as well)).
The issue is that the "fetch" event doesn't seem to ever fire. As such, if I go offline or open a browser (while already offline) and navigate to the site, I get the Chrome offline dinosaur. When I look at the network tab, it looks like the browser is trying to hit a server to retrieve the pages. I'm not sure what I'm doing wrong and I didn't touch the fetch method that was generated by the sw-precache utility...so I'm not sure what I'm missing. Any help would be greatly appreciated. My fetch event is below:
self.addEventListener('fetch', function(event) {
if (event.request.method === 'GET') {
var urlWithoutIgnoredParameters = stripIgnoredUrlParameters(event.request.url,
IgnoreUrlParametersMatching);
var cacheName = AbsoluteUrlToCacheName[urlWithoutIgnoredParameters];
var directoryIndex = 'index.html';
if (!cacheName && directoryIndex) {
urlWithoutIgnoredParameters = addDirectoryIndex(urlWithoutIgnoredParameters, directoryIndex);
cacheName = AbsoluteUrlToCacheName[urlWithoutIgnoredParameters];
}
var navigateFallback = '';
// Ideally, this would check for event.request.mode === 'navigate', but that is not widely
// supported yet:
// https://code.google.com/p/chromium/issues/detail?id=540967
// https://bugzilla.mozilla.org/show_bug.cgi?id=1209081
if (!cacheName && navigateFallback && event.request.headers.has('accept') &&
event.request.headers.get('accept').includes('text/html') &&
/* eslint-disable quotes, comma-spacing */
isPathWhitelisted([], event.request.url)) {
/* eslint-enable quotes, comma-spacing */
var navigateFallbackUrl = new URL(navigateFallback, self.location);
cacheName = AbsoluteUrlToCacheName[navigateFallbackUrl.toString()];
}
if (cacheName) {
event.respondWith(
// Rely on the fact that each cache we manage should only have one entry, and return that.
caches.open(cacheName).then(function(cache) {
return cache.keys().then(function(keys) {
return cache.match(keys[0]).then(function(response) {
if (response) {
return response;
}
// If for some reason the response was deleted from the cache,
// raise and exception and fall back to the fetch() triggered in the catch().
throw Error('The cache ' + cacheName + ' is empty.');
});
});
}).catch(function(e) {
console.warn('Couldn\'t serve response for "%s" from cache: %O', event.request.url, e);
return fetch(event.request);
})
);
}
}
});
I have a common serviceworker escenario, where I want catch a notification click and focus the tab where the notification has come from. However, clients variable is always empty, its lenght is 0
console.log("sw startup");
self.addEventListener('install', function (event) {
console.log("SW installed");
});
self.addEventListener('activate', function (event) {
console.log("SW activated");
});
self.addEventListener("notificationclick", function (e) {
// Android doesn't automatically close notifications on click
console.log(e);
e.notification.close();
// Focus tab if open
e.waitUntil(clients.matchAll({
type: 'window'
}).then(function (clientList) {
console.log("clients:" + clientList.length);
for (var i = 0; i < clientList.length; ++i) {
var client = clientList[i];
if (client.url === '/' && 'focus' in client) {
return client.focus();
}
}
if (clients.openWindow) {
return clients.openWindow('/');
}
}));
});
And the registration is this one:
this.doNotify = function (notification) {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js').then(function (reg) {
requestCreateNotification(notification, reg);
}, function (err) {
console.log('sw reg error:' + err);
});
}
...
}
chrome://serviceworker-internals/ output shows that registration and installation are fine. However, when a notification is pushed, clientList is empty. I have tried removing the filter type:'window' but the result is still the same. As clients are empty, a new window is always opened. What am I doing wrong?
The suspicion in your own comment is correct. A page is controlled by a service worker on navigation to an origin that the service worker is registered for. So the original page load that actually initializes the service worker is not itself controlled. That's why the worker only finds your tab once you visit with a new tab or do a refresh.
However (as Jeff Posnick points out in the comments) you can get uncontrolled pages as follows: ServiceWorkerClients.matchAll({includeUncontrolled: true, type: 'window'}).
Try making the service worker immediately claim the page.
E.g.:
self.addEventListener('install', event => event.waitUntil(self.skipWaiting()));
self.addEventListener('activate', event => event.waitUntil(self.clients.claim()));
For a more complex example, see https://serviceworke.rs/immediate-claim.html.
I am using WebKit Notifications for my app. Say if I am using this code:
var n = window.webkitNotifications.createNotification(
'icon.png',
'New Comment',
'Praveen commented on your post!'
);
n.onclick = function(x) { window.focus(); this.cancel(); };
n.show();
PS 1: The first five lines are actually a single line. Just for readability I have posted this way.
PS 2: For the full code, please see this: Unable to show Desktop Notifications using Google Chrome.
My question is, what if I have more than one tab opened?
Say if this is gonna get fired when a new comment appears on my app. What if I have more than one tab open? Will this generate many notifications? Say, I have 10 - 15 tabs open and I get two notifications fired. How many notifications will be generated, 20 - 30?
If that is the case, how to prevent generation of a single notification multiple times for each opened tab?
You just need to specify "tag" option for notification. Notifications with the same value in tag only shows once even if many tabs are opened.
For example:
var notification = new Notification('Hey!', {
body : 'So nice to hear from you',
tag : 'greeting-notify',
icon : 'https://mysite.com/my_funny_icon.png'
});
A detailed explanation of Tagging notifications so only the last one appears is available
on the MDN docs site
An excerpt of the code [just in case the docs go down]
The HTML
<button>Notify me!</button>
The JS
window.addEventListener('load', function () {
// At first, let's check if we have permission for notification
// If not, let's ask for it
if (Notification && Notification.permission !== "granted") {
Notification.requestPermission(function (status) {
if (Notification.permission !== status) {
Notification.permission = status;
}
});
}
var button = document.getElementsByTagName('button')[0];
button.addEventListener('click', function () {
// If the user agreed to get notified
// Let's try to send ten notifications
if (Notification && Notification.permission === "granted") {
for (var i = 0; i < 10; i++) {
// Thanks to the tag, we should only see the "Hi! 9" notification
var n = new Notification("Hi! " + i, {tag: 'soManyNotification'});
}
}
// If the user hasn't told if he wants to be notified or not
// Note: because of Chrome, we are not sure the permission property
// is set, therefore it's unsafe to check for the "default" value.
else if (Notification && Notification.permission !== "denied") {
Notification.requestPermission(function (status) {
if (Notification.permission !== status) {
Notification.permission = status;
}
// If the user said okay
if (status === "granted") {
for (var i = 0; i < 10; i++) {
// Thanks to the tag, we should only see the "Hi! 9" notification
var n = new Notification("Hi! " + i, {tag: 'soManyNotification'});
}
}
// Otherwise, we can fallback to a regular modal alert
else {
alert("Hi!");
}
});
}
// If the user refuses to get notified
else {
// We can fallback to a regular modal alert
alert("Hi!");
}
});
});
As it turns out the answer to my prior question does not work. The problem is that I am overloading the server with requests. The process needs a throttle so that subsequent requests incur a little slowdown. Here is the code in question. The important segment of code that needs a timer or setInterval is that prefaced by the alert "Profile Rejected" although it would be acceptable to slow them both down. Any suggestions?
if (greetThisOne==true && !bGreeted)
{
//alert ("Requesting Message Page");
console.log="Message Page Requested";
chrome.extension.sendRequest({cmd: "openMessage", url: messageLink, keyWordsFound: keyWordList, greeted: bGreeted});
}
else
{
//alert("Profile Rejected");
console.log="Profile Rejected";
chrome.extension.sendRequest({cmd: "profileRejected", url: messageLink, keyWordsFound: keyWordList, greeted: bGreeted});
}
You would need to implement some queue in the background page. For example:
var rejectedProfiles = [];
processRejectedProfiles();
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
if(request.cmd == "profileRejected") {
//add to the end of queue
rejectedProfiles.push({url: request.url, other: request.parameters});
}
sendResponse({});
});
function processRejectedProfiles() {
if(rejectedProfiles.length > 0) {
//get the oldest element in queue
var profile = rejectedProfiles.shift();
//process profile
...
}
//process next entry in the queue in 3 seconds
setTimeout(processRejectedProfiles, 3000);
}
This way you will be processing one profile at a time, with a provided delay.