Add HTML 5 push notifcations support (#89)

* Add push notification handling to service worker

* Add push registration to sidebar

* Whitelist manifest.json

* Remove unused property

* Catch if no url specified

* Fix eslint

* Fix bug

* Fix some bugs

* More Firefox proof

* Moar fixes

* Fix semi
This commit is contained in:
Paulus Schoutsen 2016-08-14 00:31:52 -07:00 committed by GitHub
parent 474366c536
commit 5efe930d6c
5 changed files with 129 additions and 33 deletions

@ -1 +1 @@
Subproject commit 4f22ce18a788888bff1cbdafb3586edc715ba29e
Subproject commit bf4adea64549d0adb95597f1846d90bc47648c0e

View File

@ -9,7 +9,7 @@
"scripts": {
"setup_js_dev": "git submodule init && git submodule update && cd home-assistant-js && npm install",
"clean": "rm -rf build/* build-temp/*",
"js_dev": "script/sw-dev.js && npm run watch_ru_all",
"js_dev": "script/sw-precache.js && npm run watch_ru_all",
"js_dev_demo": "BUILD_DEMO=1 npm run watch_ru_all",
"js_prod": "BUILD_DEV=0 npm run ru_all",
"js_demo": "BUILD_DEV=0 BUILD_DEMO=1 npm run ru_all",

View File

@ -1,10 +0,0 @@
#! /usr/bin/env node
var fs = require('fs');
var path = require('path');
var content = `
console.warn('Service worker disabled in dev mode');
`;
fs.writeFileSync(path.join('build', 'service_worker.js'), content);

View File

@ -15,9 +15,10 @@ var path = require('path');
var swPrecache = require('sw-precache');
var uglifyJS = require('uglify-js');
const DEV = !!JSON.parse(process.env.BUILD_DEV || 'true');
var rootDir = '..';
var panelDir = rootDir + '/panels';
// var panels = fs.readdirSync(panelDir);
var dynamicUrlToDependencies = {
'/': [rootDir + '/frontend.html', rootDir + '/core.js'],
@ -55,9 +56,9 @@ panelsFingerprinted.forEach(panel => {
dynamicUrlToDependencies[url] = [fpath];
});
var options = {
const options = {
navigateFallback: '/',
navigateFallbackWhitelist: [/^((?!(static|api|local|service_worker.js)).)*$/],
navigateFallbackWhitelist: [/^((?!(static|api|local|service_worker.js|manifest.json)).)*$/],
dynamicUrlToDependencies: dynamicUrlToDependencies,
staticFileGlobs: [
rootDir + '/icons/favicon.ico',
@ -74,9 +75,55 @@ var options = {
verbose: true,
};
var genPromise = swPrecache.generate(options);
const devBase = 'console.warn("Service worker caching disabled in development")';
if (true) {
const notify = `
self.addEventListener("push", function(event) {
var data;
if (event.data) {
data = event.data.json();
event.waitUntil(self.registration.showNotification(data.title, data));
}
});
self.addEventListener('notificationclick', function(event) {
var url;
if (!event.notification.data || !event.notification.data.url) {
return;
}
event.notification.close();
url = event.notification.data.url;
if (!url) return;
event.waitUntil(
clients.matchAll({
type: 'window',
})
.then(function (windowClients) {
var i;
var client;
for (i = 0; i < windowClients.length; i++) {
client = windowClients[i];
if (client.url === url && 'focus' in client) {
return client.focus();
}
}
if (clients.openWindow) {
return clients.openWindow(url);
}
return undefined;
})
);
});
`;
let genPromise = DEV ? Promise.resolve(devBase) : swPrecache.generate(options);
genPromise = genPromise.then(swString => swString + '\n' + notify);
if (!DEV) {
genPromise = genPromise.then(
swString => uglifyJS.minify(swString, { fromString: true }).code);
}

View File

@ -78,7 +78,7 @@
opacity: var(--dark-divider-opacity)
}
.streaming {
.setting {
@apply(--sidebar-text);
}
@ -120,8 +120,18 @@
<div>
<div class='divider'></div>
<template is='dom-if' if='[[supportPush]]'>
<paper-item class='horizontal layout justified'>
<div class='setting'>Push Notifications</div>
<paper-toggle-button
on-change='handlePushChange'
checked='{{pushToggleChecked}}'
></paper-toggle-button>
</paper-item>
</template>
<paper-item class='horizontal layout justified'>
<div class='streaming'>Streaming updates</div>
<div class='setting'>Streaming updates</div>
<stream-status hass='[[hass]]'></stream-status>
</paper-item>
@ -180,20 +190,6 @@ Polymer({
},
},
hasHistoryComponent: {
type: Boolean,
bindNuclear: function (hass) {
return hass.configGetters.isComponentLoaded('history');
},
},
hasLogbookComponent: {
type: Boolean,
bindNuclear: function (hass) {
return hass.configGetters.isComponentLoaded('logbook');
},
},
panels: {
type: Array,
bindNuclear: function (hass) {
@ -203,6 +199,19 @@ Polymer({
];
},
},
supportPush: {
type: Boolean,
value: 'PushManager' in window &&
(document.location.protocol === 'https:' ||
document.location.hostname === 'localhost'),
},
pushToggleChecked: {
type: Boolean,
value: 'Notification' in window &&
Notification.permission === 'granted',
},
},
created: function () {
@ -283,6 +292,56 @@ Polymer({
this.debounce('updateStyles', this._boundUpdateStyles, 1);
},
handlePushChange: function (ev) {
var subscribe = ev.target.checked;
// MVP, will move later.
var promise = navigator.serviceWorker.getRegistration().then(function (reg) {
if (!reg) {
throw new Error('No service worker registered');
}
return reg.pushManager.subscribe({
userVisibleOnly: true,
});
});
if (!subscribe) {
promise.then(function (sub) {
sub.unsubscribe();
});
return;
}
promise.then(function (sub) {
var browserName;
if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
browserName = 'firefox';
} else {
browserName = 'chrome';
}
return this.hass.callApi('POST', 'notify.html5', {
subscription: sub,
browser: browserName,
});
}.bind(this)).catch(function (err) {
var message;
if (err.message && err.message.indexOf('gcm_sender_id') !== -1) {
message = 'Please setup the notify.html5 platform.';
} else {
message = 'Notification registration failed.';
}
/* eslint-disable no-console */
console.error(err);
/* eslint-enable no-console */
this.hass.notificationActions.createNotification(message);
this.pushToggleChecked = false;
}.bind(this));
},
handleLogOut: function () {
this.hass.authActions.logOut();
},