From 049d58939b2adc66f6e0c7bcb8320dba75601d52 Mon Sep 17 00:00:00 2001 From: Jason Robbins Date: Tue, 28 Jul 2020 19:03:48 -0400 Subject: [PATCH 1/2] Remove service worker --- gulpfile.babel.js | 82 ------- static/js-src/features-page.js | 47 ---- static/js-src/notifications.js | 230 ------------------- static/js-src/schedule-page.js | 22 -- static/js-src/service-worker-registration.js | 125 ---------- static/js-src/shared.js | 36 --- static/manifest.json | 35 --- templates/_base.html | 15 +- templates/_base_embed.html | 3 - templates/features.html | 2 +- templates/header.html | 1 - 11 files changed, 13 insertions(+), 585 deletions(-) delete mode 100755 static/js-src/service-worker-registration.js delete mode 100644 static/manifest.json diff --git a/gulpfile.babel.js b/gulpfile.babel.js index 3e4aa6d346f3..65137f54e56b 100755 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -4,7 +4,6 @@ const path = require('path'); const gulp = require('gulp'); const babel = require("gulp-babel"); const del = require('del'); -const swPrecache = require('sw-precache'); const uglifyEs = require('gulp-uglify-es'); const uglify = uglifyEs.default; const gulpLoadPlugins = require('gulp-load-plugins'); @@ -119,86 +118,6 @@ gulp.task('clean', () => { ], {dot: true}); }); -// Generate a service worker file that will provide offline functionality for -// local resources. -gulp.task('generate-service-worker', () => { - const staticDir = 'static'; - const distDir = path.join(staticDir, 'dist'); - const filepath = path.join(distDir, 'service-worker.js'); - - return swPrecache.write(filepath, { - cacheId: 'chromestatus', - verbose: true, - logger: $.util.log, - staticFileGlobs: [ - // Images - `${staticDir}/img/{browsers-logos.png,*.svg,crstatus_128.png,github-white.png}`, - // Scripts - `${staticDir}/js/**/!(*.es6).js`, // Don't include unminimized/untranspiled js. - ], - runtimeCaching: [{ // Server-side generated content - // The features page, which optionally has a trailing slash or a - // feature id. For example: - // - /features - // - /features/ - // - /features/ - // This overly-specific regex is required to avoid matching other - // static content (i.e. /static/css/features/features.css) - urlPattern: /\/features(\/(\w+)?)?$/, - handler: 'fastest', - options: { - cache: { - maxEntries: 10, - name: 'features-cache' - } - } - }, { - // The metrics pages (optionally with a trailing slash) - // - /metrics/css/animated - // - /metrics/css/timeline/animated - // - /metrics/css/popularity - // - /metrics/css/timeline/popularity - // - /metrics/feature/popularity - // - /metrics/feature/timeline/popularity - urlPattern: /\/metrics\/(css|feature)\/(timeline\/)?(animated|popularity)(\/)?$/, - handler: 'fastest', - options: { - cache: { - maxEntries: 10, - name: 'metrics-cache' - } - } - }, { - // The samples page (optionally with a trailing slash) - urlPattern: /\/samples(\/)?$/, - handler: 'fastest', - options: { - cache: { - maxEntries: 10, - name: 'samples-cache' - } - } - }, { - // For dynamic data (json), use "fastest" so liefi scenarios are fast. - // "fastest" also makes a network request to update the cached copy. - // The worst case is that the user with an active SW gets stale content - // and never refreshes the page. - // TODO: use sw-toolbox notifyOnCacheUpdate when it's ready - // https://github.com/GoogleChrome/sw-toolbox/pull/174/ - urlPattern: /\/data\//, - handler: 'fastest' - }, { - urlPattern: /\/features(_v\d+)?.json$/, - handler: 'fastest' - }, { - urlPattern: /\/samples.json$/, - handler: 'fastest' - }, { - urlPattern: /\/omaha_data$/, - handler: 'fastest' - }] - }); -}); // Build production files, the default task gulp.task('default', gulp.series( @@ -207,7 +126,6 @@ gulp.task('default', gulp.series( 'js', 'lint-fix', 'rollup', - 'generate-service-worker', )); // Build production files, the default task diff --git a/static/js-src/features-page.js b/static/js-src/features-page.js index 8463e2a709f3..d04b41da49cb 100644 --- a/static/js-src/features-page.js +++ b/static/js-src/features-page.js @@ -86,58 +86,11 @@ window.addEventListener('popstate', (e) => { featureListEl.addEventListener('app-ready', () => { document.body.classList.remove('loading'); - // Want "Caching is complete" toast to be slightly delayed after page load. - // To do that, wait to register SW until features have loaded. - registerServiceWorker(); - - // Lazy load Firebase messaging SDK after features list visible. - loadFirebaseSDKLibs().then(() => { - PushNotifications.init(); // init Firebase messaging. - - // If use already granted the notification permission, update state of the - // push icon for each feature the user is subscribed to. - if (PushNotifier.GRANTED_ACCESS) { - PushNotifications.getAllSubscribedFeatures().then((subscribedFeatures) => { - const iconEl = document.querySelector('#features-subscribe-button').firstElementChild; - if (subscribedFeatures.includes(PushNotifier.ALL_FEATURES_TOPIC_ID)) { - iconEl.icon = 'chromestatus:notifications'; - } else { - iconEl.icon = 'chromestatus:notifications-off'; - } - }); - } - }); - StarService.getStars().then((starredFeatureIds) => { featureListEl.starredFeatures = new Set(starredFeatureIds); }); }); -if (PushNotifier.SUPPORTS_NOTIFICATIONS) { - const subscribeButtonEl = document.querySelector('#features-subscribe-button'); - subscribeButtonEl.removeAttribute('hidden'); - - subscribeButtonEl.addEventListener('click', (e) => { - e.preventDefault(); - - if (window.Notification && Notification.permission === 'denied') { - alert('Notifications were previously denied. Please reset the browser permission.'); - return; - } - - PushNotifications.getAllSubscribedFeatures().then(subscribedFeatures => { - const iconEl = document.querySelector('#features-subscribe-button').firstElementChild; - if (subscribedFeatures.includes(PushNotifier.ALL_FEATURES_TOPIC_ID)) { - iconEl.icon = 'chromestatus:notifications-off'; - PushNotifications.unsubscribeFromFeature(); - } else { - iconEl.icon = 'chromestatus:notifications'; - PushNotifications.subscribeToFeature(); - } - }); - }); -} - legendEl.views = VIEWS; document.querySelector('.legend-button').addEventListener('click', (e) => { diff --git a/static/js-src/notifications.js b/static/js-src/notifications.js index ef8f0ca0768c..087d73b8390d 100644 --- a/static/js-src/notifications.js +++ b/static/js-src/notifications.js @@ -1,223 +1,6 @@ -/* global firebase */ - (function(exports) { 'use strict'; -let _libsLoaded = false; - -/** - * Lazy loads a script. - * @param {string} src URL of script. - * @return {!Promise} Resolves when the script has loaded. Rejects otherwise. - */ -function loadLib(src) { - return new Promise((resolve, reject) => { - const script = document.createElement('script'); - script.src = src; - script.onload = resolve; - script.onerror = reject; - document.head.appendChild(script); - }); -} - -/** - * Lazy load the Firebase Messaging (FCM) SDK. - */ -async function loadLibs() { - await loadLib('https://www.gstatic.com/firebasejs/4.1.3/firebase-app.js'); - await loadLib('https://www.gstatic.com/firebasejs/4.1.3/firebase-messaging.js'); - _libsLoaded = true; -} - -class PushNotifier { - init() { - if (!_libsLoaded) { - throw Error('Firebase SDK not loaded.'); - } - - firebase.initializeApp({ - apiKey: 'AIzaSyDMfRkOLG6OUTeEL_Z2ixEMDceyklm10UM', - authDomain: 'cr-status.firebaseapp.com', - databaseURL: 'https://cr-status.firebaseio.com', - projectId: 'cr-status', - storageBucket: 'cr-status.appspot.com', - messagingSenderId: '999517574127', - }); - - this.messaging = firebase.messaging(); - - this.messaging.onTokenRefresh(async () => { - let refreshedToken; - try { - // Get Instance ID token. Initially this makes a network call. Once - // retrieved, subsequent calls to getToken will return from the cache. - refreshedToken = await this.messaging.getToken(); - } catch (err) { - console.error('Unable to retrieve refreshed token ', err); - return; - } - - this._setTokenSentToServer(false); - await this.sendTokenToServer(refreshedToken); - }); - - this.messaging.onMessage(payload => { - const notification = new Notification( - payload.notification.title, payload.notification); - - notification.onerror = function(e) { - console.log(e); - }; - - notification.onclick = function() { - // window.open(payload.notification.click_action, '_blank'); - exports.focus(); - }; - }); - } - - static get SUPPORTS_NOTIFICATIONS() { - return Boolean(navigator.serviceWorker && exports.Notification); - } - - static get GRANTED_ACCESS() { - return window.Notification && Notification.permission === 'granted'; - } - - static get ALL_FEATURES_TOPIC_ID() { - return 'new-feature'; - } - - _isTokenSentToServer() { - return exports.localStorage.getItem('pushTokenSentToServer') === 1; - } - - _setTokenSentToServer(sent) { - exports.localStorage.setItem('pushTokenSentToServer', sent ? 1 : 0); - } - - /** - * Fetches information about the push token. - * @param {string=} token Push subscription token. - * @return {!Promise} JSON - */ - async getTokenInfo(token = null) { - token = token || await this.getToken(); - - try { - const resp = await fetch('/features/push/info', { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({subscriptionId: token}), - }); - // if (resp.status !== 200) { - // const text = await resp.text(); - // throw new Error(text); - // } - return await resp.json(); - } catch (err) { - console.error('Error sending notification to FCM.', err); - } - } - - async requestNotifications() { - try { - await this.messaging.requestPermission(); - } catch (err) { - console.error('Unable to get permission to notify.', err); - } - return Notification.permission === 'granted'; - } - - async getToken() { - let currentToken; - try { - // Get Instance ID token. Initially this makes a network call, once retrieved - // subsequent calls to getToken will return from cache. - currentToken = await this.messaging.getToken(); - } catch (err) { - console.log('An error occurred while retrieving token. ', err); - this._setTokenSentToServer(false); - return; - } - - if (currentToken) { - await this.sendTokenToServer(currentToken); - } else { - // console.log('No Instance ID token available. Request permission to generate one.'); - // Prompt user to enable notifications. - this._setTokenSentToServer(false); - const granted = await this.requestNotifications(); - if (granted) { - currentToken = await this.getToken(); - } - } - - return currentToken; - } - - async sendTokenToServer(token) { - if (!this._isTokenSentToServer()) { - await fetch('/features/push/new', { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({subscriptionId: token}), - }); - - this._setTokenSentToServer(true); - } - } - - async subscribeToFeature(featureId, remove = false) { - if (!PushNotifier.SUPPORTS_NOTIFICATIONS) { - return; - } else if (Notification.permission === 'denied') { - // eslint-disable-next-line no-alert - alert('Notifications were previously denied. ' + - 'Please reset the browser permission.'); - return; - } - - featureId = featureId || ''; - - const token = await this.getToken(); - - try { - const body = {subscriptionId: token}; - if (remove) { - body.remove = true; - } - await fetch(`/features/push/subscribe/${featureId}`, { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify(body), - }); - } catch (err) { - console.error('Error [un]subscribing to topic.', err); - } - - return true; - } - - async unsubscribeFromFeature(featureId) { - return this.subscribeToFeature(featureId, true); - } - - async getAllSubscribedFeatures() { - if (!PushNotifier.GRANTED_ACCESS) { - console.warn('getAllSubscribedFeatures(): notifications ' + - 'permission has not been granted yet.'); - return []; - } - - const token = await this.getToken(); - - return this.getTokenInfo(token).then(info => { - return info.rel ? Object.keys(info.rel.topics).map(id => String(id)) : []; - }); - } -} - class StarService { static getStars() { @@ -249,18 +32,5 @@ class StarService { } -exports.PushNotifier = PushNotifier; -exports.PushNotifications = new PushNotifier(); -exports.loadFirebaseSDKLibs = loadLibs; exports.StarService = StarService; - -// if (SUPPORTS_NOTIFICATIONS) { -// // navigator.serviceWorker.ready.then(reg => { -// // this.messaging.useServiceWorker(reg); // use site's existing sw instead of the one FB messaging registers. - -// // getToken().then(token => { -// // // console.log(token); -// // }); -// // }); -// } })(window); diff --git a/static/js-src/schedule-page.js b/static/js-src/schedule-page.js index fa3922b52fac..8a102719a7e0 100644 --- a/static/js-src/schedule-page.js +++ b/static/js-src/schedule-page.js @@ -26,10 +26,6 @@ async function init() { const scheduleEl = document.querySelector('chromedash-schedule'); scheduleEl.channels = CHANNELS; - // Show push notification icons if the browser supports the feature. - if (window.PushNotifier && PushNotifier.SUPPORTS_NOTIFICATIONS) { - initNotifications(features); - } StarService.getStars().then((starredFeatureIds) => { scheduleEl.starredFeatures = new Set(starredFeatureIds); }); @@ -57,24 +53,6 @@ function mapFeaturesToComponents(features) { return featuresMappedToComponents; } -async function initNotifications(allFeatures) { - await loadFirebaseSDKLibs(); // Lazy load Firebase messaging SDK. - - PushNotifications.init(); // init Firebase messaging. - - // If use already granted the notification permission, update state of the - // push icon for each feature the user is subscribed to. - const subscribedFeatures = await PushNotifications.getAllSubscribedFeatures(); - allFeatures.forEach((feature) => { - if (subscribedFeatures.includes(String(feature.id))) { - // f.receivePush = true; - const iconEl = document.querySelector(`[data-feature-id="${feature.id}"] .pushicon`); - if (iconEl) { - iconEl.icon = 'chromestatus:notifications'; - } - } - }); -} /** * @param {!Array} features diff --git a/static/js-src/service-worker-registration.js b/static/js-src/service-worker-registration.js deleted file mode 100755 index 6a4b0ab7d243..000000000000 --- a/static/js-src/service-worker-registration.js +++ /dev/null @@ -1,125 +0,0 @@ -/* eslint max-nested-callbacks: ["error", 7] */ -/* eslint-env browser */ - -(function(exports) { -'use strict'; - -const Toast = document.querySelector('chromedash-toast'); - -let toastReady = new Promise(function(resolve, reject) { - // If the page is using async imports, wait for them to load so toast custom - // element is upgraded and has its methods. - if (window.asyncImportsLoadPromise) { - return window.asyncImportsLoadPromise.then(resolve, reject); - } - resolve(); -}); - -/** - * Returns a promise for the total size of assets precached by service worker. - * @return {Promise} Promises that fulfills with the total size. - */ -function getPrecachedAssetTotalSize() { - // Note that any opaque (i.e. cross-domain, without CORS) responses in the - // cache will return a size of 0. - return caches.keys().then(cacheNames => { - let total = 0; - - return Promise.all( - cacheNames.map(cacheName => { - // Filter for assets cached by precache. - if (!cacheName.includes('sw-precache')) { - // eslint-disable-next-line array-callback-return - return; - } - - return caches.open(cacheName).then(cache => { - return cache.keys().then(keys => { - return Promise.all( - keys.map(key => { - return cache.match(key) - .then(response => response.arrayBuffer()) - .then(function(buffer) { - total += buffer.byteLength; - }); - }) - ); - }); - }); - }) - ).then(function() { - return total; - }).catch(function() { - // noop! - }); - }); -} - -/** - * Registers a service worker. - */ -function registerServiceWorker() { - if ('serviceWorker' in navigator) { - navigator.serviceWorker.register('/service-worker.js').then(reg => { - reg.onupdatefound = function() { - // The updatefound event implies that registration.installing is set. - const installingWorker = reg.installing; - installingWorker.onstatechange = function() { - switch (installingWorker.state) { - case 'installed': - if (Toast && !navigator.serviceWorker.controller) { - toastReady.then(getPrecachedAssetTotalSize().then(bytes => { - let kb = Math.round(bytes / 1000); - console.info('[ServiceWorker] precached', kb, 'KB'); - - // Send precached bytes to GA. - let metric = new Metric('sw_precache'); - metric.sendToAnalytics( - 'service worker', 'precache size', bytes); - - Toast.showMessage( - `This site is cached (${kb}KB). Ready to use offline!`); - })); - } - break; - case 'redundant': - throw Error('The installing service worker became redundant.'); - default: - break; - } - }; - }; - }).catch(function(e) { - console.error('Error during service worker registration:', e); - }); - } -} - -if (!window.asyncImportsLoadPromise) { - registerServiceWorker(); -} - -// 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(e) { - if (e.target.state === 'redundant') { - const tapHandler = function() { - window.location.reload(); - }; - - if (Toast) { - toastReady.then(function() { - Toast.showMessage('A new version of this app is available.', - 'Refresh', tapHandler, -1); - }); - } else { - tapHandler(); // Force reload if toast never loads. - } - } - }; -} - -exports.registerServiceWorker = registerServiceWorker; -})(window); diff --git a/static/js-src/shared.js b/static/js-src/shared.js index fda8f4ca4515..5c3e65d664ea 100644 --- a/static/js-src/shared.js +++ b/static/js-src/shared.js @@ -16,42 +16,6 @@ 'use strict'; -(function(exports) { -let addToHomescreenEvent; - -const a2hsButton = document.querySelector('#a2hs-button'); - -exports.addEventListener('beforeinstallprompt', e => { - e.preventDefault(); - addToHomescreenEvent = e; - a2hsButton.classList.add('available'); -}); - -if (a2hsButton) { - a2hsButton.addEventListener('click', e => { - e.preventDefault(); - if (addToHomescreenEvent && !a2hsButton.classList.contains('disabled')) { - addToHomescreenEvent.prompt().then(() => { - // TODO: handle user pressing "cancel" button on prompt. - // https://bugs.chromium.org/p/chromium/issues/detail?id=805744 - addToHomescreenEvent.userChoice.then(choice => { - console.log(choice); - if (choice.outcome === 'accepted') { - a2hsButton.classList.add('disabled'); - a2hsButton.setAttribute('title', 'App already installed.'); - addToHomescreenEvent = null; - } else { - a2hsButton.setAttribute( - 'title', 'Refresh the page and click again to install app.'); - } - a2hsButton.classList.add('disabled'); // Can't re-prompt, so disable button - }); - }); - } - }); -} -})(window); - // Google Analytics /* eslint-disable */ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ diff --git a/static/manifest.json b/static/manifest.json deleted file mode 100644 index ab23e12c52bf..000000000000 --- a/static/manifest.json +++ /dev/null @@ -1,35 +0,0 @@ - -{ - "name": "Chrome Platform Status", - "short_name": "Chrome status", - "start_url": "/features", - "display": "standalone", - "theme_color": "#366597", - "background_color": "#366597", - "icons": [{ - "src": "./img/crstatus_72.png", - "sizes": "72x72", - "type": "image/png" - }, { - "src": "./img/crstatus_96.png", - "sizes": "96x96", - "type": "image/png" - }, { - "src": "./img/crstatus_128.png", - "sizes": "128x128", - "type": "image/png" - }, { - "src": "./img/crstatus_144.png", - "sizes": "144x144", - "type": "image/png" - }, { - "src": "./img/crstatus_192.png", - "sizes": "192x192", - "type": "image/png" - }, { - "src": "./img/crstatus_512.png", - "sizes": "512x512", - "type": "image/png" - }], - "gcm_sender_id": "103953800507" -} diff --git a/templates/_base.html b/templates/_base.html index 13e4e9e1abe0..e0e65c3e95eb 100644 --- a/templates/_base.html +++ b/templates/_base.html @@ -22,8 +22,6 @@ {% block page_title %}{% endblock %}{{ APP_TITLE }} - - @@ -117,7 +115,18 @@ diff --git a/templates/_base_embed.html b/templates/_base_embed.html index 2ee286725b35..3f06dfc8dc54 100644 --- a/templates/_base_embed.html +++ b/templates/_base_embed.html @@ -22,8 +22,6 @@ {% block page_title %}{% endblock %}{{ APP_TITLE }} - - @@ -93,7 +91,6 @@ diff --git a/templates/features.html b/templates/features.html index a8d21d7203b7..b9d3bf0e89ed 100644 --- a/templates/features.html +++ b/templates/features.html @@ -29,7 +29,7 @@

Features:

- From 461f961286c8adb8376e89e2bdf6ef337fd781c1 Mon Sep 17 00:00:00 2001 From: Jason Robbins Date: Wed, 29 Jul 2020 14:45:06 -0400 Subject: [PATCH 2/2] Remove remaining CSS related to a2hs button --- static/sass/main.scss | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/static/sass/main.scss b/static/sass/main.scss index c4ada290fdc5..d77182a8c038 100644 --- a/static/sass/main.scss +++ b/static/sass/main.scss @@ -412,19 +412,6 @@ footer { margin: 0; } - #a2hs-button.available { - display: flex !important; - justify-content: center; - align-items: center; - margin-top: $content-padding / 2; - - &::after { - content: 'Install app'; - text-transform: uppercase; - font-weight: 600; - margin-left: $content-padding / 2; - } - } } // When banner doesn't block navigation buttons. @@ -434,28 +421,4 @@ footer { } } -#a2hs-button { - display: none; - margin-left: $content-padding / 2; - - &.available { - display: inline-block; - } - - &.disabled { - opacity: 0.5; - } - - img { - height: 24px; - width: 24px; - } -} - -@media all and (display-mode: standalone) { - #a2hs-button { - display: none; - } -} - @import 'fouc';