From f24d8961b3b87821413297688803fc85113086b3 Mon Sep 17 00:00:00 2001 From: Feiyang Date: Tue, 6 Apr 2021 15:08:34 -0700 Subject: [PATCH] Auth explicit initialization (#4714) * Add explicit instantation mode * implement auth explicit initialization * Update packages/component/src/types.ts Co-authored-by: Yuchen Shi * address comments * fix typeing error * fix typings * fix typings * fix typings * Create chatty-cooks-drop.md * Create stale-zebras-hug.md Co-authored-by: Yuchen Shi --- .changeset/chatty-cooks-drop.md | 5 + .changeset/stale-zebras-hug.md | 7 ++ common/api-review/app-exp.api.md | 4 +- .../app-compat/src/firebaseNamespaceCore.ts | 6 +- packages-exp/app-compat/src/types.ts | 10 +- packages-exp/app-exp/src/internal.ts | 9 +- .../app-exp/src/platformLoggerService.ts | 8 +- .../auth-exp/src/core/auth/register.ts | 25 ++++- packages/app-types/private.d.ts | 10 +- packages/app/src/firebaseApp.ts | 2 +- packages/app/src/platformLoggerService.ts | 6 +- packages/component/src/component.ts | 10 +- packages/component/src/component_container.ts | 4 +- packages/component/src/provider.test.ts | 93 ++++++++++++++---- packages/component/src/provider.ts | 97 +++++++++++++++---- packages/component/src/types.ts | 11 ++- packages/rules-unit-testing/src/api/index.ts | 2 +- 17 files changed, 232 insertions(+), 77 deletions(-) create mode 100644 .changeset/chatty-cooks-drop.md create mode 100644 .changeset/stale-zebras-hug.md diff --git a/.changeset/chatty-cooks-drop.md b/.changeset/chatty-cooks-drop.md new file mode 100644 index 00000000000..be333c12686 --- /dev/null +++ b/.changeset/chatty-cooks-drop.md @@ -0,0 +1,5 @@ +--- +"@firebase/component": minor +--- + +Support new instantiation mode `EXPLICIT` diff --git a/.changeset/stale-zebras-hug.md b/.changeset/stale-zebras-hug.md new file mode 100644 index 00000000000..df604fddfad --- /dev/null +++ b/.changeset/stale-zebras-hug.md @@ -0,0 +1,7 @@ +--- +"@firebase/app-types": patch +"@firebase/app": patch +"@firebase/rules-unit-testing": patch +--- + +Internal typing changes diff --git a/common/api-review/app-exp.api.md b/common/api-review/app-exp.api.md index 64f4c52f8dc..5d0b46c5cb0 100644 --- a/common/api-review/app-exp.api.md +++ b/common/api-review/app-exp.api.md @@ -13,7 +13,7 @@ import { Name } from '@firebase/component'; import { Provider } from '@firebase/component'; // @internal (undocumented) -export function _addComponent(app: FirebaseApp, component: Component): void; +export function _addComponent(app: FirebaseApp, component: Component): void; // @internal (undocumented) export function _addOrOverwriteComponent(app: FirebaseApp, component: Component): void; @@ -102,7 +102,7 @@ export function initializeApp(options: FirebaseOptions, config?: FirebaseAppConf export function onLog(logCallback: LogCallback | null, options?: LogOptions): void; // @internal (undocumented) -export function _registerComponent(component: Component): boolean; +export function _registerComponent(component: Component): boolean; // @public export function registerVersion(libraryKeyOrName: string, version: string, variant?: string): void; diff --git a/packages-exp/app-compat/src/firebaseNamespaceCore.ts b/packages-exp/app-compat/src/firebaseNamespaceCore.ts index 09609b6bf4e..f16b4b5bb21 100644 --- a/packages-exp/app-compat/src/firebaseNamespaceCore.ts +++ b/packages-exp/app-compat/src/firebaseNamespaceCore.ts @@ -23,7 +23,7 @@ import { } from './types'; import * as modularAPIs from '@firebase/app-exp'; import { _FirebaseAppInternal as _FirebaseAppExp } from '@firebase/app-exp'; -import { Component, ComponentType } from '@firebase/component'; +import { Component, ComponentType, Name } from '@firebase/component'; import { deepExtend, contains } from '@firebase/util'; import { FirebaseAppImpl } from './firebaseApp'; @@ -131,8 +131,8 @@ export function createFirebaseNamespaceCore( return Object.keys(apps).map(name => apps[name]); } - function registerComponentCompat( - component: Component + function registerComponentCompat( + component: Component ): FirebaseServiceNamespace<_FirebaseService> | null { const componentName = component.name; const componentNameWithoutCompat = componentName.replace('-compat', ''); diff --git a/packages-exp/app-compat/src/types.ts b/packages-exp/app-compat/src/types.ts index a06988b73d0..85ec2b86c35 100644 --- a/packages-exp/app-compat/src/types.ts +++ b/packages-exp/app-compat/src/types.ts @@ -22,7 +22,7 @@ import { FirebaseApp, FirebaseNamespace } from './public-types'; import { Compat } from '@firebase/util'; -import { Component, ComponentContainer } from '@firebase/component'; +import { Component, ComponentContainer, Name } from '@firebase/component'; export interface FirebaseServiceInternals { /** @@ -50,8 +50,8 @@ export interface FirebaseServiceNamespace { // eslint-disable-next-line @typescript-eslint/naming-convention export interface _FirebaseApp extends FirebaseApp { container: ComponentContainer; - _addComponent(component: Component): void; - _addOrOverwriteComponent(component: Component): void; + _addComponent(component: Component): void; + _addOrOverwriteComponent(component: Component): void; _removeServiceInstance(name: string, instanceIdentifier?: string): void; } @@ -72,8 +72,8 @@ export interface _FirebaseNamespace extends FirebaseNamespace { * @param allowMultipleInstances Whether the registered service supports * multiple instances per app. If not specified, the default is false. */ - registerComponent( - component: Component + registerComponent( + component: Component ): FirebaseServiceNamespace<_FirebaseService> | null; /** diff --git a/packages-exp/app-exp/src/internal.ts b/packages-exp/app-exp/src/internal.ts index f2281ccb111..d653521d535 100644 --- a/packages-exp/app-exp/src/internal.ts +++ b/packages-exp/app-exp/src/internal.ts @@ -39,7 +39,10 @@ export const _components = new Map>(); * * @internal */ -export function _addComponent(app: FirebaseApp, component: Component): void { +export function _addComponent( + app: FirebaseApp, + component: Component +): void { try { (app as FirebaseAppImpl).container.addComponent(component); } catch (e) { @@ -68,7 +71,9 @@ export function _addOrOverwriteComponent( * * @internal */ -export function _registerComponent(component: Component): boolean { +export function _registerComponent( + component: Component +): boolean { const componentName = component.name; if (_components.has(componentName)) { logger.debug( diff --git a/packages-exp/app-exp/src/platformLoggerService.ts b/packages-exp/app-exp/src/platformLoggerService.ts index 70e3b937d2d..bcb5c3900c2 100644 --- a/packages-exp/app-exp/src/platformLoggerService.ts +++ b/packages-exp/app-exp/src/platformLoggerService.ts @@ -21,7 +21,7 @@ import { Provider, Name } from '@firebase/component'; -import { PlatformLoggerService } from './types'; +import { PlatformLoggerService, VersionService } from './types'; export class PlatformLoggerServiceImpl implements PlatformLoggerService { constructor(private readonly container: ComponentContainer) {} @@ -34,7 +34,7 @@ export class PlatformLoggerServiceImpl implements PlatformLoggerService { return providers .map(provider => { if (isVersionServiceProvider(provider)) { - const service = provider.getImmediate(); + const service = provider.getImmediate() as VersionService; return `${service.library}/${service.version}`; } else { return null; @@ -52,9 +52,7 @@ export class PlatformLoggerServiceImpl implements PlatformLoggerService { * provides VersionService. The provider is not necessarily a 'app-version' * provider. */ -function isVersionServiceProvider( - provider: Provider -): provider is Provider<'app-version'> { +function isVersionServiceProvider(provider: Provider): boolean { const component = provider.getComponent(); return component?.type === ComponentType.VERSION; } diff --git a/packages-exp/auth-exp/src/core/auth/register.ts b/packages-exp/auth-exp/src/core/auth/register.ts index 32dfa2e828b..11bbd9b5311 100644 --- a/packages-exp/auth-exp/src/core/auth/register.ts +++ b/packages-exp/auth-exp/src/core/auth/register.ts @@ -16,7 +16,11 @@ */ import { _registerComponent, registerVersion } from '@firebase/app-exp'; -import { Component, ComponentType } from '@firebase/component'; +import { + Component, + ComponentType, + InstantiationMode +} from '@firebase/component'; import { version } from '../../../package.json'; import { AuthErrorCode } from '../errors'; @@ -86,6 +90,23 @@ export function registerAuth(clientPlatform: ClientPlatform): void { }, ComponentType.PUBLIC ) + /** + * Auth can only be initialized by explicitly calling getAuth() or initializeAuth() + * For why we do this, See go/firebase-next-auth-init + */ + .setInstantiationMode(InstantiationMode.EXPLICIT) + /** + * Because all firebase products that depend on auth depend on auth-internal directly, + * we need to initialize auth-internal after auth is initialized to make it available to other firebase products. + */ + .setInstanceCreatedCallback( + (container, _instanceIdentifier, _instance) => { + const authInternalProvider = container.getProvider( + _ComponentName.AUTH_INTERNAL + ); + authInternalProvider.initialize(); + } + ) ); _registerComponent( @@ -98,7 +119,7 @@ export function registerAuth(clientPlatform: ClientPlatform): void { return (auth => new AuthInterop(auth))(auth); }, ComponentType.PRIVATE - ) + ).setInstantiationMode(InstantiationMode.EXPLICIT) ); registerVersion( diff --git a/packages/app-types/private.d.ts b/packages/app-types/private.d.ts index 1e21c2210e9..410eece1010 100644 --- a/packages/app-types/private.d.ts +++ b/packages/app-types/private.d.ts @@ -23,7 +23,7 @@ import { FirebaseApp, FirebaseNamespace } from '@firebase/app-types'; import { Observer, Subscribe } from '@firebase/util'; import { FirebaseError, ErrorFactory } from '@firebase/util'; -import { Component, ComponentContainer } from '@firebase/component'; +import { Component, ComponentContainer, Name } from '@firebase/component'; export interface FirebaseServiceInternals { /** @@ -86,8 +86,8 @@ export interface FirebaseAppInternals { export interface _FirebaseApp extends FirebaseApp { container: ComponentContainer; - _addComponent(component: Component): void; - _addOrOverwriteComponent(component: Component): void; + _addComponent(component: Component): void; + _addOrOverwriteComponent(component: Component): void; _removeServiceInstance(name: string, instanceIdentifier?: string): void; } export interface _FirebaseNamespace extends FirebaseNamespace { @@ -106,8 +106,8 @@ export interface _FirebaseNamespace extends FirebaseNamespace { * @param allowMultipleInstances Whether the registered service supports * multiple instances per app. If not specified, the default is false. */ - registerComponent( - component: Component + registerComponent( + component: Component ): FirebaseServiceNamespace | null; /** diff --git a/packages/app/src/firebaseApp.ts b/packages/app/src/firebaseApp.ts index 6fc62a4bc95..8f83d599890 100644 --- a/packages/app/src/firebaseApp.ts +++ b/packages/app/src/firebaseApp.ts @@ -148,7 +148,7 @@ export class FirebaseAppImpl implements FirebaseApp { /** * @param component the component being added to this app's container */ - _addComponent(component: Component): void { + _addComponent(component: Component): void { try { this.container.addComponent(component); } catch (e) { diff --git a/packages/app/src/platformLoggerService.ts b/packages/app/src/platformLoggerService.ts index 54048b714fb..4744f678776 100644 --- a/packages/app/src/platformLoggerService.ts +++ b/packages/app/src/platformLoggerService.ts @@ -33,7 +33,7 @@ export class PlatformLoggerService { return providers .map(provider => { if (isVersionServiceProvider(provider)) { - const service = provider.getImmediate(); + const service = (provider as Provider<'app-version'>).getImmediate(); return `${service.library}/${service.version}`; } else { return null; @@ -51,9 +51,7 @@ export class PlatformLoggerService { * provides VersionService. The provider is not necessarily a 'app-version' * provider. */ -function isVersionServiceProvider( - provider: Provider -): provider is Provider<'app-version'> { +function isVersionServiceProvider(provider: Provider): boolean { const component = provider.getComponent(); return component?.type === ComponentType.VERSION; } diff --git a/packages/component/src/component.ts b/packages/component/src/component.ts index 4758d5eff14..67b2ed56eb2 100644 --- a/packages/component/src/component.ts +++ b/packages/component/src/component.ts @@ -19,7 +19,8 @@ import { InstanceFactory, ComponentType, Dictionary, - Name + Name, + onInstanceCreatedCallback } from './types'; /** @@ -34,6 +35,8 @@ export class Component { instantiationMode = InstantiationMode.LAZY; + onInstanceCreated: onInstanceCreatedCallback | null = null; + /** * * @param name The public service name, e.g. app, auth, firestore, database @@ -60,4 +63,9 @@ export class Component { this.serviceProps = props; return this; } + + setInstanceCreatedCallback(callback: onInstanceCreatedCallback): this { + this.onInstanceCreated = callback; + return this; + } } diff --git a/packages/component/src/component_container.ts b/packages/component/src/component_container.ts index 287da6a1d11..8cd36ee3560 100644 --- a/packages/component/src/component_container.ts +++ b/packages/component/src/component_container.ts @@ -66,12 +66,12 @@ export class ComponentContainer { */ getProvider(name: T): Provider { if (this.providers.has(name)) { - return this.providers.get(name) as Provider; + return (this.providers.get(name) as unknown) as Provider; } // create a Provider for a service that hasn't registered with Firebase const provider = new Provider(name, this); - this.providers.set(name, provider); + this.providers.set(name, (provider as unknown) as Provider); return provider as Provider; } diff --git a/packages/component/src/provider.test.ts b/packages/component/src/provider.test.ts index c9bc3b1976c..49acaf7cbd1 100644 --- a/packages/component/src/provider.test.ts +++ b/packages/component/src/provider.test.ts @@ -93,8 +93,7 @@ describe('Provider', () => { it('does not throw if instance factory throws when registering a component with a pending promise', () => { // create a pending promise - // eslint-disable-next-line @typescript-eslint/no-floating-promises - provider.get(); + void provider.get(); const component = getFakeComponent('test', () => { throw Error('something went wrong!'); }); @@ -127,17 +126,34 @@ describe('Provider', () => { expect((instance as any).options).to.deep.equal(options); }); + + it('resolve pending promises created by Provider.get() with the same identifier', () => { + provider.setComponent( + getFakeComponent( + 'test', + () => ({ test: true }), + false, + InstantiationMode.EXPLICIT + ) + ); + const servicePromise = provider.get(); + expect((provider as any).instances.size).to.equal(0); + + provider.initialize(); + expect((provider as any).instances.size).to.equal(1); + return expect(servicePromise).to.eventually.deep.equal({ test: true }); + }); }); describe('Provider (multipleInstances = false)', () => { describe('getImmediate()', () => { - it('throws if the service is not available', () => { + it('throws if component has not been registered', () => { expect(provider.getImmediate.bind(provider)).to.throw( 'Service test is not available' ); }); - it('returns null if the service is not available with optional flag', () => { + it('returns null with the optional flag set if component has not been registered ', () => { expect(provider.getImmediate({ optional: true })).to.equal(null); }); @@ -186,8 +202,7 @@ describe('Provider', () => { describe('setComponent()', () => { it('instantiates the service if there is a pending promise and the service is eager', () => { // create a pending promise - // eslint-disable-next-line @typescript-eslint/no-floating-promises - provider.get(); + void provider.get(); provider.setComponent( getFakeComponent('test', () => ({}), false, InstantiationMode.EAGER) @@ -197,8 +212,7 @@ describe('Provider', () => { it('instantiates the service if there is a pending promise and the service is NOT eager', () => { // create a pending promise - // eslint-disable-next-line @typescript-eslint/no-floating-promises - provider.get(); + void provider.get(); provider.setComponent(getFakeComponent('test', () => ({}))); expect((provider as any).instances.size).to.equal(1); @@ -251,8 +265,7 @@ describe('Provider', () => { ) ); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - provider.delete(); + void provider.delete(); expect(deleteFake).to.have.been.called; }); @@ -274,8 +287,7 @@ describe('Provider', () => { ) ); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - provider.delete(); + void provider.delete(); expect(deleteFake).to.have.been.called; }); @@ -350,12 +362,10 @@ describe('Provider', () => { describe('setComponent()', () => { it('instantiates services for the pending promises for all instance identifiers', async () => { - /* eslint-disable @typescript-eslint/no-floating-promises */ // create 3 promises for 3 different identifiers - provider.get(); - provider.get('name1'); - provider.get('name2'); - /* eslint-enable @typescript-eslint/no-floating-promises */ + void provider.get(); + void provider.get('name1'); + void provider.get('name2'); provider.setComponent( getFakeComponent('test', () => ({ test: true }), true) @@ -378,8 +388,7 @@ describe('Provider', () => { it(`instantiates the default serviec if there are pending promises for other identifiers but not for the default identifer and the service is eager`, () => { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - provider.get('name1'); + void provider.get('name1'); provider.setComponent( getFakeComponent( 'test', @@ -415,8 +424,7 @@ describe('Provider', () => { provider.getImmediate({ identifier: 'instance1' }); provider.getImmediate({ identifier: 'instance2' }); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - provider.delete(); + void provider.delete(); expect(deleteFakes.length).to.equal(2); for (const f of deleteFakes) { @@ -478,4 +486,47 @@ describe('Provider', () => { }); }); }); + + describe('InstantiationMode: EXPLICIT', () => { + it('setComponent() does NOT auto-initialize the service', () => { + // create a pending promise which should trigger initialization if instantiationMode is non-EXPLICIT + void provider.get(); + + provider.setComponent( + getFakeComponent('test', () => ({}), false, InstantiationMode.EXPLICIT) + ); + expect((provider as any).instances.size).to.equal(0); + }); + + it('get() does NOT auto-initialize the service', () => { + provider.setComponent( + getFakeComponent('test', () => ({}), false, InstantiationMode.EXPLICIT) + ); + expect((provider as any).instances.size).to.equal(0); + void provider.get(); + expect((provider as any).instances.size).to.equal(0); + }); + + it('getImmediate() does NOT auto-initialize the service and throws if the service has not been initialized', () => { + provider.setComponent( + getFakeComponent('test', () => ({}), false, InstantiationMode.EXPLICIT) + ); + expect((provider as any).instances.size).to.equal(0); + + expect(() => provider.getImmediate()).to.throw( + 'Service test is not available' + ); + expect((provider as any).instances.size).to.equal(0); + }); + + it('getImmediate() does NOT auto-initialize the service and returns null if the optional flag is set', () => { + provider.setComponent( + getFakeComponent('test', () => ({}), false, InstantiationMode.EXPLICIT) + ); + expect((provider as any).instances.size).to.equal(0); + + expect(provider.getImmediate({ optional: true })).to.equal(null); + expect((provider as any).instances.size).to.equal(0); + }); + }); }); diff --git a/packages/component/src/provider.ts b/packages/component/src/provider.ts index 43a32746233..e591267a032 100644 --- a/packages/component/src/provider.ts +++ b/packages/component/src/provider.ts @@ -54,17 +54,23 @@ export class Provider { if (!this.instancesDeferred.has(normalizedIdentifier)) { const deferred = new Deferred(); this.instancesDeferred.set(normalizedIdentifier, deferred); - // If the service instance is available, resolve the promise with it immediately - try { - const instance = this.getOrInitializeService({ - instanceIdentifier: normalizedIdentifier - }); - if (instance) { - deferred.resolve(instance); + + if ( + this.isInitialized(normalizedIdentifier) || + this.shouldAutoInitialize() + ) { + // initialize the service if it can be auto-initialized + try { + const instance = this.getOrInitializeService({ + instanceIdentifier: normalizedIdentifier + }); + if (instance) { + deferred.resolve(instance); + } + } catch (e) { + // when the instance factory throws an exception during get(), it should not cause + // a fatal error. We just return the unresolved promise in this case. } - } catch (e) { - // when the instance factory throws an exception during get(), it should not cause - // a fatal error. We just return the unresolved promise in this case. } } @@ -98,23 +104,28 @@ export class Provider { }; // if multipleInstances is not supported, use the default name const normalizedIdentifier = this.normalizeInstanceIdentifier(identifier); - try { - const instance = this.getOrInitializeService({ - instanceIdentifier: normalizedIdentifier - }); - if (!instance) { + if ( + this.isInitialized(normalizedIdentifier) || + this.shouldAutoInitialize() + ) { + try { + return this.getOrInitializeService({ + instanceIdentifier: normalizedIdentifier + }); + } catch (e) { if (optional) { return null; + } else { + throw e; } - throw Error(`Service ${this.name} is not available`); } - return instance; - } catch (e) { + } else { + // In case a component is not initialized and should/can not be auto-initialized at the moment, return null if the optional flag is set, or throw if (optional) { return null; } else { - throw e; + throw Error(`Service ${this.name} is not available`); } } } @@ -135,6 +146,12 @@ export class Provider { } this.component = component; + + // return early without attempting to initialize the component if the component requires explicit initialization (calling `Provider.initialize()`) + if (!this.shouldAutoInitialize()) { + return; + } + // if the service is eager, initialize the default instance if (isComponentEager(component)) { try { @@ -216,10 +233,24 @@ export class Provider { throw Error(`Component ${this.name} has not been registered yet`); } - return this.getOrInitializeService({ + const instance = this.getOrInitializeService({ instanceIdentifier: normalizedIdentifier, options })!; + + // resolve any pending promise waiting for the service instance + for (const [ + instanceIdentifier, + instanceDeferred + ] of this.instancesDeferred.entries()) { + const normalizedDeferredIdentifier = this.normalizeInstanceIdentifier( + instanceIdentifier + ); + if (normalizedIdentifier === normalizedDeferredIdentifier) { + instanceDeferred.resolve(instance); + } + } + return instance; } private getOrInitializeService({ @@ -236,6 +267,23 @@ export class Provider { options }); this.instances.set(instanceIdentifier, instance); + + /** + * Order is important + * onInstanceCreated() should be called after this.instances.set(instanceIdentifier, instance); which + * makes `isInitialized()` return true. + */ + if (this.component.onInstanceCreated) { + try { + this.component.onInstanceCreated( + this.container, + instanceIdentifier, + instance + ); + } catch { + // ignore errors in the onInstanceCreatedCallback + } + } } return instance || null; @@ -248,6 +296,13 @@ export class Provider { return identifier; // assume multiple instances are supported before the component is provided. } } + + private shouldAutoInitialize(): boolean { + return ( + !!this.component && + this.component.instantiationMode !== InstantiationMode.EXPLICIT + ); + } } // undefined should be passed to the service factory for the default instance @@ -255,6 +310,6 @@ function normalizeIdentifierForFactory(identifier: string): string | undefined { return identifier === DEFAULT_ENTRY_NAME ? undefined : identifier; } -function isComponentEager(component: Component): boolean { +function isComponentEager(component: Component): boolean { return component.instantiationMode === InstantiationMode.EAGER; } diff --git a/packages/component/src/types.ts b/packages/component/src/types.ts index d0a94349d30..eb5c26fb6da 100644 --- a/packages/component/src/types.ts +++ b/packages/component/src/types.ts @@ -18,8 +18,9 @@ import { ComponentContainer } from './component_container'; export const enum InstantiationMode { - LAZY = 'LAZY', // Currently all components are LAZY in JS SDK - EAGER = 'EAGER' + LAZY = 'LAZY', // Currently most components are LAZY in JS SDK + EAGER = 'EAGER', // EAGER components are initialized immediately upon registration + EXPLICIT = 'EXPLICIT' // component needs to be initialized explicitly by calling Provider.initialize() } /** @@ -56,6 +57,12 @@ export type InstanceFactory = ( options: InstanceFactoryOptions ) => NameServiceMapping[T]; +export type onInstanceCreatedCallback = ( + container: ComponentContainer, + instanceIdentifier: string, + instance: NameServiceMapping[T] +) => void; + export interface Dictionary { [key: string]: unknown; } diff --git a/packages/rules-unit-testing/src/api/index.ts b/packages/rules-unit-testing/src/api/index.ts index f5138d52505..818505f5c0a 100644 --- a/packages/rules-unit-testing/src/api/index.ts +++ b/packages/rules-unit-testing/src/api/index.ts @@ -583,7 +583,7 @@ export function assertFails(pr: Promise): any { const isPermissionDenied = errCode === 'permission-denied' || errCode === 'permission_denied' || - errMessage.indexOf('permission_denied') >= 0 || + errMessage.indexOf('permission_denied') >= 0 || errMessage.indexOf('permission denied') >= 0; if (!isPermissionDenied) {