diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index b2f107e95f..9005e91a07 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -498,15 +498,18 @@ export interface GoogleOAuthAccessToken { export function initializeApp(options?: AppOptions, name?: string): app.App; // @public -export function instanceId(app?: app.App): instanceId.InstanceId; +export class InstanceId { + get app(): App; + deleteInstanceId(instanceId: string): Promise; + } + +// @public +export function instanceId(app?: App): InstanceId; // @public (undocumented) export namespace instanceId { - export interface InstanceId { - // (undocumented) - app: app.App; - deleteInstanceId(instanceId: string): Promise; - } + // (undocumented) + export type InstanceId = InstanceId; } // @public diff --git a/gulpfile.js b/gulpfile.js index 1e1adcbba6..0adae29789 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -88,6 +88,7 @@ gulp.task('compile', function() { 'lib/firebase-namespace-api.d.ts', 'lib/core.d.ts', 'lib/app/*.d.ts', + 'lib/instance-id/*.d.ts', '!lib/utils/index.d.ts', ]; diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index 80f0fb2088..bcd8819f23 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -31,7 +31,7 @@ import { database } from '../database/index'; import { DatabaseService } from '../database/database-internal'; import { Firestore } from '@google-cloud/firestore'; import { FirestoreService } from '../firestore/firestore-internal'; -import { InstanceId } from '../instance-id/instance-id'; +import { InstanceId } from '../instance-id/index'; import { ProjectManagement } from '../project-management/project-management'; import { SecurityRules } from '../security-rules/security-rules'; import { RemoteConfig } from '../remote-config/remote-config'; @@ -231,6 +231,8 @@ export class FirebaseAppInternals { /** * Global context object for a collection of services using a shared authentication state. + * + * @internal */ export class FirebaseApp implements app.App { public INTERNAL: FirebaseAppInternals; @@ -333,10 +335,8 @@ export class FirebaseApp implements app.App { * @return The InstanceId service instance of this app. */ public instanceId(): InstanceId { - return this.ensureService_('iid', () => { - const iidService: typeof InstanceId = require('../instance-id/instance-id').InstanceId; - return new iidService(this); - }); + const fn = require('../instance-id/index').instanceId; + return fn(this); } /** @@ -410,6 +410,13 @@ export class FirebaseApp implements app.App { return deepCopy(this.options_); } + /** + * @internal + */ + public getOrInitService(name: string, init: (app: FirebaseApp) => T): T { + return this.ensureService_(name, () => init(this)); + } + /** * Deletes the FirebaseApp instance. * diff --git a/src/instance-id/index.ts b/src/instance-id/index.ts index 09cbfe4022..53b12bffcc 100644 --- a/src/instance-id/index.ts +++ b/src/instance-id/index.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { app } from '../firebase-namespace-api'; +import { FirebaseApp } from '../app/firebase-app'; +import { App, getApp } from '../app/index'; +import { InstanceId } from './instance-id'; + +export { InstanceId }; /** * Gets the {@link instanceId.InstanceId `InstanceId`} service for the @@ -46,40 +50,18 @@ import { app } from '../firebase-namespace-api'; * no app is provided or the `InstanceId` service associated with the * provided app. */ -export declare function instanceId(app?: app.App): instanceId.InstanceId; +export function instanceId(app?: App): InstanceId { + if (typeof app === 'undefined') { + app = getApp(); + } + + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('instanceId', (app) => new InstanceId(app)); +} + +import { InstanceId as TInstanceId } from './instance-id'; /* eslint-disable @typescript-eslint/no-namespace */ export namespace instanceId { - /** - * Gets the {@link InstanceId `InstanceId`} service for the - * current app. - * - * @example - * ```javascript - * var instanceId = app.instanceId(); - * // The above is shorthand for: - * // var instanceId = admin.instanceId(app); - * ``` - * - * @return The `InstanceId` service for the - * current app. - */ - export interface InstanceId { - app: app.App; - - /** - * Deletes the specified instance ID and the associated data from Firebase. - * - * Note that Google Analytics for Firebase uses its own form of Instance ID to - * keep track of analytics data. Therefore deleting a Firebase Instance ID does - * not delete Analytics data. See - * [Delete an Instance ID](/support/privacy/manage-iids#delete_an_instance_id) - * for more information. - * - * @param instanceId The instance ID to be deleted. - * - * @return A promise fulfilled when the instance ID is deleted. - */ - deleteInstanceId(instanceId: string): Promise; - } + export type InstanceId = TInstanceId; } diff --git a/src/instance-id/instance-id-request-internal.ts b/src/instance-id/instance-id-request-internal.ts index fbd3ba15ed..1ef9125744 100644 --- a/src/instance-id/instance-id-request-internal.ts +++ b/src/instance-id/instance-id-request-internal.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { App } from '../app/index'; import { FirebaseApp } from '../app/firebase-app'; import { FirebaseInstanceIdError, InstanceIdClientErrorCode } from '../utils/error'; import { @@ -54,12 +55,12 @@ export class FirebaseInstanceIdRequestHandler { private path: string; /** - * @param {FirebaseApp} app The app used to fetch access tokens to sign API requests. + * @param app The app used to fetch access tokens to sign API requests. * * @constructor */ - constructor(private readonly app: FirebaseApp) { - this.httpClient = new AuthorizedHttpClient(app); + constructor(private readonly app: App) { + this.httpClient = new AuthorizedHttpClient(app as FirebaseApp); } public deleteInstanceId(instanceId: string): Promise { diff --git a/src/instance-id/instance-id.ts b/src/instance-id/instance-id.ts index b92662bfd7..1af8bdb151 100644 --- a/src/instance-id/instance-id.ts +++ b/src/instance-id/instance-id.ts @@ -14,14 +14,11 @@ * limitations under the License. */ -import { FirebaseApp } from '../app/firebase-app'; +import { App } from '../app/index'; import { FirebaseInstanceIdError, InstanceIdClientErrorCode } from '../utils/error'; import { FirebaseInstanceIdRequestHandler } from './instance-id-request-internal'; -import { instanceId } from './index'; import * as validator from '../utils/validator'; -import InstanceIdInterface = instanceId.InstanceId; - /** * Gets the {@link InstanceId `InstanceId`} service for the * current app. @@ -36,20 +33,21 @@ import InstanceIdInterface = instanceId.InstanceId; * @return The `InstanceId` service for the * current app. */ -export class InstanceId implements InstanceIdInterface { +export class InstanceId { - private app_: FirebaseApp; + private app_: App; private requestHandler: FirebaseInstanceIdRequestHandler; /** - * @param {FirebaseApp} app The app for this InstanceId service. + * @param app The app for this InstanceId service. * @constructor + * @internal */ - constructor(app: FirebaseApp) { + constructor(app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseInstanceIdError( InstanceIdClientErrorCode.INVALID_ARGUMENT, - 'First argument passed to admin.instanceId() must be a valid Firebase app instance.', + 'First argument passed to instanceId() must be a valid Firebase app instance.', ); } @@ -80,9 +78,9 @@ export class InstanceId implements InstanceIdInterface { /** * Returns the app associated with this InstanceId instance. * - * @return {FirebaseApp} The app associated with this InstanceId instance. + * @return The app associated with this InstanceId instance. */ - get app(): FirebaseApp { + get app(): App { return this.app_; } } diff --git a/src/utils/index.ts b/src/utils/index.ts index b8cfa2faf0..834b8cb6cd 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { app as _app } from '../firebase-namespace-api'; +import { App } from '../app/index'; import { ServiceAccountCredential, ComputeEngineCredential } from '../credential/credential-internal'; @@ -23,6 +23,7 @@ import * as validator from './validator'; let sdkVersion: string; +// TODO: Move to firebase-admin/app as an internal member. export function getSdkVersion(): string { if (!sdkVersion) { const { version } = require('../../package.json'); // eslint-disable-line @typescript-eslint/no-var-requires @@ -76,7 +77,7 @@ export function addReadonlyGetter(obj: object, prop: string, value: any): void { * * @return A project ID string or null. */ -export function getExplicitProjectId(app: _app.App): string | null { +export function getExplicitProjectId(app: App): string | null { const options = app.options; if (validator.isNonEmptyString(options.projectId)) { return options.projectId; @@ -105,7 +106,7 @@ export function getExplicitProjectId(app: _app.App): string | null { * * @return A project ID string or null. */ -export function findProjectId(app: _app.App): Promise { +export function findProjectId(app: App): Promise { const projectId = getExplicitProjectId(app); if (projectId) { return Promise.resolve(projectId); diff --git a/test/unit/index.spec.ts b/test/unit/index.spec.ts index 887ccb5642..a7321f8cb0 100644 --- a/test/unit/index.spec.ts +++ b/test/unit/index.spec.ts @@ -60,6 +60,7 @@ import './storage/storage.spec'; import './firestore/firestore.spec'; // InstanceId +import './instance-id/index.spec'; import './instance-id/instance-id.spec'; import './instance-id/instance-id-request.spec'; diff --git a/test/unit/instance-id/index.spec.ts b/test/unit/instance-id/index.spec.ts new file mode 100644 index 0000000000..125d0ca6fa --- /dev/null +++ b/test/unit/instance-id/index.spec.ts @@ -0,0 +1,75 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { App } from '../../../src/app/index'; +import { instanceId, InstanceId } from '../../../src/instance-id/index'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('InstanceId', () => { + let mockApp: App; + let mockCredentialApp: App; + + const noProjectIdError = 'Failed to determine project ID for InstanceId. Initialize the SDK ' + + 'with service account credentials or set project ID as an app option. Alternatively set the ' + + 'GOOGLE_CLOUD_PROJECT environment variable.'; + + beforeEach(() => { + mockApp = mocks.app(); + mockCredentialApp = mocks.mockCredentialApp(); + }); + + describe('instanceId()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return instanceId(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should reject given an invalid credential without project ID', () => { + // Project ID not set in the environment. + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.GCLOUD_PROJECT; + const iid = instanceId(mockCredentialApp); + return iid.deleteInstanceId('iid') + .should.eventually.rejectedWith(noProjectIdError); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return instanceId(mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const iid1: InstanceId = instanceId(mockApp); + const iid2: InstanceId = instanceId(mockApp); + expect(iid1).to.equal(iid2); + }); + }); +}); diff --git a/test/unit/instance-id/instance-id.spec.ts b/test/unit/instance-id/instance-id.spec.ts index a2b2650df8..90bbacdb8d 100644 --- a/test/unit/instance-id/instance-id.spec.ts +++ b/test/unit/instance-id/instance-id.spec.ts @@ -83,7 +83,7 @@ describe('InstanceId', () => { expect(() => { const iidAny: any = InstanceId; return new iidAny(invalidApp); - }).to.throw('First argument passed to admin.instanceId() must be a valid Firebase app instance.'); + }).to.throw('First argument passed to instanceId() must be a valid Firebase app instance.'); }); }); @@ -91,7 +91,7 @@ describe('InstanceId', () => { expect(() => { const iidAny: any = InstanceId; return new iidAny(); - }).to.throw('First argument passed to admin.instanceId() must be a valid Firebase app instance.'); + }).to.throw('First argument passed to instanceId() must be a valid Firebase app instance.'); }); it('should reject given an invalid credential without project ID', () => {