-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathswSrc.js
215 lines (181 loc) · 7.78 KB
/
swSrc.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
/* global importScripts, workbox, clients */
/* eslint-disable no-restricted-globals */
/* eslint-disable quotes */
/* eslint-disable comma-dangle */
/* eslint-disable quote-props */
importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.6.2/workbox-sw.js');
const APP_SHELL = '/app-shell';
const HASHED_CACHE = 'hashedCache';
// Precache all the files needed by the App Shell, as defined in workbox-config.js
// If any of these files are updated, run `npm run update-sw` to update this file automatically.
workbox.precaching.precacheAndRoute([]);
// Cache the App Shell route. We try to get latest from the network, fall back to cache for offline.
//
// Workbox's `registerNavigationRoute` helper will always try the cache first, and this causes Meteor's
// hot code pushing to enter an infinite loop (Meteor requests a reload, the service worker returns the old
// cached version, so Meteor requests a reload, and so on). So, rather than using the helper, we'll register
// a new `NavigationRoute` ourselves, so that we can supply a handler.
//
// We'd like to use the `NetworkFirst` strategy, but the normal version will always try to fetch and cache
// the route that's being accessed. We override the class to force the request to be our app-shell url.
class NetworkFirstToFixedRoute extends workbox.strategies.NetworkFirst {
constructor(url, options = {}) {
super(options);
this.url = url;
}
async handle({ event }) {
console.log(event);
return this.makeRequest({
event,
request: this.url,
});
}
}
// Finally, we can register a navigation route with a the custom handler that always fetches from `APP_SHELL`;
workbox.routing.registerRoute(new workbox.routing.NavigationRoute(new NetworkFirstToFixedRoute(APP_SHELL)));
workbox.clientsClaim();
function removeHash(element) {
if (typeof element === 'string') return element.split('?hash=')[0];
return element;
}
function hasSameHash(firstUrl, secondUrl) {
if (typeof firstUrl === 'string' && typeof secondUrl === 'string') {
return /\?hash=(.*)/.exec(firstUrl)[1] === /\?hash=(.*)/.exec(secondUrl)[1];
}
return false;
}
// Use our own cache for all hashed assets (Meteor generates the hashes for us)
// Old versions of a given URL are replaced when a version with a different hash is
// requested, otherwise we always return the cached version.
//
// We also need to make sure that we eventually delete files that are no longer used
// at all. We assume that once a file hasn't been used for a few weeks it's no longer
// useful. We use Workbox's CacheExpiration module to expire all the old files.
const expirationManager = new workbox.expiration.CacheExpiration(
HASHED_CACHE,
{
maxAgeSeconds: 4 * 7 * 24 * 60 * 60
}
);
workbox.routing.registerRoute(/\?hash=.*/, ({ url, event }) => {
caches.open(HASHED_CACHE).then((cache) => {
const req = event.request.clone();
return cache.match(req).then((cached) => {
// Return the cached version if the hash is the same (updating the timestamp in the expiration manager)
if (cached && hasSameHash(url.toString(), cached.url.toString())) {
return expirationManager.updateTimestamp(url.toString()).then(() => cached);
}
// Try to fetch it....
return fetch(req).then((response) => {
const clonedResponse = response.clone();
if (!clonedResponse || clonedResponse.status !== 200 || clonedResponse.type !== 'basic') {
return response;
}
// Remove all other versions of this file frome the cache
const re = new RegExp(removeHash(url.toString()));
caches.open(HASHED_CACHE).then(hashCache => hashCache.keys().then(keys => keys.forEach((asset) => {
if (re.test(removeHash(asset.url.toString()))) {
hashCache.delete(asset);
}
})));
// Cache this version
caches.open(HASHED_CACHE)
.then(hashCache => hashCache.put(event.request, clonedResponse))
.then(() => expirationManager.updateTimestamp(url.toString()));
// Return it
return response;
});
});
})
.then(() => { expirationManager.expireEntries(); })
.catch(e => console.log(`Service worker threw ${e}`));
});
// Push event listener aux function:
const showNotification = (evt) => {
if (!(self.Notification && self.Notification.permission === 'granted')) {
return;
}
const title = 'Push notification demo';
const options = {
body: evt.data && evt.data.text() ? evt.data.text() : 'Push message no payload',
tag: 'demo',
icon: '/images/apple-touch-icon.png',
badge: '/images/apple-touch-icon.png',
// Custom actions buttons
actions: [
{ action: 'yes', title: 'I ♥ this app!' },
{ action: 'no', title: 'I don\'t like this app' },
],
};
evt.waitUntil(
self.registration.showNotification(title, options),
);
};
// When to Show Notifications:
// If the user is already using your application there is no need to display a
// notification. You can manage this logic on the server, but it is easier to
// do it in the push handler inside your service worker:
// the 'clients' global in the service worker lists all of the active push
// clients on this machine. If there are no clients active, the user must be
// in another app. We should show a notification in this case. If there are
// active clients it means that the user has your site open in one or more
// windows. The best practice is to relay the message to each of those windows.
// Source: https://developers.google.com/web/ilt/pwa/introduction-to-push-notifications
// Source: https://developers.google.com/web/fundamentals/codelabs/push-notifications/
self.addEventListener('push', (evt) => {
console.log('[Service Worker] Push Received.');
console.log(`[Service Worker] Push had this data: "${evt && evt.data}"`);
// Comment out the following line in case you only want to display
// notifications when the app isn't open
showNotification(evt);
clients.matchAll()
.then((client) => {
if (client.length === 0) {
// Un-comment the following line in case you only want to display
// notifications when the app isn't open
// showNotification(evt);
} else {
// Send a message to the page to update the UI
console.log('Application is already open!');
}
});
});
// The code below looks for the first window with 'visibilityState' set to
// 'visible'. If one is found it navigates that client to the correct URL and
// focuses the window. If a window that suits our needs is not found, it
// opens a new window.
// Source: https://developers.google.com/web/fundamentals/codelabs/push-notifications/
// Source: https://developers.google.com/web/ilt/pwa/introduction-to-push-notifications
self.addEventListener('notificationclick', (evt) => {
console.log('[Service Worker] Notification click Received.');
const appUrl = new URL('/', location).href;
// Listen to custom action buttons in push notification
if (evt.action === 'yes') {
console.log('I ♥ this app!');
} else if (evt.action === 'no') {
console.log('I don\'t like this app');
}
evt.waitUntil(
clients.matchAll()
.then((clientsList) => {
const client = clientsList.find(c => (
c.visibilityState === 'visible'
));
if (client !== undefined) {
client.navigate(appUrl);
client.focus();
} else {
// There are no visible windows. Open one.
clients.openWindow(appUrl);
}
})
,
);
// Close all notifications (thisincludes any other notifications from the
// same origin)
// Source: https://developers.google.com/web/ilt/pwa/introduction-to-push-notifications
self.registration.getNotifications()
.then((notifications) => {
notifications.forEach((notification) => { notification.close(); });
});
});