Compare commits

..

8 Commits
5.0.4 ... 5.0.5

Author SHA1 Message Date
9dc310eb50 docs: add changelog for 5.0.5 2017-12-01 14:40:05 -08:00
b26bc9096e release: cut the 5.0.5 release 2017-12-01 14:38:26 -08:00
56c98f7c23 fix(service-worker): use relative path for ngsw.json
Not every application is served from the domain root. The Service
Worker made a bad assumption that it would be, and so requested
/ngsw.json from the domain root.

This change corrects this assumption, and requests ngsw.json without
the leading slash. This causes the request to be interpreted
relative to the SW origin, which will be the application root.
2017-12-01 14:28:40 -08:00
6bf07b4e60 fix(service-worker): send initialization signal from the application
The Service Worker contains a mechanism by which it will postMessage
itself a signal to initialize its caches. Through this mechanism,
initialization happens asynchronously while keeping the SW process
alive.

Unfortunately in Firefox, the SW does not have the ability to
postMessage itself during the activation event. This prevents the
above mechanism from working, and the SW initializes on the next
fetch event, which is often too late.

Therefore, this change has the application wait for SW changes and
tells each new SW to initialize itself. This happens in addition to
the self-signal that the SW attempts to send (as self-signaling is
more reliable). That way even on browsers such as Firefox,
initialization happens eagerly.
2017-12-01 14:28:32 -08:00
a2ff4abddc fix(compiler-cli): propagate ts.SourceFile moduleName into metadata 2017-12-01 14:28:18 -08:00
ee37d4b26d fix(service-worker): don't crash if SW not supported
Currently a bug exists where attempting to inject SwPush crashes the
application if Service Workers are unsupported. This happens because
SwPush doesn't properly detect that navigator.serviceWorker isn't
set.

This change ensures that all passive observation of SwPush and
SwUpdate doesn't cause crashes, and that calling methods to perform
actions on them results in rejected Promises. It's up to applications
to detect when those services are not available, and refrain from
attempting to use them.

To that end, this change also adds an `isSupported` getter to both
services, so users don't have to rely on feature detection directly
with browser APIs. Currently this simply detects whether the SW API
is present, but in the future it will be expanded to detect whether
a particular browser supports specific APIs (such as push
notifications, for example).
2017-12-01 14:27:49 -08:00
f99335bc47 fix(service-worker): allow disabling SW while still using services
Currently, the way to not use the SW is to not install its module.
However, this means that you can't inject any of its services.

This change adds a ServiceWorkerModule.disabled() MWP, that still
registers all of the right providers but acts as if the browser does
not support Service Workers.
2017-12-01 14:27:34 -08:00
445d833b5d fix(build): accidental character in commit-message.json 2017-12-01 11:20:51 -08:00
16 changed files with 153 additions and 28 deletions

View File

@ -1,3 +1,17 @@
<a name="5.0.5"></a>
## [5.0.5](https://github.com/angular/angular/compare/5.0.4...5.0.5) (2017-12-01)
### Bug Fixes
* **compiler-cli:** propagate ts.SourceFile moduleName into metadata ([a2ff4ab](https://github.com/angular/angular/commit/a2ff4ab))
* **service-worker:** allow disabling SW while still using services ([f99335b](https://github.com/angular/angular/commit/f99335b))
* **service-worker:** don't crash if SW not supported ([ee37d4b](https://github.com/angular/angular/commit/ee37d4b))
* **service-worker:** send initialization signal from the application ([6bf07b4](https://github.com/angular/angular/commit/6bf07b4))
* **service-worker:** use relative path for ngsw.json ([56c98f7](https://github.com/angular/angular/commit/56c98f7))
<a name="5.0.4"></a> <a name="5.0.4"></a>
## [5.0.4](https://github.com/angular/angular/compare/5.0.3...5.0.4) (2017-12-01) ## [5.0.4](https://github.com/angular/angular/compare/5.0.3...5.0.4) (2017-12-01)

View File

@ -1,6 +1,6 @@
{ {
"name": "angular-srcs", "name": "angular-srcs",
"version": "5.0.4", "version": "5.0.5",
"private": true, "private": true,
"branchPattern": "2.0.*", "branchPattern": "2.0.*",
"description": "Angular - a web framework for modern web apps", "description": "Angular - a web framework for modern web apps",

View File

@ -551,6 +551,7 @@ export class MetadataCollector {
__symbolic: 'module', __symbolic: 'module',
version: this.options.version || METADATA_VERSION, metadata version: this.options.version || METADATA_VERSION, metadata
}; };
if (sourceFile.moduleName) result.importAs = sourceFile.moduleName;
if (exports) result.exports = exports; if (exports) result.exports = exports;
return result; return result;
} }

View File

@ -112,6 +112,7 @@ function upgradeMetadataWithDtsData(
newMetadata.metadata[prop] = dtsMetadata.metadata[prop]; newMetadata.metadata[prop] = dtsMetadata.metadata[prop];
} }
} }
if (dtsMetadata['importAs']) newMetadata['importAs'] = dtsMetadata['importAs'];
// Only copy exports from exports from metadata prior to version 3. // Only copy exports from exports from metadata prior to version 3.
// Starting with version 3 the collector began collecting exports and // Starting with version 3 the collector began collecting exports and

View File

@ -45,6 +45,7 @@ describe('Collector', () => {
're-exports.ts', 're-exports.ts',
're-exports-2.ts', 're-exports-2.ts',
'export-as.d.ts', 'export-as.d.ts',
'named-module.d.ts',
'static-field-reference.ts', 'static-field-reference.ts',
'static-method.ts', 'static-method.ts',
'static-method-call.ts', 'static-method-call.ts',
@ -101,6 +102,12 @@ describe('Collector', () => {
}); });
}); });
it('should preserve module names from TypeScript sources', () => {
const sourceFile = program.getSourceFile('named-module.d.ts');
const metadata = collector.getMetadata(sourceFile);
expect(metadata !['importAs']).toEqual('some-named-module');
});
it('should be able to collect a simple component\'s metadata', () => { it('should be able to collect a simple component\'s metadata', () => {
const sourceFile = program.getSourceFile('app/hero-detail.component.ts'); const sourceFile = program.getSourceFile('app/hero-detail.component.ts');
const metadata = collector.getMetadata(sourceFile); const metadata = collector.getMetadata(sourceFile);
@ -1384,6 +1391,10 @@ const FILES: Directory = {
declare function someFunction(): void; declare function someFunction(): void;
export { someFunction as SomeFunction }; export { someFunction as SomeFunction };
`, `,
'named-module.d.ts': `
/// <amd-module name="some-named-module" />
export type SomeType = 'a';
`,
'local-symbol-ref.ts': ` 'local-symbol-ref.ts': `
import {Component, Validators} from 'angular2/core'; import {Component, Validators} from 'angular2/core';

View File

@ -21,6 +21,7 @@ const globals = {
'rxjs/observable/defer': 'Rx.Observable', 'rxjs/observable/defer': 'Rx.Observable',
'rxjs/observable/fromEvent': 'Rx.Observable', 'rxjs/observable/fromEvent': 'Rx.Observable',
'rxjs/observable/merge': 'Rx.Observable', 'rxjs/observable/merge': 'Rx.Observable',
'rxjs/observable/never': 'Rx.Observable',
'rxjs/observable/of': 'Rx.Observable', 'rxjs/observable/of': 'Rx.Observable',
'rxjs/observable/throw': 'Rx.Observable', 'rxjs/observable/throw': 'Rx.Observable',

View File

@ -24,7 +24,7 @@ import {switchMap as op_switchMap} from 'rxjs/operator/switchMap';
import {take as op_take} from 'rxjs/operator/take'; import {take as op_take} from 'rxjs/operator/take';
import {toPromise as op_toPromise} from 'rxjs/operator/toPromise'; import {toPromise as op_toPromise} from 'rxjs/operator/toPromise';
const ERR_SW_NOT_SUPPORTED = 'Service workers are not supported by this browser'; export const ERR_SW_NOT_SUPPORTED = 'Service workers are disabled or not supported by this browser';
export interface Version { export interface Version {
hash: string; hash: string;
@ -86,9 +86,9 @@ export class NgswCommChannel {
*/ */
readonly events: Observable<IncomingEvent>; readonly events: Observable<IncomingEvent>;
constructor(serviceWorker: ServiceWorkerContainer|undefined) { constructor(private serviceWorker: ServiceWorkerContainer|undefined) {
if (!serviceWorker) { if (!serviceWorker) {
this.worker = this.events = errorObservable(ERR_SW_NOT_SUPPORTED); this.worker = this.events = this.registration = errorObservable(ERR_SW_NOT_SUPPORTED);
} else { } else {
const controllerChangeEvents = const controllerChangeEvents =
<Observable<any>>(obs_fromEvent(serviceWorker, 'controllerchange')); <Observable<any>>(obs_fromEvent(serviceWorker, 'controllerchange'));
@ -176,4 +176,6 @@ export class NgswCommChannel {
})); }));
return op_toPromise.call(mapErrorAndValue); return op_toPromise.call(mapErrorAndValue);
} }
get isEnabled(): boolean { return !!this.serviceWorker; }
} }

View File

@ -16,14 +16,18 @@ import {NgswCommChannel} from './low_level';
import {SwPush} from './push'; import {SwPush} from './push';
import {SwUpdate} from './update'; import {SwUpdate} from './update';
export abstract class RegistrationOptions {
scope?: string;
enabled?: boolean;
}
export const SCRIPT = new InjectionToken<string>('NGSW_REGISTER_SCRIPT'); export const SCRIPT = new InjectionToken<string>('NGSW_REGISTER_SCRIPT');
export const OPTS = new InjectionToken<Object>('NGSW_REGISTER_OPTIONS');
export function ngswAppInitializer( export function ngswAppInitializer(
injector: Injector, script: string, options: RegistrationOptions): Function { injector: Injector, script: string, options: RegistrationOptions): Function {
const initializer = () => { const initializer = () => {
const app = injector.get<ApplicationRef>(ApplicationRef); const app = injector.get<ApplicationRef>(ApplicationRef);
if (!('serviceWorker' in navigator)) { if (!('serviceWorker' in navigator) || options.enabled === false) {
return; return;
} }
const onStable = const onStable =
@ -31,15 +35,24 @@ export function ngswAppInitializer(
const isStable = op_take.call(onStable, 1) as Observable<boolean>; const isStable = op_take.call(onStable, 1) as Observable<boolean>;
const whenStable = op_toPromise.call(isStable) as Promise<boolean>; const whenStable = op_toPromise.call(isStable) as Promise<boolean>;
// 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.
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (navigator.serviceWorker.controller !== null) {
navigator.serviceWorker.controller.postMessage({action: 'INITIALIZE'});
}
});
// Don't return the Promise, as that will block the application until the SW is registered, and // Don't return the Promise, as that will block the application until the SW is registered, and
// cause a crash if the SW registration fails. // cause a crash if the SW registration fails.
whenStable.then(() => navigator.serviceWorker.register(script, options)); whenStable.then(() => navigator.serviceWorker.register(script, {scope: options.scope}));
}; };
return initializer; return initializer;
} }
export function ngswCommChannelFactory(): NgswCommChannel { export function ngswCommChannelFactory(opts: RegistrationOptions): NgswCommChannel {
return new NgswCommChannel(navigator.serviceWorker); return new NgswCommChannel(opts.enabled !== false ? navigator.serviceWorker : undefined);
} }
/** /**
@ -49,20 +62,27 @@ export function ngswCommChannelFactory(): NgswCommChannel {
providers: [SwPush, SwUpdate], providers: [SwPush, SwUpdate],
}) })
export class ServiceWorkerModule { export class ServiceWorkerModule {
static register(script: string, opts: RegistrationOptions = {}): ModuleWithProviders { /**
* 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.
*/
static register(script: string, opts: {scope?: string; enabled?: boolean;} = {}):
ModuleWithProviders {
return { return {
ngModule: ServiceWorkerModule, ngModule: ServiceWorkerModule,
providers: [ providers: [
{provide: SCRIPT, useValue: script}, {provide: SCRIPT, useValue: script},
{provide: OPTS, useValue: opts}, {provide: RegistrationOptions, useValue: opts},
{provide: NgswCommChannel, useFactory: ngswCommChannelFactory}, {provide: NgswCommChannel, useFactory: ngswCommChannelFactory, deps: [RegistrationOptions]},
{ {
provide: APP_INITIALIZER, provide: APP_INITIALIZER,
useFactory: ngswAppInitializer, useFactory: ngswAppInitializer,
deps: [Injector, SCRIPT, OPTS], deps: [Injector, SCRIPT, RegistrationOptions],
multi: true, multi: true,
}, },
], ],
}; };
} }
} }

View File

@ -9,15 +9,15 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {Observable} from 'rxjs/Observable'; import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject'; import {Subject} from 'rxjs/Subject';
import {merge as obs_merge} from 'rxjs/observable/merge'; import {merge as obs_merge} from 'rxjs/observable/merge';
import {never as obs_never} from 'rxjs/observable/never';
import {map as op_map} from 'rxjs/operator/map'; import {map as op_map} from 'rxjs/operator/map';
import {switchMap as op_switchMap} from 'rxjs/operator/switchMap'; import {switchMap as op_switchMap} from 'rxjs/operator/switchMap';
import {take as op_take} from 'rxjs/operator/take'; import {take as op_take} from 'rxjs/operator/take';
import {toPromise as op_toPromise} from 'rxjs/operator/toPromise'; import {toPromise as op_toPromise} from 'rxjs/operator/toPromise';
import {NgswCommChannel} from './low_level'; import {ERR_SW_NOT_SUPPORTED, NgswCommChannel} from './low_level';
/** /**
* Subscribe and listen to push notifications from the Service Worker. * Subscribe and listen to push notifications from the Service Worker.
@ -34,6 +34,11 @@ export class SwPush {
new Subject<PushSubscription|null>(); new Subject<PushSubscription|null>();
constructor(private sw: NgswCommChannel) { constructor(private sw: NgswCommChannel) {
if (!sw.isEnabled) {
this.messages = obs_never();
this.subscription = obs_never();
return;
}
this.messages = this.messages =
op_map.call(this.sw.eventsOfType('PUSH'), (message: {data: object}) => message.data); op_map.call(this.sw.eventsOfType('PUSH'), (message: {data: object}) => message.data);
@ -46,7 +51,16 @@ export class SwPush {
this.subscription = obs_merge(workerDrivenSubscriptions, this.subscriptionChanges); this.subscription = obs_merge(workerDrivenSubscriptions, this.subscriptionChanges);
} }
/**
* Returns true if the Service Worker is enabled (supported by the browser and enabled via
* ServiceWorkerModule).
*/
get isEnabled(): boolean { return this.sw.isEnabled; }
requestSubscription(options: {serverPublicKey: string}): Promise<PushSubscription> { requestSubscription(options: {serverPublicKey: string}): Promise<PushSubscription> {
if (!this.sw.isEnabled) {
return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED));
}
const pushOptions: PushSubscriptionOptionsInit = {userVisibleOnly: true}; const pushOptions: PushSubscriptionOptionsInit = {userVisibleOnly: true};
let key = atob(options.serverPublicKey.replace(/_/g, '/').replace(/-/g, '+')); let key = atob(options.serverPublicKey.replace(/_/g, '/').replace(/-/g, '+'));
let applicationServerKey = new Uint8Array(new ArrayBuffer(key.length)); let applicationServerKey = new Uint8Array(new ArrayBuffer(key.length));
@ -64,6 +78,9 @@ export class SwPush {
} }
unsubscribe(): Promise<void> { unsubscribe(): Promise<void> {
if (!this.sw.isEnabled) {
return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED));
}
const unsubscribe = op_switchMap.call(this.subscription, (sub: PushSubscription | null) => { const unsubscribe = op_switchMap.call(this.subscription, (sub: PushSubscription | null) => {
if (sub !== null) { if (sub !== null) {
return sub.unsubscribe().then(success => { return sub.unsubscribe().then(success => {

View File

@ -9,9 +9,10 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {Observable} from 'rxjs/Observable'; import {Observable} from 'rxjs/Observable';
import {defer as obs_defer} from 'rxjs/observable/defer'; import {defer as obs_defer} from 'rxjs/observable/defer';
import {never as obs_never} from 'rxjs/observable/never';
import {map as op_map} from 'rxjs/operator/map'; import {map as op_map} from 'rxjs/operator/map';
import {NgswCommChannel, UpdateActivatedEvent, UpdateAvailableEvent} from './low_level'; import {ERR_SW_NOT_SUPPORTED, NgswCommChannel, UpdateActivatedEvent, UpdateAvailableEvent} from './low_level';
/** /**
@ -26,16 +27,33 @@ export class SwUpdate {
readonly activated: Observable<UpdateActivatedEvent>; readonly activated: Observable<UpdateActivatedEvent>;
constructor(private sw: NgswCommChannel) { constructor(private sw: NgswCommChannel) {
if (!sw.isEnabled) {
this.available = obs_never();
this.activated = obs_never();
return;
}
this.available = this.sw.eventsOfType('UPDATE_AVAILABLE'); this.available = this.sw.eventsOfType('UPDATE_AVAILABLE');
this.activated = this.sw.eventsOfType('UPDATE_ACTIVATED'); this.activated = this.sw.eventsOfType('UPDATE_ACTIVATED');
} }
/**
* Returns true if the Service Worker is enabled (supported by the browser and enabled via
* ServiceWorkerModule).
*/
get isEnabled(): boolean { return this.sw.isEnabled; }
checkForUpdate(): Promise<void> { checkForUpdate(): Promise<void> {
if (!this.sw.isEnabled) {
return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED));
}
const statusNonce = this.sw.generateNonce(); const statusNonce = this.sw.generateNonce();
return this.sw.postMessageWithStatus('CHECK_FOR_UPDATES', {statusNonce}, statusNonce); return this.sw.postMessageWithStatus('CHECK_FOR_UPDATES', {statusNonce}, statusNonce);
} }
activateUpdate(): Promise<void> { activateUpdate(): Promise<void> {
if (!this.sw.isEnabled) {
return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED));
}
const statusNonce = this.sw.generateNonce(); const statusNonce = this.sw.generateNonce();
return this.sw.postMessageWithStatus('ACTIVATE_UPDATE', {statusNonce}, statusNonce); return this.sw.postMessageWithStatus('ACTIVATE_UPDATE', {statusNonce}, statusNonce);
} }

View File

@ -71,6 +71,24 @@ export function main() {
}); });
expect(() => TestBed.get(SwPush)).not.toThrow(); expect(() => TestBed.get(SwPush)).not.toThrow();
}); });
describe('with no SW', () => {
beforeEach(() => { comm = new NgswCommChannel(undefined); });
it('can be instantiated', () => { push = new SwPush(comm); });
it('does not crash on subscription to observables', () => {
push = new SwPush(comm);
push.messages.toPromise().catch(err => fail(err));
push.subscription.toPromise().catch(err => fail(err));
});
it('gives an error when registering', done => {
push = new SwPush(comm);
push.requestSubscription({serverPublicKey: 'test'}).catch(err => { done(); });
});
it('gives an error when unsubscribing', done => {
push = new SwPush(comm);
push.unsubscribe().catch(err => { done(); });
});
});
}); });
describe('SwUpdate', () => { describe('SwUpdate', () => {
let update: SwUpdate; let update: SwUpdate;
@ -147,6 +165,23 @@ export function main() {
}); });
expect(() => TestBed.get(SwUpdate)).not.toThrow(); expect(() => TestBed.get(SwUpdate)).not.toThrow();
}); });
describe('with no SW', () => {
beforeEach(() => { comm = new NgswCommChannel(undefined); });
it('can be instantiated', () => { update = new SwUpdate(comm); });
it('does not crash on subscription to observables', () => {
update = new SwUpdate(comm);
update.available.toPromise().catch(err => fail(err));
update.activated.toPromise().catch(err => fail(err));
});
it('gives an error when checking for updates', done => {
update = new SwUpdate(comm);
update.checkForUpdate().catch(err => { done(); });
});
it('gives an error when activating updates', done => {
update = new SwUpdate(comm);
update.activateUpdate().catch(err => { done(); });
});
});
}); });
}); });
} }

View File

@ -580,8 +580,8 @@ export class Driver implements Debuggable, UpdateSource {
* Retrieve a copy of the latest manifest from the server. * Retrieve a copy of the latest manifest from the server.
*/ */
private async fetchLatestManifest(): Promise<Manifest> { private async fetchLatestManifest(): Promise<Manifest> {
const res = await this.safeFetch( const res =
this.adapter.newRequest('/ngsw.json?ngsw-cache-bust=' + Math.random())); await this.safeFetch(this.adapter.newRequest('ngsw.json?ngsw-cache-bust=' + Math.random()));
if (!res.ok) { if (!res.ok) {
if (res.status === 404) { if (res.status === 404) {
await this.deleteAllCaches(); await this.deleteAllCaches();

View File

@ -158,7 +158,7 @@ export function main() {
expect(await scope.startup(true)).toEqual(true); expect(await scope.startup(true)).toEqual(true);
await scope.resolveSelfMessages(); await scope.resolveSelfMessages();
await driver.initialized; await driver.initialized;
server.assertSawRequestFor('/ngsw.json'); server.assertSawRequestFor('ngsw.json');
server.assertSawRequestFor('/foo.txt'); server.assertSawRequestFor('/foo.txt');
server.assertSawRequestFor('/bar.txt'); server.assertSawRequestFor('/bar.txt');
server.assertSawRequestFor('/redirected.txt'); server.assertSawRequestFor('/redirected.txt');
@ -170,7 +170,7 @@ export function main() {
async_it('initializes prefetched content correctly, after a request kicks it off', async() => { async_it('initializes prefetched content correctly, after a request kicks it off', async() => {
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo'); expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
await driver.initialized; await driver.initialized;
server.assertSawRequestFor('/ngsw.json'); server.assertSawRequestFor('ngsw.json');
server.assertSawRequestFor('/foo.txt'); server.assertSawRequestFor('/foo.txt');
server.assertSawRequestFor('/bar.txt'); server.assertSawRequestFor('/bar.txt');
server.assertSawRequestFor('/redirected.txt'); server.assertSawRequestFor('/redirected.txt');
@ -230,7 +230,7 @@ export function main() {
scope.updateServerState(serverUpdate); scope.updateServerState(serverUpdate);
expect(await driver.checkForUpdate()).toEqual(true); expect(await driver.checkForUpdate()).toEqual(true);
serverUpdate.assertSawRequestFor('/ngsw.json'); serverUpdate.assertSawRequestFor('ngsw.json');
serverUpdate.assertSawRequestFor('/foo.txt'); serverUpdate.assertSawRequestFor('/foo.txt');
serverUpdate.assertSawRequestFor('/redirected.txt'); serverUpdate.assertSawRequestFor('/redirected.txt');
serverUpdate.assertNoOtherRequests(); serverUpdate.assertNoOtherRequests();
@ -263,7 +263,7 @@ export function main() {
scope.updateServerState(serverUpdate); scope.updateServerState(serverUpdate);
expect(await driver.checkForUpdate()).toEqual(true); expect(await driver.checkForUpdate()).toEqual(true);
serverUpdate.assertSawRequestFor('/ngsw.json'); serverUpdate.assertSawRequestFor('ngsw.json');
serverUpdate.assertSawRequestFor('/foo.txt'); serverUpdate.assertSawRequestFor('/foo.txt');
serverUpdate.assertSawRequestFor('/redirected.txt'); serverUpdate.assertSawRequestFor('/redirected.txt');
serverUpdate.assertNoOtherRequests(); serverUpdate.assertNoOtherRequests();
@ -331,7 +331,7 @@ export function main() {
scope.advance(12000); scope.advance(12000);
await driver.idle.empty; await driver.idle.empty;
serverUpdate.assertSawRequestFor('/ngsw.json'); serverUpdate.assertSawRequestFor('ngsw.json');
serverUpdate.assertSawRequestFor('/foo.txt'); serverUpdate.assertSawRequestFor('/foo.txt');
serverUpdate.assertSawRequestFor('/redirected.txt'); serverUpdate.assertSawRequestFor('/redirected.txt');
serverUpdate.assertNoOtherRequests(); serverUpdate.assertNoOtherRequests();

View File

@ -73,7 +73,7 @@ export class MockServerStateBuilder {
} }
withManifest(manifest: Manifest): MockServerStateBuilder { withManifest(manifest: Manifest): MockServerStateBuilder {
this.resources.set('/ngsw.json', new MockResponse(JSON.stringify(manifest))); this.resources.set('ngsw.json', new MockResponse(JSON.stringify(manifest)));
return this; return this;
} }

View File

@ -1,10 +1,14 @@
/** @experimental */ /** @experimental */
export declare class ServiceWorkerModule { export declare class ServiceWorkerModule {
static register(script: string, opts?: RegistrationOptions): ModuleWithProviders; static register(script: string, opts?: {
scope?: string;
enabled?: boolean;
}): ModuleWithProviders;
} }
/** @experimental */ /** @experimental */
export declare class SwPush { export declare class SwPush {
readonly isEnabled: boolean;
readonly messages: Observable<object>; readonly messages: Observable<object>;
readonly subscription: Observable<PushSubscription | null>; readonly subscription: Observable<PushSubscription | null>;
constructor(sw: NgswCommChannel); constructor(sw: NgswCommChannel);
@ -18,6 +22,7 @@ export declare class SwPush {
export declare class SwUpdate { export declare class SwUpdate {
readonly activated: Observable<UpdateActivatedEvent>; readonly activated: Observable<UpdateActivatedEvent>;
readonly available: Observable<UpdateAvailableEvent>; readonly available: Observable<UpdateAvailableEvent>;
readonly isEnabled: boolean;
constructor(sw: NgswCommChannel); constructor(sw: NgswCommChannel);
activateUpdate(): Promise<void>; activateUpdate(): Promise<void>;
checkForUpdate(): Promise<void>; checkForUpdate(): Promise<void>;

View File

@ -1,4 +1,4 @@
g{ {
"maxLength": 120, "maxLength": 120,
"types": [ "types": [
"build", "build",