Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
9dc310eb50 | |||
b26bc9096e | |||
56c98f7c23 | |||
6bf07b4e60 | |||
a2ff4abddc | |||
ee37d4b26d | |||
f99335bc47 | |||
445d833b5d |
14
CHANGELOG.md
14
CHANGELOG.md
@ -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>
|
||||
## [5.0.4](https://github.com/angular/angular/compare/5.0.3...5.0.4) (2017-12-01)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "5.0.4",
|
||||
"version": "5.0.5",
|
||||
"private": true,
|
||||
"branchPattern": "2.0.*",
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
|
@ -551,6 +551,7 @@ export class MetadataCollector {
|
||||
__symbolic: 'module',
|
||||
version: this.options.version || METADATA_VERSION, metadata
|
||||
};
|
||||
if (sourceFile.moduleName) result.importAs = sourceFile.moduleName;
|
||||
if (exports) result.exports = exports;
|
||||
return result;
|
||||
}
|
||||
|
@ -112,6 +112,7 @@ function upgradeMetadataWithDtsData(
|
||||
newMetadata.metadata[prop] = dtsMetadata.metadata[prop];
|
||||
}
|
||||
}
|
||||
if (dtsMetadata['importAs']) newMetadata['importAs'] = dtsMetadata['importAs'];
|
||||
|
||||
// Only copy exports from exports from metadata prior to version 3.
|
||||
// Starting with version 3 the collector began collecting exports and
|
||||
|
@ -45,6 +45,7 @@ describe('Collector', () => {
|
||||
're-exports.ts',
|
||||
're-exports-2.ts',
|
||||
'export-as.d.ts',
|
||||
'named-module.d.ts',
|
||||
'static-field-reference.ts',
|
||||
'static-method.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', () => {
|
||||
const sourceFile = program.getSourceFile('app/hero-detail.component.ts');
|
||||
const metadata = collector.getMetadata(sourceFile);
|
||||
@ -1384,6 +1391,10 @@ const FILES: Directory = {
|
||||
declare function someFunction(): void;
|
||||
export { someFunction as SomeFunction };
|
||||
`,
|
||||
'named-module.d.ts': `
|
||||
/// <amd-module name="some-named-module" />
|
||||
export type SomeType = 'a';
|
||||
`,
|
||||
'local-symbol-ref.ts': `
|
||||
import {Component, Validators} from 'angular2/core';
|
||||
|
||||
|
@ -21,6 +21,7 @@ const globals = {
|
||||
'rxjs/observable/defer': 'Rx.Observable',
|
||||
'rxjs/observable/fromEvent': 'Rx.Observable',
|
||||
'rxjs/observable/merge': 'Rx.Observable',
|
||||
'rxjs/observable/never': 'Rx.Observable',
|
||||
'rxjs/observable/of': 'Rx.Observable',
|
||||
'rxjs/observable/throw': 'Rx.Observable',
|
||||
|
||||
|
@ -24,7 +24,7 @@ import {switchMap as op_switchMap} from 'rxjs/operator/switchMap';
|
||||
import {take as op_take} from 'rxjs/operator/take';
|
||||
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 {
|
||||
hash: string;
|
||||
@ -86,9 +86,9 @@ export class NgswCommChannel {
|
||||
*/
|
||||
readonly events: Observable<IncomingEvent>;
|
||||
|
||||
constructor(serviceWorker: ServiceWorkerContainer|undefined) {
|
||||
constructor(private serviceWorker: ServiceWorkerContainer|undefined) {
|
||||
if (!serviceWorker) {
|
||||
this.worker = this.events = errorObservable(ERR_SW_NOT_SUPPORTED);
|
||||
this.worker = this.events = this.registration = errorObservable(ERR_SW_NOT_SUPPORTED);
|
||||
} else {
|
||||
const controllerChangeEvents =
|
||||
<Observable<any>>(obs_fromEvent(serviceWorker, 'controllerchange'));
|
||||
@ -176,4 +176,6 @@ export class NgswCommChannel {
|
||||
}));
|
||||
return op_toPromise.call(mapErrorAndValue);
|
||||
}
|
||||
|
||||
get isEnabled(): boolean { return !!this.serviceWorker; }
|
||||
}
|
@ -16,14 +16,18 @@ import {NgswCommChannel} from './low_level';
|
||||
import {SwPush} from './push';
|
||||
import {SwUpdate} from './update';
|
||||
|
||||
export abstract class RegistrationOptions {
|
||||
scope?: string;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export const SCRIPT = new InjectionToken<string>('NGSW_REGISTER_SCRIPT');
|
||||
export const OPTS = new InjectionToken<Object>('NGSW_REGISTER_OPTIONS');
|
||||
|
||||
export function ngswAppInitializer(
|
||||
injector: Injector, script: string, options: RegistrationOptions): Function {
|
||||
const initializer = () => {
|
||||
const app = injector.get<ApplicationRef>(ApplicationRef);
|
||||
if (!('serviceWorker' in navigator)) {
|
||||
if (!('serviceWorker' in navigator) || options.enabled === false) {
|
||||
return;
|
||||
}
|
||||
const onStable =
|
||||
@ -31,15 +35,24 @@ export function ngswAppInitializer(
|
||||
const isStable = op_take.call(onStable, 1) as Observable<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
|
||||
// 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;
|
||||
}
|
||||
|
||||
export function ngswCommChannelFactory(): NgswCommChannel {
|
||||
return new NgswCommChannel(navigator.serviceWorker);
|
||||
export function ngswCommChannelFactory(opts: RegistrationOptions): NgswCommChannel {
|
||||
return new NgswCommChannel(opts.enabled !== false ? navigator.serviceWorker : undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -49,20 +62,27 @@ export function ngswCommChannelFactory(): NgswCommChannel {
|
||||
providers: [SwPush, SwUpdate],
|
||||
})
|
||||
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 {
|
||||
ngModule: ServiceWorkerModule,
|
||||
providers: [
|
||||
{provide: SCRIPT, useValue: script},
|
||||
{provide: OPTS, useValue: opts},
|
||||
{provide: NgswCommChannel, useFactory: ngswCommChannelFactory},
|
||||
{provide: RegistrationOptions, useValue: opts},
|
||||
{provide: NgswCommChannel, useFactory: ngswCommChannelFactory, deps: [RegistrationOptions]},
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: ngswAppInitializer,
|
||||
deps: [Injector, SCRIPT, OPTS],
|
||||
deps: [Injector, SCRIPT, RegistrationOptions],
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,15 +9,15 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {Subject} from 'rxjs/Subject';
|
||||
|
||||
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 {switchMap as op_switchMap} from 'rxjs/operator/switchMap';
|
||||
import {take as op_take} from 'rxjs/operator/take';
|
||||
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.
|
||||
@ -34,6 +34,11 @@ export class SwPush {
|
||||
new Subject<PushSubscription|null>();
|
||||
|
||||
constructor(private sw: NgswCommChannel) {
|
||||
if (!sw.isEnabled) {
|
||||
this.messages = obs_never();
|
||||
this.subscription = obs_never();
|
||||
return;
|
||||
}
|
||||
this.messages =
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
if (!this.sw.isEnabled) {
|
||||
return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED));
|
||||
}
|
||||
const pushOptions: PushSubscriptionOptionsInit = {userVisibleOnly: true};
|
||||
let key = atob(options.serverPublicKey.replace(/_/g, '/').replace(/-/g, '+'));
|
||||
let applicationServerKey = new Uint8Array(new ArrayBuffer(key.length));
|
||||
@ -64,6 +78,9 @@ export class SwPush {
|
||||
}
|
||||
|
||||
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) => {
|
||||
if (sub !== null) {
|
||||
return sub.unsubscribe().then(success => {
|
||||
|
@ -9,9 +9,10 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
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 {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>;
|
||||
|
||||
constructor(private sw: NgswCommChannel) {
|
||||
if (!sw.isEnabled) {
|
||||
this.available = obs_never();
|
||||
this.activated = obs_never();
|
||||
return;
|
||||
}
|
||||
this.available = this.sw.eventsOfType('UPDATE_AVAILABLE');
|
||||
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> {
|
||||
if (!this.sw.isEnabled) {
|
||||
return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED));
|
||||
}
|
||||
const statusNonce = this.sw.generateNonce();
|
||||
return this.sw.postMessageWithStatus('CHECK_FOR_UPDATES', {statusNonce}, statusNonce);
|
||||
}
|
||||
|
||||
activateUpdate(): Promise<void> {
|
||||
if (!this.sw.isEnabled) {
|
||||
return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED));
|
||||
}
|
||||
const statusNonce = this.sw.generateNonce();
|
||||
return this.sw.postMessageWithStatus('ACTIVATE_UPDATE', {statusNonce}, statusNonce);
|
||||
}
|
||||
|
@ -71,6 +71,24 @@ export function main() {
|
||||
});
|
||||
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', () => {
|
||||
let update: SwUpdate;
|
||||
@ -147,6 +165,23 @@ export function main() {
|
||||
});
|
||||
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(); });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -580,8 +580,8 @@ export class Driver implements Debuggable, UpdateSource {
|
||||
* Retrieve a copy of the latest manifest from the server.
|
||||
*/
|
||||
private async fetchLatestManifest(): Promise<Manifest> {
|
||||
const res = await this.safeFetch(
|
||||
this.adapter.newRequest('/ngsw.json?ngsw-cache-bust=' + Math.random()));
|
||||
const res =
|
||||
await this.safeFetch(this.adapter.newRequest('ngsw.json?ngsw-cache-bust=' + Math.random()));
|
||||
if (!res.ok) {
|
||||
if (res.status === 404) {
|
||||
await this.deleteAllCaches();
|
||||
|
@ -158,7 +158,7 @@ export function main() {
|
||||
expect(await scope.startup(true)).toEqual(true);
|
||||
await scope.resolveSelfMessages();
|
||||
await driver.initialized;
|
||||
server.assertSawRequestFor('/ngsw.json');
|
||||
server.assertSawRequestFor('ngsw.json');
|
||||
server.assertSawRequestFor('/foo.txt');
|
||||
server.assertSawRequestFor('/bar.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() => {
|
||||
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
|
||||
await driver.initialized;
|
||||
server.assertSawRequestFor('/ngsw.json');
|
||||
server.assertSawRequestFor('ngsw.json');
|
||||
server.assertSawRequestFor('/foo.txt');
|
||||
server.assertSawRequestFor('/bar.txt');
|
||||
server.assertSawRequestFor('/redirected.txt');
|
||||
@ -230,7 +230,7 @@ export function main() {
|
||||
|
||||
scope.updateServerState(serverUpdate);
|
||||
expect(await driver.checkForUpdate()).toEqual(true);
|
||||
serverUpdate.assertSawRequestFor('/ngsw.json');
|
||||
serverUpdate.assertSawRequestFor('ngsw.json');
|
||||
serverUpdate.assertSawRequestFor('/foo.txt');
|
||||
serverUpdate.assertSawRequestFor('/redirected.txt');
|
||||
serverUpdate.assertNoOtherRequests();
|
||||
@ -263,7 +263,7 @@ export function main() {
|
||||
|
||||
scope.updateServerState(serverUpdate);
|
||||
expect(await driver.checkForUpdate()).toEqual(true);
|
||||
serverUpdate.assertSawRequestFor('/ngsw.json');
|
||||
serverUpdate.assertSawRequestFor('ngsw.json');
|
||||
serverUpdate.assertSawRequestFor('/foo.txt');
|
||||
serverUpdate.assertSawRequestFor('/redirected.txt');
|
||||
serverUpdate.assertNoOtherRequests();
|
||||
@ -331,7 +331,7 @@ export function main() {
|
||||
scope.advance(12000);
|
||||
await driver.idle.empty;
|
||||
|
||||
serverUpdate.assertSawRequestFor('/ngsw.json');
|
||||
serverUpdate.assertSawRequestFor('ngsw.json');
|
||||
serverUpdate.assertSawRequestFor('/foo.txt');
|
||||
serverUpdate.assertSawRequestFor('/redirected.txt');
|
||||
serverUpdate.assertNoOtherRequests();
|
||||
|
@ -73,7 +73,7 @@ export class 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;
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,14 @@
|
||||
/** @experimental */
|
||||
export declare class ServiceWorkerModule {
|
||||
static register(script: string, opts?: RegistrationOptions): ModuleWithProviders;
|
||||
static register(script: string, opts?: {
|
||||
scope?: string;
|
||||
enabled?: boolean;
|
||||
}): ModuleWithProviders;
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class SwPush {
|
||||
readonly isEnabled: boolean;
|
||||
readonly messages: Observable<object>;
|
||||
readonly subscription: Observable<PushSubscription | null>;
|
||||
constructor(sw: NgswCommChannel);
|
||||
@ -18,6 +22,7 @@ export declare class SwPush {
|
||||
export declare class SwUpdate {
|
||||
readonly activated: Observable<UpdateActivatedEvent>;
|
||||
readonly available: Observable<UpdateAvailableEvent>;
|
||||
readonly isEnabled: boolean;
|
||||
constructor(sw: NgswCommChannel);
|
||||
activateUpdate(): Promise<void>;
|
||||
checkForUpdate(): Promise<void>;
|
||||
|
@ -1,4 +1,4 @@
|
||||
g{
|
||||
{
|
||||
"maxLength": 120,
|
||||
"types": [
|
||||
"build",
|
||||
|
Reference in New Issue
Block a user