1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | import { isPlatformBrowser } from '@angular/common';
|
8 | import * as i0 from '@angular/core';
|
9 | import { Injectable, InjectionToken, NgZone, ApplicationRef, PLATFORM_ID, APP_INITIALIZER, Injector, NgModule } from '@angular/core';
|
10 | import { defer, throwError, fromEvent, of, concat, Subject, NEVER, merge } from 'rxjs';
|
11 | import { map, filter, switchMap, publish, take, tap, delay } from 'rxjs/operators';
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 | const ERR_SW_NOT_SUPPORTED = 'Service workers are disabled or not supported by this browser';
|
21 | function errorObservable(message) {
|
22 | return defer(() => throwError(new Error(message)));
|
23 | }
|
24 |
|
25 |
|
26 |
|
27 | class NgswCommChannel {
|
28 | constructor(serviceWorker) {
|
29 | this.serviceWorker = serviceWorker;
|
30 | if (!serviceWorker) {
|
31 | this.worker = this.events = this.registration = errorObservable(ERR_SW_NOT_SUPPORTED);
|
32 | }
|
33 | else {
|
34 | const controllerChangeEvents = fromEvent(serviceWorker, 'controllerchange');
|
35 | const controllerChanges = controllerChangeEvents.pipe(map(() => serviceWorker.controller));
|
36 | const currentController = defer(() => of(serviceWorker.controller));
|
37 | const controllerWithChanges = concat(currentController, controllerChanges);
|
38 | this.worker = controllerWithChanges.pipe(filter((c) => !!c));
|
39 | this.registration = (this.worker.pipe(switchMap(() => serviceWorker.getRegistration())));
|
40 | const rawEvents = fromEvent(serviceWorker, 'message');
|
41 | const rawEventPayload = rawEvents.pipe(map(event => event.data));
|
42 | const eventsUnconnected = rawEventPayload.pipe(filter(event => event && event.type));
|
43 | const events = eventsUnconnected.pipe(publish());
|
44 | events.connect();
|
45 | this.events = events;
|
46 | }
|
47 | }
|
48 | postMessage(action, payload) {
|
49 | return this.worker
|
50 | .pipe(take(1), tap((sw) => {
|
51 | sw.postMessage(Object.assign({ action }, payload));
|
52 | }))
|
53 | .toPromise()
|
54 | .then(() => undefined);
|
55 | }
|
56 | postMessageWithOperation(type, payload, operationNonce) {
|
57 | const waitForOperationCompleted = this.waitForOperationCompleted(operationNonce);
|
58 | const postMessage = this.postMessage(type, payload);
|
59 | return Promise.all([postMessage, waitForOperationCompleted]).then(([, result]) => result);
|
60 | }
|
61 | generateNonce() {
|
62 | return Math.round(Math.random() * 10000000);
|
63 | }
|
64 | eventsOfType(type) {
|
65 | let filterFn;
|
66 | if (typeof type === 'string') {
|
67 | filterFn = (event) => event.type === type;
|
68 | }
|
69 | else {
|
70 | filterFn = (event) => type.includes(event.type);
|
71 | }
|
72 | return this.events.pipe(filter(filterFn));
|
73 | }
|
74 | nextEventOfType(type) {
|
75 | return this.eventsOfType(type).pipe(take(1));
|
76 | }
|
77 | waitForOperationCompleted(nonce) {
|
78 | return this.eventsOfType('OPERATION_COMPLETED')
|
79 | .pipe(filter(event => event.nonce === nonce), take(1), map(event => {
|
80 | if (event.result !== undefined) {
|
81 | return event.result;
|
82 | }
|
83 | throw new Error(event.error);
|
84 | }))
|
85 | .toPromise();
|
86 | }
|
87 | get isEnabled() {
|
88 | return !!this.serviceWorker;
|
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 | class SwPush {
|
179 | constructor(sw) {
|
180 | this.sw = sw;
|
181 | this.subscriptionChanges = new Subject();
|
182 | if (!sw.isEnabled) {
|
183 | this.messages = NEVER;
|
184 | this.notificationClicks = NEVER;
|
185 | this.subscription = NEVER;
|
186 | return;
|
187 | }
|
188 | this.messages = this.sw.eventsOfType('PUSH').pipe(map(message => message.data));
|
189 | this.notificationClicks =
|
190 | this.sw.eventsOfType('NOTIFICATION_CLICK').pipe(map((message) => message.data));
|
191 | this.pushManager = this.sw.registration.pipe(map(registration => registration.pushManager));
|
192 | const workerDrivenSubscriptions = this.pushManager.pipe(switchMap(pm => pm.getSubscription()));
|
193 | this.subscription = merge(workerDrivenSubscriptions, this.subscriptionChanges);
|
194 | }
|
195 | |
196 |
|
197 |
|
198 |
|
199 | get isEnabled() {
|
200 | return this.sw.isEnabled;
|
201 | }
|
202 | |
203 |
|
204 |
|
205 |
|
206 |
|
207 |
|
208 |
|
209 | requestSubscription(options) {
|
210 | if (!this.sw.isEnabled) {
|
211 | return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED));
|
212 | }
|
213 | const pushOptions = { userVisibleOnly: true };
|
214 | let key = this.decodeBase64(options.serverPublicKey.replace(/_/g, '/').replace(/-/g, '+'));
|
215 | let applicationServerKey = new Uint8Array(new ArrayBuffer(key.length));
|
216 | for (let i = 0; i < key.length; i++) {
|
217 | applicationServerKey[i] = key.charCodeAt(i);
|
218 | }
|
219 | pushOptions.applicationServerKey = applicationServerKey;
|
220 | return this.pushManager.pipe(switchMap(pm => pm.subscribe(pushOptions)), take(1))
|
221 | .toPromise()
|
222 | .then(sub => {
|
223 | this.subscriptionChanges.next(sub);
|
224 | return sub;
|
225 | });
|
226 | }
|
227 | |
228 |
|
229 |
|
230 |
|
231 |
|
232 |
|
233 | unsubscribe() {
|
234 | if (!this.sw.isEnabled) {
|
235 | return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED));
|
236 | }
|
237 | const doUnsubscribe = (sub) => {
|
238 | if (sub === null) {
|
239 | throw new Error('Not subscribed to push notifications.');
|
240 | }
|
241 | return sub.unsubscribe().then(success => {
|
242 | if (!success) {
|
243 | throw new Error('Unsubscribe failed!');
|
244 | }
|
245 | this.subscriptionChanges.next(null);
|
246 | });
|
247 | };
|
248 | return this.subscription.pipe(take(1), switchMap(doUnsubscribe)).toPromise();
|
249 | }
|
250 | decodeBase64(input) {
|
251 | return atob(input);
|
252 | }
|
253 | }
|
254 | SwPush.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.1.2", ngImport: i0, type: SwPush, deps: [{ token: NgswCommChannel }], target: i0.ɵɵFactoryTarget.Injectable });
|
255 | SwPush.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.1.2", ngImport: i0, type: SwPush });
|
256 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.1.2", ngImport: i0, type: SwPush, decorators: [{
|
257 | type: Injectable
|
258 | }], ctorParameters: function () { return [{ type: NgswCommChannel }]; } });
|
259 |
|
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 |
|
271 |
|
272 |
|
273 |
|
274 |
|
275 | class SwUpdate {
|
276 | constructor(sw) {
|
277 | this.sw = sw;
|
278 | if (!sw.isEnabled) {
|
279 | this.versionUpdates = NEVER;
|
280 | this.available = NEVER;
|
281 | this.activated = NEVER;
|
282 | this.unrecoverable = NEVER;
|
283 | return;
|
284 | }
|
285 | this.versionUpdates = this.sw.eventsOfType([
|
286 | 'VERSION_DETECTED',
|
287 | 'VERSION_INSTALLATION_FAILED',
|
288 | 'VERSION_READY',
|
289 | 'NO_NEW_VERSION_DETECTED',
|
290 | ]);
|
291 | this.available = this.versionUpdates.pipe(filter((evt) => evt.type === 'VERSION_READY'), map(evt => ({
|
292 | type: 'UPDATE_AVAILABLE',
|
293 | current: evt.currentVersion,
|
294 | available: evt.latestVersion,
|
295 | })));
|
296 | this.activated = this.sw.eventsOfType('UPDATE_ACTIVATED');
|
297 | this.unrecoverable = this.sw.eventsOfType('UNRECOVERABLE_STATE');
|
298 | }
|
299 | |
300 |
|
301 |
|
302 |
|
303 | get isEnabled() {
|
304 | return this.sw.isEnabled;
|
305 | }
|
306 | |
307 |
|
308 |
|
309 |
|
310 |
|
311 |
|
312 |
|
313 |
|
314 |
|
315 | checkForUpdate() {
|
316 | if (!this.sw.isEnabled) {
|
317 | return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED));
|
318 | }
|
319 | const nonce = this.sw.generateNonce();
|
320 | return this.sw.postMessageWithOperation('CHECK_FOR_UPDATES', { nonce }, nonce);
|
321 | }
|
322 | |
323 |
|
324 |
|
325 |
|
326 |
|
327 |
|
328 |
|
329 |
|
330 |
|
331 |
|
332 | activateUpdate() {
|
333 | if (!this.sw.isEnabled) {
|
334 | return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED));
|
335 | }
|
336 | const nonce = this.sw.generateNonce();
|
337 | return this.sw.postMessageWithOperation('ACTIVATE_UPDATE', { nonce }, nonce);
|
338 | }
|
339 | }
|
340 | SwUpdate.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.1.2", ngImport: i0, type: SwUpdate, deps: [{ token: NgswCommChannel }], target: i0.ɵɵFactoryTarget.Injectable });
|
341 | SwUpdate.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.1.2", ngImport: i0, type: SwUpdate });
|
342 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.1.2", ngImport: i0, type: SwUpdate, decorators: [{
|
343 | type: Injectable
|
344 | }], ctorParameters: function () { return [{ type: NgswCommChannel }]; } });
|
345 |
|
346 |
|
347 |
|
348 |
|
349 |
|
350 |
|
351 |
|
352 |
|
353 |
|
354 |
|
355 |
|
356 |
|
357 |
|
358 |
|
359 |
|
360 |
|
361 |
|
362 |
|
363 |
|
364 |
|
365 | class SwRegistrationOptions {
|
366 | }
|
367 | const SCRIPT = new InjectionToken('NGSW_REGISTER_SCRIPT');
|
368 | function ngswAppInitializer(injector, script, options, platformId) {
|
369 | const initializer = () => {
|
370 | if (!(isPlatformBrowser(platformId) && ('serviceWorker' in navigator) &&
|
371 | options.enabled !== false)) {
|
372 | return;
|
373 | }
|
374 |
|
375 |
|
376 |
|
377 | navigator.serviceWorker.addEventListener('controllerchange', () => {
|
378 | if (navigator.serviceWorker.controller !== null) {
|
379 | navigator.serviceWorker.controller.postMessage({ action: 'INITIALIZE' });
|
380 | }
|
381 | });
|
382 | let readyToRegister$;
|
383 | if (typeof options.registrationStrategy === 'function') {
|
384 | readyToRegister$ = options.registrationStrategy();
|
385 | }
|
386 | else {
|
387 | const [strategy, ...args] = (options.registrationStrategy || 'registerWhenStable:30000').split(':');
|
388 | switch (strategy) {
|
389 | case 'registerImmediately':
|
390 | readyToRegister$ = of(null);
|
391 | break;
|
392 | case 'registerWithDelay':
|
393 | readyToRegister$ = delayWithTimeout(+args[0] || 0);
|
394 | break;
|
395 | case 'registerWhenStable':
|
396 | readyToRegister$ = !args[0] ? whenStable(injector) :
|
397 | merge(whenStable(injector), delayWithTimeout(+args[0]));
|
398 | break;
|
399 | default:
|
400 |
|
401 | throw new Error(`Unknown ServiceWorker registration strategy: ${options.registrationStrategy}`);
|
402 | }
|
403 | }
|
404 |
|
405 |
|
406 |
|
407 |
|
408 | const ngZone = injector.get(NgZone);
|
409 | ngZone.runOutsideAngular(() => readyToRegister$.pipe(take(1)).subscribe(() => navigator.serviceWorker.register(script, { scope: options.scope })
|
410 | .catch(err => console.error('Service worker registration failed with:', err))));
|
411 | };
|
412 | return initializer;
|
413 | }
|
414 | function delayWithTimeout(timeout) {
|
415 | return of(null).pipe(delay(timeout));
|
416 | }
|
417 | function whenStable(injector) {
|
418 | const appRef = injector.get(ApplicationRef);
|
419 | return appRef.isStable.pipe(filter(stable => stable));
|
420 | }
|
421 | function ngswCommChannelFactory(opts, platformId) {
|
422 | return new NgswCommChannel(isPlatformBrowser(platformId) && opts.enabled !== false ? navigator.serviceWorker :
|
423 | undefined);
|
424 | }
|
425 |
|
426 |
|
427 |
|
428 | class ServiceWorkerModule {
|
429 | |
430 |
|
431 |
|
432 |
|
433 |
|
434 |
|
435 | static register(script, opts = {}) {
|
436 | return {
|
437 | ngModule: ServiceWorkerModule,
|
438 | providers: [
|
439 | { provide: SCRIPT, useValue: script },
|
440 | { provide: SwRegistrationOptions, useValue: opts },
|
441 | {
|
442 | provide: NgswCommChannel,
|
443 | useFactory: ngswCommChannelFactory,
|
444 | deps: [SwRegistrationOptions, PLATFORM_ID]
|
445 | },
|
446 | {
|
447 | provide: APP_INITIALIZER,
|
448 | useFactory: ngswAppInitializer,
|
449 | deps: [Injector, SCRIPT, SwRegistrationOptions, PLATFORM_ID],
|
450 | multi: true,
|
451 | },
|
452 | ],
|
453 | };
|
454 | }
|
455 | }
|
456 | ServiceWorkerModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.1.2", ngImport: i0, type: ServiceWorkerModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
457 | ServiceWorkerModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.1.2", ngImport: i0, type: ServiceWorkerModule });
|
458 | ServiceWorkerModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.1.2", ngImport: i0, type: ServiceWorkerModule, providers: [SwPush, SwUpdate] });
|
459 | i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.1.2", ngImport: i0, type: ServiceWorkerModule, decorators: [{
|
460 | type: NgModule,
|
461 | args: [{
|
462 | providers: [SwPush, SwUpdate],
|
463 | }]
|
464 | }] });
|
465 |
|
466 |
|
467 |
|
468 |
|
469 |
|
470 |
|
471 |
|
472 |
|
473 |
|
474 |
|
475 |
|
476 |
|
477 |
|
478 |
|
479 |
|
480 |
|
481 |
|
482 |
|
483 |
|
484 |
|
485 |
|
486 |
|
487 |
|
488 |
|
489 |
|
490 |
|
491 |
|
492 |
|
493 |
|
494 |
|
495 | export { ServiceWorkerModule, SwPush, SwRegistrationOptions, SwUpdate };
|
496 |
|