UNPKG

10.4 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', {
4 value: true
5});
6exports.isAvailable = isAvailable;
7exports.isSubscribed = isSubscribed;
8exports.requestPush = requestPush;
9exports.updateSubscriptionState = updateSubscriptionState;
10exports.getStoredSubscription = getStoredSubscription;
11exports.getPublicKey = getPublicKey;
12var isSecure = window.location.protocol === 'https:' || window.location.hostname === 'localhost' || window.location.hostname.indexOf('127.') === 0;
13
14var CACHE_NAME = '$$$offline-plugin:push';
15
16/*
17
18Push API availability:
19 * available
20 * not-available
21 * already-blocked
22
23Subscription states:
24 * <all above> (except `available`)
25 * subscribed -- Device/browser already has subscription
26 * able-to-subscribe -- Device/browser isn't subscribed, but push API available
27 * subscribing -- User just granted permission in this session
28
29Push Request:
30 * <all above> (except `available`)
31 * (maybe use `blocked` everywhere instead of denied/already-blocked?) denied -- Notifications permission clearly denied by the user in this session
32
33Background Subscription:
34 * subscribed -- Subscription was successful
35 * error -- subscription wasn't successful (with possible error report)
36
37*/
38
39// If I have no UID/current push data stored and have subscription --
40// unsubscribe current subscription if permission is allowing that and
41// create new one. If not allowed, just leave subscription there.
42// (though I don't think it's possible to have revoked permission and
43// subscription in place)
44
45var _status = undefined;
46var _onStatusChange = undefined;
47var _pushPublicKey = undefined;
48
49var push = Object.defineProperties({
50
51 init: function init() {
52 var _ref = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
53
54 var onStatusChange = _ref.onStatusChange;
55
56 _onStatusChange = onStatusChange;
57
58 return push.status = isSubscribed();
59 }
60}, {
61 status: {
62 get: function get() {
63 return _status;
64 },
65 set: function set(val) {
66 _status = val;
67
68 if (typeof _onStatusChange === 'function') {
69 val.then(_onStatusChange);
70 }
71 },
72 configurable: true,
73 enumerable: true
74 }
75});
76
77exports['default'] = push;
78
79function isAvailable() {
80 if (!isSecure) {
81 return Promise.resolve({ status: 'not-available' });
82 }
83
84 if (!navigator.serviceWorker) {
85 return Promise.resolve({ status: 'not-available' });
86 }
87
88 if (!('PushManager' in window)) {
89 return Promise.resolve({ status: 'not-available' });
90 }
91
92 if (!window.Notification || !Notification.permission) {
93 return Promise.resolve({ status: 'not-available' });
94 }
95
96 if (Notification.permission === 'denied') {
97 return Promise.resolve({ status: 'already-blocked' });
98 }
99
100 return Promise.resolve({ status: 'available' });
101}
102
103function isSubscribed() {
104 var params = arguments.length <= 0 || arguments[0] === undefined ? { resubscribe: true } : arguments[0];
105
106 return isAvailable().then(function (result) {
107 if (result.status !== 'available') return result;
108
109 return navigator.serviceWorker.ready.then(function (reg) {
110 if (!reg.pushManager) {
111 return Promise.resolve({ status: 'not-available' });
112 }
113
114 return reg.pushManager.getSubscription()['catch'](function () {}).then(function (subscription) {
115 if (!subscription) {
116 if (params.resubscribe && Notification.permission === 'granted') {
117 // Subscribe asynchronously
118 performBackgroundSubscription();
119 return Promise.resolve({ status: 'subscribing' });
120 }
121
122 return Promise.resolve({ status: 'able-to-subscribe' });
123 }
124
125 var subscriptionKey = subscription.options && new Uint8Array(subscription.options.applicationServerKey);
126
127 if (!subscription.options || getPublicKey().toString() === subscriptionKey.toString()) {
128 // Let server know subscription is still valid
129 handleSubscription({
130 subscription: subscription
131 });
132
133 return Promise.resolve({ status: 'subscribed' });
134 }
135
136 var unsubscribe = subscription.unsubscribe();
137
138 if (params.resubscribe && Notification.permission === 'granted') {
139 return unsubscribe.then(function () {
140 return requestPushInternal({
141 replace: subscription,
142 userVisible: false
143 });
144 });
145 }
146
147 return Promise.resolve({ status: 'able-to-subscribe' });
148 });
149 });
150 });
151}
152
153function requestPush(params) {
154 return push.status = _requestPush(params);
155}
156
157function _requestPush() {
158 var params = arguments.length <= 0 || arguments[0] === undefined ? {
159 replace: void 0,
160 userVisible: true
161 } : arguments[0];
162
163 return isAvailable().then(function (result) {
164 if (result.status !== 'available') return result;
165
166 if (Notification.permission === 'granted') {
167 // Subscribe asynchronously
168 performBackgroundSubscription(params);
169 return Promise.resolve({ status: 'subscribing' });
170 }
171
172 // 'denied' handled by isAvailabe() and 'granted' just handled
173 // it's 'default' now
174 if (!params.userVisible) {
175 return Promise.resolve({ status: 'able-to-subscribe' });
176 }
177
178 return requestPermission().then(function (permission) {
179 if (permission === 'granted') {
180 // Subscribe asynchronously
181 performBackgroundSubscription(params);
182 return Promise.resolve({ status: 'subscribing' });
183 }
184
185 if (permission === 'denied') {
186 return Promise.resolve({ status: 'denied' });
187 }
188
189 return Promise.resolve({ status: 'able-to-subscribe' });
190 });
191 });
192}
193
194function requestPermission() {
195 if (!window.Notification || !window.Notification.requestPermission) {
196 return Promise.resolve('default');
197 }
198
199 if (Notification.permission === 'granted' || Notification.permission === 'denied') {
200 return Promise.resolve(Notification.permission);
201 }
202
203 return new Promise(function (resolve) {
204 return Notification.requestPermission(resolve);
205 });
206 // return Notification.requestPermission().catch(() => {});
207}
208
209function performBackgroundSubscription() {
210 var params = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
211
212 return navigator.serviceWorker.ready.then(function (registration) {
213 return registration.pushManager.subscribe({
214 userVisibleOnly: true,
215 applicationServerKey: getPublicKey()
216 });
217 }).then(function (subscription) {
218 if (!subscription) {
219 throw void 0;
220 }
221
222 handleSubscription({
223 replace: params.replace,
224 subscription: subscription
225 });
226
227 return Promise.resolve({ status: 'subscribed' });
228 }).then(function (result) {
229 // Update push status once background subscription finished
230 push.status = Promise.resolve(result);
231 return result;
232 })['catch'](function (error) {
233 // At this moment push.status most likely is 'subscribing',
234 // check subscription status again
235 push.status = isSubscribed({ resubscribe: false });
236 return Promise.resolve({ status: 'error', error: error });
237 });
238}
239
240function handleSubscription(_ref2) {
241 var subscription = _ref2.subscription;
242
243 getStoredSubscription().then(function (storedData) {
244 subscription = subscription.toJSON();
245
246 var id = undefined;
247 var active = true;
248
249 if (storedData && storedData.body && storedData.body.data) {
250 id = storedData.body.data.id || void 0;
251
252 if (typeof storedData.body.data.active === 'boolean') {
253 active = storedData.body.data.active;
254 }
255 }
256
257 var body = {
258 action: 'add',
259 data: {
260 id: id,
261 active: active,
262 subscription: subscription
263 }
264 };
265
266 var data = {
267 url: '/push-subscribe',
268 body: body,
269 saved: false
270 };
271
272 syncSubscription(data);
273 });
274}
275
276function syncSubscription(data) {
277 storeSubscription(data).then(function () {
278 return navigator.serviceWorker.ready.then(function (reg) {
279 if (reg.sync) {
280 console.log('via background sync');
281 reg.sync.register('send-subscription');
282 } else {
283 sendSubscription(data);
284 }
285 });
286 })['catch'](function () {
287 sendSubscription(data);
288 });
289}
290
291function sendSubscription(data) {
292 return fetch(data.url, {
293 headers: {
294 'Content-Type': 'application/json'
295 },
296 method: 'POST',
297 body: JSON.stringify(data.body)
298 }).then(function (response) {
299 if (!response.ok) {
300 return;
301 }
302
303 return response.text().then(function (uuid) {
304 data.saved = true;
305 data.body.data.id = uuid;
306
307 return storeSubscription(data)['catch'](function () {});
308 });
309 });
310}
311
312function updateSubscriptionState(_ref3) {
313 var active = _ref3.active;
314
315 getStoredSubscription().then(function (storedData) {
316 var id = undefined;
317 var subscription = undefined;
318
319 if (storedData && storedData.body && storedData.body.data) {
320 id = storedData.body.data.id || void 0;
321 subscription = storedData.body.data.subscription || void 0;
322 }
323
324 // Cannot perform an update if both id and subscription itself aren't available
325 if (!id || !subscription) return;
326
327 var body = {
328 action: 'update',
329 data: {
330 id: id,
331 active: active,
332 subscription: subscription
333 }
334 };
335
336 var data = {
337 url: '/push-subscribe',
338 body: body,
339 saved: false
340 };
341
342 syncSubscription(data);
343 });
344}
345
346function getStoredSubscription() {
347 return caches.open(CACHE_NAME).then(function (cache) {
348 return cache.match('/__push_subscription__');
349 }).then(function (data) {
350 if (data) {
351 return JSON.parse(data);
352 }
353 })['catch'](function () {});
354}
355
356function storeSubscription(data) {
357 return caches.open(CACHE_NAME).then(function (cache) {
358 var response = new Response(JSON.stringify(data));
359
360 return cache.put('/__push_subscription__', response);
361 });
362}
363
364function getPublicKey() {
365 if (!_pushPublicKey) {
366 _pushPublicKey = urlBase64ToUint8Array(PUSH_PUBLIC_KEY);
367 }
368
369 return _pushPublicKey;
370}
371
372function urlBase64ToUint8Array(base64String) {
373 var padding = '='.repeat((4 - base64String.length % 4) % 4);
374 var base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/');
375
376 var rawData = window.atob(base64);
377 var outputArray = new Uint8Array(rawData.length);
378
379 for (var i = 0; i < rawData.length; ++i) {
380 outputArray[i] = rawData.charCodeAt(i);
381 }
382
383 return outputArray;
384}
\No newline at end of file