From: ebelcrom Date: Sun, 19 Jan 2020 22:25:13 +0000 (+0100) Subject: notification implemented X-Git-Tag: v1.0.0~1 X-Git-Url: http://binomiant.duckdns.org/9wAuyR5S/?a=commitdiff_plain;h=771b771d00b92d92e24ce6d3394bb619eb6f2cdd;p=garnod-pwa.git notification implemented --- diff --git a/src/lib/lib.js b/src/lib/lib.js index 9c213c1..f0bc4d4 100644 --- a/src/lib/lib.js +++ b/src/lib/lib.js @@ -1,3 +1,14 @@ +const url = 'https://binomiant.duckdns.org/mVk7Yr3k/v1'; +const prodStatus = '/status'; +const prodEvents = '/events'; +const prodControl = '/control'; +const prodNotification = '/test/notification'; +const testStatus = '/test/status'; +const testEvents = '/test/events'; +const testControl = '/test/control'; +const base64PubKey = 'BLDSdGasI5sLks30brbIWvlLMFqzoxxkOs7' + + 'aW_E9PDBzIO_mDs6-tvtb2U0-BVFDafNd58DJgoXxdK5711FF29c'; + function setItem(key, value) { if (key === null) { return; @@ -32,17 +43,176 @@ export const storage = { hasItem: hasItem, }; +function urlBase64ToUint8Array(base64String) { + const padding = '='.repeat((4 - base64String.length % 4) % 4); + const base64 = (base64String + padding) + .replace(/\-/g, '+') + .replace(/_/g, '/'); + const rawData = window.atob(base64); + const outputArray = new Uint8Array(rawData.length); + + for (let i = 0; i < rawData.length; ++i) { + outputArray[i] = rawData.charCodeAt(i); + } + + return outputArray; +} + +function register() { + const permission = Notification.permission; + switch (permission) { + case 'granted': + return new Promise((resolve, reject) => { + navigator.serviceWorker.ready + .then(registration => { + if (registration) { + registration.pushManager.getSubscription() + .then(subscription => { + if (subscription) { + resolve(subscription); + } else { + const publicKey = urlBase64ToUint8Array(base64PubKey); + registration.pushManager.subscribe({ + userVisibleOnly: true, + applicationServerKey: publicKey, + }) + .then(subscription => { + if (subscription) { + resolve(subscription); + } else { + reject(null); + } + }); + } + }) + .catch(err => { + reject(err); + }); + } else { + reject(null); + } + }) + .catch(err => { + reject(err); + }); + }); + break; + case 'default': + return new Promise((resolve, reject) => { + Notification.requestPermission() + .then(status => { + if (status) { + switch (status) { + case 'granted': + navigator.serviceWorker.ready + .then(registration => { + if (registration) { + const publicKey = urlBase64ToUint8Array(base64PubKey); + registration.pushManager.subscribe({ + userVisibleOnly: true, + applicationServerKey: publicKey, + }) + .then(subscription => { + if (subscription) { + resolve(subscription); + } else { + reject(null); + } + }); + } else { + reject(null); + } + }) + .catch(err => { + reject(err); + }); + break; + case 'denied': + case 'default': + default: + reject(null); + break; + } + } else { + reject(null); + } + }) + .catch(err => { + reject(err); + }); + }); + break; + case 'denied': + default: + return Promise.reject(); + break; + } +} + +function subscribe() { + var apiKey = getItem('apiKey'); + + var data = { + path: prodNotification, + query: '', + method: 'POST', + apiKey: apiKey, + requestContentType: 'json', + responseContentType: 'json', + body: '', + } + + return new Promise((resolve, reject) => { + register() + .then(subscription => { + if (subscription === null) { + reject(null); + } else { + data.body = JSON.stringify(subscription); + sendRequest(data) + .then(response => { + resolve(response); + }) + .catch(err => { + reject(err); + }); + } + }) + .catch(err => { + reject(err); + }); + }); +} + +function unsubscribe() { + return new Promise((resolve, reject) => { + navigator.serviceWorker.ready + .then(registration => { + registration.pushManager.getSubscription() + .then(subscription => { + subscription.unsubscribe() + .then(result => { + resolve(result); + }) + .catch(err => { + reject(err); + }); + }) + .catch(err => { + reject(err); + }); + }) + .catch(err => { + reject(err); + }); + }); +} + export const notification = { + subscribe: subscribe, + unsubscribe: unsubscribe, }; -const url = 'https://binomiant.duckdns.org/mVk7Yr3k/v1'; -const prodStatus = '/status'; -const prodEvents = '/events'; -const prodControl = '/control'; -const testStatus = '/test/status'; -const testEvents = '/test/events'; -const testControl = '/test/control'; - async function sendRequest(data) { var init = { method: data.method, @@ -51,11 +221,14 @@ async function sendRequest(data) { if (data.requestContentType != null) { init.headers['Content-Type'] = 'application/json'; } - if (data.apiKey == null) { + if (data.apiKey === '') { init.headers['X-API-Key-Test'] = '2TTqCD4mNNny'; } else { init.headers['X-API-Key'] = data.apiKey; } + if (data.body) { + init.body = data.body; + } const response = await fetch(url + data.path + data.query, init); if (response.status >= 200 && response.status <= 399) { @@ -73,7 +246,7 @@ async function sendRequest(data) { function getStatus() { const testMode = getItem('testMode'); var path = '', - apiKey = null; + apiKey = ''; var query = new URLSearchParams(''); if (testMode) { @@ -99,7 +272,7 @@ function getStatus() { function getEvents() { const testMode = getItem('testMode'); var path = '', - apiKey = null; + apiKey = ''; var query = new URLSearchParams(''); if (testMode) { @@ -180,7 +353,7 @@ function getEvents() { function postControl() { const testMode = getItem('testMode'); var path = '', - apiKey = null; + apiKey = ''; var query = new URLSearchParams(''); if (testMode) { diff --git a/src/service-worker.js b/src/service-worker.js new file mode 100644 index 0000000..c093648 --- /dev/null +++ b/src/service-worker.js @@ -0,0 +1,59 @@ +/* Custom part */ + +workbox.core.setCacheNameDetails({prefix: "garnod-pwa.git"}); + +self.addEventListener('message', (event) => { + if (event.data && event.data.type === 'SKIP_WAITING') { + self.skipWaiting(); + } +}); + +self.addEventListener('push', event => { + const notificationTitle = 'Garage Node'; + + var message = 'Door is open!'; + var icon = null; + if (event.data) { + message = event.data.json().message; + icon = event.data.json().icon; + } + + var notificationOptions = { + body: message, + tag: 'notification' + }; + if (icon) { + notificationOptions.badge = icon; + notificationOptions.icon = icon; + } + + event.waitUntil( + Promise.all([ + self.registration.showNotification( + notificationTitle, notificationOptions) + ]) + ); +}); + +self.addEventListener('notificationclick', event => { + event.notification.close(); + + var clickResponsePromise = Promise.resolve(); + if (event.notification.data && event.notification.data.url) { + clickResponsePromise = clients.openWindow(event.notification.data.url); + } + + event.waitUntil( + Promise.all([ + clickResponsePromise + ]) + ); +}); + +/** + * The workboxSW.precacheAndRoute() method efficiently caches and responds to + * requests for URLs in the manifest. + * See https://goo.gl/S9QRab + */ +self.__precacheManifest = [].concat(self.__precacheManifest || []); +workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); diff --git a/src/views/Settings.vue b/src/views/Settings.vue index 1a59720..ae173fa 100644 --- a/src/views/Settings.vue +++ b/src/views/Settings.vue @@ -48,12 +48,28 @@ :showApiKeyDialog="showApiKeyDialog" v-on:apiKeySetEvent="setApiKey" /> + + + {{snackbar.text}} +