mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
96b79fc393
This change replaces all remaining occurrences of `typeof ngDevMode !== undefined` with the correct `typeof ngDevMode !== 'undefined'` form. This aligns the codebase with JavaScript typeof semantics and maintains consistency with other Angular code.
256 lines
9.4 KiB
TypeScript
256 lines
9.4 KiB
TypeScript
/*!
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.dev/license
|
|
*/
|
|
|
|
import {
|
|
ApplicationRef,
|
|
EnvironmentProviders,
|
|
inject,
|
|
InjectionToken,
|
|
Injector,
|
|
makeEnvironmentProviders,
|
|
NgZone,
|
|
provideAppInitializer,
|
|
ɵRuntimeError as RuntimeError,
|
|
ɵformatRuntimeError as formatRuntimeError,
|
|
} from '@angular/core';
|
|
import type {Observable} from 'rxjs';
|
|
|
|
import {NgswCommChannel} from './low_level';
|
|
import {SwPush} from './push';
|
|
import {SwUpdate} from './update';
|
|
import {RuntimeErrorCode} from './errors';
|
|
|
|
export const SCRIPT = new InjectionToken<string>(
|
|
typeof ngDevMode !== 'undefined' && ngDevMode ? 'NGSW_REGISTER_SCRIPT' : '',
|
|
);
|
|
|
|
export function ngswAppInitializer(): void {
|
|
if (typeof ngServerMode !== 'undefined' && ngServerMode) {
|
|
return;
|
|
}
|
|
|
|
const options = inject(SwRegistrationOptions);
|
|
|
|
if (!('serviceWorker' in navigator && options.enabled !== false)) {
|
|
return;
|
|
}
|
|
|
|
const script = inject(SCRIPT);
|
|
const ngZone = inject(NgZone);
|
|
const appRef = inject(ApplicationRef);
|
|
|
|
// Set up the `controllerchange` event listener outside of
|
|
// the Angular zone to avoid unnecessary change detections,
|
|
// as this event has no impact on view updates.
|
|
ngZone.runOutsideAngular(() => {
|
|
// Wait for service worker controller changes, and fire an INITIALIZE action when a new SW
|
|
// becomes active. This allows the SW to initialize itself even if there is no application
|
|
// traffic.
|
|
const sw = navigator.serviceWorker;
|
|
const onControllerChange = () => sw.controller?.postMessage({action: 'INITIALIZE'});
|
|
|
|
sw.addEventListener('controllerchange', onControllerChange);
|
|
|
|
appRef.onDestroy(() => {
|
|
sw.removeEventListener('controllerchange', onControllerChange);
|
|
});
|
|
});
|
|
|
|
// Run outside the Angular zone to avoid preventing the app from stabilizing (especially
|
|
// given that some registration strategies wait for the app to stabilize).
|
|
ngZone.runOutsideAngular(() => {
|
|
let readyToRegister: Promise<void>;
|
|
|
|
const {registrationStrategy} = options;
|
|
if (typeof registrationStrategy === 'function') {
|
|
readyToRegister = new Promise((resolve) => registrationStrategy().subscribe(() => resolve()));
|
|
} else {
|
|
const [strategy, ...args] = (registrationStrategy || 'registerWhenStable:30000').split(':');
|
|
|
|
switch (strategy) {
|
|
case 'registerImmediately':
|
|
readyToRegister = Promise.resolve();
|
|
break;
|
|
case 'registerWithDelay':
|
|
readyToRegister = delayWithTimeout(+args[0] || 0);
|
|
break;
|
|
case 'registerWhenStable':
|
|
readyToRegister = Promise.race([appRef.whenStable(), delayWithTimeout(+args[0])]);
|
|
break;
|
|
default:
|
|
// Unknown strategy.
|
|
throw new RuntimeError(
|
|
RuntimeErrorCode.UNKNOWN_REGISTRATION_STRATEGY,
|
|
(typeof ngDevMode === 'undefined' || ngDevMode) &&
|
|
`Unknown ServiceWorker registration strategy: ${options.registrationStrategy}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Don't return anything to avoid blocking the application until the SW is registered.
|
|
// Catch and log the error if SW registration fails to avoid uncaught rejection warning.
|
|
readyToRegister.then(() => {
|
|
// If the registration strategy has resolved after the application has
|
|
// been explicitly destroyed by the user (e.g., by navigating away to
|
|
// another application), we simply should not register the worker.
|
|
if (appRef.destroyed) {
|
|
return;
|
|
}
|
|
|
|
navigator.serviceWorker
|
|
.register(script, {
|
|
scope: options.scope,
|
|
updateViaCache: options.updateViaCache,
|
|
type: options.type,
|
|
})
|
|
.catch((err) =>
|
|
console.error(
|
|
formatRuntimeError(
|
|
RuntimeErrorCode.SERVICE_WORKER_REGISTRATION_FAILED,
|
|
(typeof ngDevMode === 'undefined' || ngDevMode) &&
|
|
'Service worker registration failed with: ' + err,
|
|
),
|
|
),
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
function delayWithTimeout(timeout: number): Promise<void> {
|
|
return new Promise((resolve) => setTimeout(resolve, timeout));
|
|
}
|
|
|
|
export function ngswCommChannelFactory(): NgswCommChannel {
|
|
const opts = inject(SwRegistrationOptions);
|
|
const injector = inject(Injector);
|
|
const isBrowser = !(typeof ngServerMode !== 'undefined' && ngServerMode);
|
|
|
|
return new NgswCommChannel(
|
|
isBrowser && opts.enabled !== false ? navigator.serviceWorker : undefined,
|
|
injector,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Token that can be used to provide options for `ServiceWorkerModule` outside of
|
|
* `ServiceWorkerModule.register()`.
|
|
*
|
|
* You can use this token to define a provider that generates the registration options at runtime,
|
|
* for example via a function call:
|
|
*
|
|
* {@example service-worker/registration-options/module.ts region="registration-options"
|
|
* header="app.module.ts"}
|
|
*
|
|
* @see [Service worker configuration](ecosystem/service-workers/getting-started#service-worker-configuration)
|
|
*
|
|
* @publicApi
|
|
*/
|
|
export abstract class SwRegistrationOptions {
|
|
/**
|
|
* Whether the ServiceWorker will be registered and the related services (such as `SwPush` and
|
|
* `SwUpdate`) will attempt to communicate and interact with it.
|
|
*
|
|
* Default: true
|
|
*/
|
|
enabled?: boolean;
|
|
|
|
/**
|
|
* The value of the setting used to determine the circumstances in which the browser
|
|
* will consult the HTTP cache when it tries to update the service worker or any scripts that are imported via importScripts().
|
|
* [ServiceWorkerRegistration.updateViaCache](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/updateViaCache)
|
|
*/
|
|
updateViaCache?: ServiceWorkerUpdateViaCache;
|
|
|
|
/**
|
|
* The type of the ServiceWorker script to register.
|
|
* [ServiceWorkerRegistration#type](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register#type)
|
|
* - `classic`: Registers the script as a classic worker. ES module features such as `import` and `export` are NOT allowed in the script.
|
|
* - `module`: Registers the script as an ES module. Allows use of `import`/`export` syntax and module features.
|
|
*
|
|
* @default 'classic'
|
|
*/
|
|
type?: WorkerType;
|
|
|
|
/**
|
|
* A URL that defines the ServiceWorker's registration scope; that is, what range of URLs it can
|
|
* control. It will be used when calling
|
|
* [ServiceWorkerContainer#register()](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register).
|
|
*/
|
|
scope?: string;
|
|
|
|
/**
|
|
* Defines the ServiceWorker registration strategy, which determines when it will be registered
|
|
* with the browser.
|
|
*
|
|
* The default behavior of registering once the application stabilizes (i.e. as soon as there are
|
|
* no pending micro- and macro-tasks) is designed to register the ServiceWorker as soon as
|
|
* possible but without affecting the application's first time load.
|
|
*
|
|
* Still, there might be cases where you want more control over when the ServiceWorker is
|
|
* registered (for example, there might be a long-running timeout or polling interval, preventing
|
|
* the app from stabilizing). The available option are:
|
|
*
|
|
* - `registerWhenStable:<timeout>`: Register as soon as the application stabilizes (no pending
|
|
* micro-/macro-tasks) but no later than `<timeout>` milliseconds. If the app hasn't
|
|
* stabilized after `<timeout>` milliseconds (for example, due to a recurrent asynchronous
|
|
* task), the ServiceWorker will be registered anyway.
|
|
* If `<timeout>` is omitted, the ServiceWorker will only be registered once the app
|
|
* stabilizes.
|
|
* - `registerImmediately`: Register immediately.
|
|
* - `registerWithDelay:<timeout>`: Register with a delay of `<timeout>` milliseconds. For
|
|
* example, use `registerWithDelay:5000` to register the ServiceWorker after 5 seconds. If
|
|
* `<timeout>` is omitted, is defaults to `0`, which will register the ServiceWorker as soon
|
|
* as possible but still asynchronously, once all pending micro-tasks are completed.
|
|
* - An Observable factory function: A function that returns an `Observable`.
|
|
* The function will be used at runtime to obtain and subscribe to the `Observable` and the
|
|
* ServiceWorker will be registered as soon as the first value is emitted.
|
|
*
|
|
* Default: 'registerWhenStable:30000'
|
|
*/
|
|
registrationStrategy?: string | (() => Observable<unknown>);
|
|
}
|
|
|
|
/**
|
|
* @publicApi
|
|
*
|
|
* Sets up providers to register the given Angular Service Worker script.
|
|
*
|
|
* If `enabled` is set to `false` in the given options, the module will behave as if service
|
|
* workers are not supported by the browser, and the service worker will not be registered.
|
|
*
|
|
* Example usage:
|
|
* ```ts
|
|
* bootstrapApplication(AppComponent, {
|
|
* providers: [
|
|
* provideServiceWorker('ngsw-worker.js')
|
|
* ],
|
|
* });
|
|
* ```
|
|
*
|
|
* @see [Custom service worker script](ecosystem/service-workers/custom-service-worker-scripts)
|
|
*
|
|
* @see [Service worker configuration](ecosystem/service-workers/getting-started#service-worker-configuration)
|
|
*
|
|
*/
|
|
export function provideServiceWorker(
|
|
script: string,
|
|
options: SwRegistrationOptions = {},
|
|
): EnvironmentProviders {
|
|
return makeEnvironmentProviders([
|
|
SwPush,
|
|
SwUpdate,
|
|
{provide: SCRIPT, useValue: script},
|
|
{provide: SwRegistrationOptions, useValue: options},
|
|
{
|
|
provide: NgswCommChannel,
|
|
useFactory: ngswCommChannelFactory,
|
|
},
|
|
provideAppInitializer(ngswAppInitializer),
|
|
]);
|
|
}
|