Skip to content

Commit

Permalink
feat(fis): Adding the admin.installations() API for deleting Firebase…
Browse files Browse the repository at this point in the history
… installation IDs (#1187)

* feat(fis): Added admin.installations() API

* fix: Marked IID APIs deprecated

* fix: Deprecated App.instanceId() method; Added more unit and integration tests

* fix: Throwing FirebaseInstallationsError from constructor

* fix: Some docs updates

* fix: Minor update to API doc comment

* fix: Added Installations class to API ref toc

* fix: Minor doc updates
  • Loading branch information
hiranya911 committed Jun 23, 2021
1 parent 9872b9b commit 0f9a7de
Show file tree
Hide file tree
Showing 20 changed files with 617 additions and 75 deletions.
6 changes: 6 additions & 0 deletions docgen/content-sources/node/toc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,12 @@ toc:
- title: "admin.firestore"
path: /docs/reference/admin/node/admin.firestore

- title: "admin.installations"
path: /docs/reference/admin/node/admin.installations
section:
- title: "Installations"
path: /docs/reference/admin/node/admin.installations.Installations-1

- title: "admin.instanceId"
path: /docs/reference/admin/node/admin.instanceId
section:
Expand Down
16 changes: 16 additions & 0 deletions etc/firebase-admin.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export namespace app {
// (undocumented)
firestore(): firestore.Firestore;
// (undocumented)
installations(): installations.Installations;
// @deprecated (undocumented)
instanceId(): instanceId.InstanceId;
// (undocumented)
machineLearning(): machineLearning.MachineLearning;
Expand Down Expand Up @@ -542,13 +544,27 @@ export interface GoogleOAuthAccessToken {
export function initializeApp(options?: AppOptions, name?: string): app.App;

// @public
export function installations(app?: app.App): installations.Installations;

// @public (undocumented)
export namespace installations {
export interface Installations {
// (undocumented)
app: app.App;
deleteInstallation(fid: string): Promise<void>;
}
}

// @public @deprecated
export function instanceId(app?: app.App): instanceId.InstanceId;

// @public (undocumented)
export namespace instanceId {
// @deprecated
export interface InstanceId {
// (undocumented)
app: app.App;
// @deprecated
deleteInstanceId(instanceId: string): Promise<void>;
}
}
Expand Down
15 changes: 15 additions & 0 deletions src/firebase-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +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 { Installations } from './installations/installations';
import { InstanceId } from './instance-id/instance-id';
import { ProjectManagement } from './project-management/project-management';
import { SecurityRules } from './security-rules/security-rules';
Expand Down Expand Up @@ -256,9 +257,23 @@ export class FirebaseApp implements app.App {
return service.client;
}

/**
* Returns the `Installations` service instance associated with this app.
*
* @return The `Installations` service instance of this app.
*/
public installations(): Installations {
return this.ensureService_('installations', () => {
const fisService: typeof Installations = require('./installations/installations').Installations;
return new fisService(this);
});
}

/**
* Returns the InstanceId service instance associated with this app.
*
* This API is deprecated. Use the `installations()` API instead.
*
* @return The InstanceId service instance of this app.
*/
public instanceId(): InstanceId {
Expand Down
3 changes: 3 additions & 0 deletions src/firebase-namespace-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { auth } from './auth/index';
import { credential } from './credential/index';
import { database } from './database/index';
import { firestore } from './firestore/index';
import { installations } from './installations/index';
import { instanceId } from './instance-id/index';
import { machineLearning } from './machine-learning/index';
import { messaging } from './messaging/index';
Expand Down Expand Up @@ -227,6 +228,8 @@ export namespace app {
auth(): auth.Auth;
database(url?: string): database.Database;
firestore(): firestore.Firestore;
installations(): installations.Installations;
/** @deprecated */
instanceId(): instanceId.InstanceId;
machineLearning(): machineLearning.MachineLearning;
messaging(): messaging.Messaging;
Expand Down
1 change: 1 addition & 0 deletions src/firebase-namespace.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export * from './app-check/index';
export * from './auth/index';
export * from './database/index';
export * from './firestore/index';
export * from './installations/index';
export * from './instance-id/index';
export * from './machine-learning/index';
export * from './messaging/index';
Expand Down
14 changes: 14 additions & 0 deletions src/firebase-namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { appCheck } from './app-check/index';
import { auth } from './auth/index';
import { database } from './database/index';
import { firestore } from './firestore/index';
import { installations } from './installations/index';
import { instanceId } from './instance-id/index';
import { machineLearning } from './machine-learning/index';
import { messaging } from './messaging/index';
Expand All @@ -43,6 +44,7 @@ import AppCheck = appCheck.AppCheck;
import Auth = auth.Auth;
import Database = database.Database;
import Firestore = firestore.Firestore;
import Installations = installations.Installations;
import InstanceId = instanceId.InstanceId;
import MachineLearning = machineLearning.MachineLearning;
import Messaging = messaging.Messaging;
Expand Down Expand Up @@ -311,6 +313,18 @@ export class FirebaseNamespace {
return Object.assign(fn, { MachineLearning: machineLearning });
}

/**
* Gets the `Installations` service namespace. The returned namespace can be used to get the
* `Installations` service for the default app or an explicitly specified app.
*/
get installations(): FirebaseServiceNamespace<Installations> {
const fn: FirebaseServiceNamespace<Installations> = (app?: App) => {
return this.ensureApp(app).installations();
};
const installations = require('./installations/installations').Installations;
return Object.assign(fn, { Installations: installations });
}

/**
* Gets the `InstanceId` service namespace. The returned namespace can be used to get the
* `Instance` service for the default app or an explicitly specified app.
Expand Down
85 changes: 85 additions & 0 deletions src/installations/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*!
* 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.
*/

import { app } from '../firebase-namespace-api';

/**
* Gets the {@link installations.Installations `Installations`} service for the
* default app or a given app.
*
* `admin.installations()` can be called with no arguments to access the default
* app's {@link installations.Installations `Installations`} service or as
* `admin.installations(app)` to access the
* {@link installations.Installations `Installations`} service associated with a
* specific app.
*
* @example
* ```javascript
* // Get the Installations service for the default app
* var defaultInstallations = admin.installations();
* ```
*
* @example
* ```javascript
* // Get the Installations service for a given app
* var otherInstallations = admin.installations(otherApp);
*```
*
* @param app Optional app whose `Installations` service to
* return. If not provided, the default `Installations` service is
* returned.
*
* @return The default `Installations` service if
* no app is provided or the `Installations` service associated with the
* provided app.
*/
export declare function installations(app?: app.App): installations.Installations;

/* eslint-disable @typescript-eslint/no-namespace */
export namespace installations {
/**
* Gets the {@link Installations `Installations`} service for the
* current app.
*
* @example
* ```javascript
* var installations = app.installations();
* // The above is shorthand for:
* // var installations = admin.installations(app);
* ```
*
* @return The `Installations` service for the
* current app.
*/
export interface Installations {
app: app.App;

/**
* Deletes the specified installation 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 installation ID does
* not delete Analytics data. See
* [Delete a Firebase installation](/docs/projects/manage-installations#delete-installation)
* for more information.
*
* @param fid The Firebase installation ID to be deleted.
*
* @return A promise fulfilled when the installation ID is deleted.
*/
deleteInstallation(fid: string): Promise<void>;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*!
* @license
* Copyright 2017 Google Inc.
* 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.
Expand All @@ -16,7 +16,7 @@
*/

import { FirebaseApp } from '../firebase-app';
import { FirebaseInstanceIdError, InstanceIdClientErrorCode } from '../utils/error';
import { FirebaseInstallationsError, InstallationsClientErrorCode } from '../utils/error';
import {
ApiSettings, AuthorizedHttpClient, HttpRequestConfig, HttpError,
} from '../utils/api-request';
Expand All @@ -33,50 +33,50 @@ const FIREBASE_IID_TIMEOUT = 10000;

/** HTTP error codes raised by the backend server. */
const ERROR_CODES: {[key: number]: string} = {
400: 'Malformed instance ID argument.',
400: 'Malformed installation ID argument.',
401: 'Request not authorized.',
403: 'Project does not match instance ID or the client does not have sufficient privileges.',
404: 'Failed to find the instance ID.',
403: 'Project does not match installation ID or the client does not have sufficient privileges.',
404: 'Failed to find the installation ID.',
409: 'Already deleted.',
429: 'Request throttled out by the backend server.',
500: 'Internal server error.',
503: 'Backend servers are over capacity. Try again later.',
};

/**
* Class that provides mechanism to send requests to the Firebase Instance ID backend endpoints.
* Class that provides mechanism to send requests to the FIS backend endpoints.
*/
export class FirebaseInstanceIdRequestHandler {
export class FirebaseInstallationsRequestHandler {

private readonly host: string = FIREBASE_IID_HOST;
private readonly timeout: number = FIREBASE_IID_TIMEOUT;
private readonly httpClient: AuthorizedHttpClient;
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);
}

public deleteInstanceId(instanceId: string): Promise<void> {
if (!validator.isNonEmptyString(instanceId)) {
return Promise.reject(new FirebaseInstanceIdError(
InstanceIdClientErrorCode.INVALID_INSTANCE_ID,
'Instance ID must be a non-empty string.',
public deleteInstallation(fid: string): Promise<void> {
if (!validator.isNonEmptyString(fid)) {
return Promise.reject(new FirebaseInstallationsError(
InstallationsClientErrorCode.INVALID_INSTALLATION_ID,
'Installation ID must be a non-empty string.',
));
}
return this.invokeRequestHandler(new ApiSettings(instanceId, 'DELETE'));
return this.invokeRequestHandler(new ApiSettings(fid, 'DELETE'));
}

/**
* Invokes the request handler based on the API settings object passed.
*
* @param {ApiSettings} apiSettings The API endpoint settings to apply to request and response.
* @return {Promise<void>} A promise that resolves when the request is complete.
* @param apiSettings The API endpoint settings to apply to request and response.
* @return A promise that resolves when the request is complete.
*/
private invokeRequestHandler(apiSettings: ApiSettings): Promise<void> {
return this.getPathPrefix()
Expand All @@ -98,8 +98,8 @@ export class FirebaseInstanceIdRequestHandler {
response.data.error : response.text;
const template: string = ERROR_CODES[response.status];
const message: string = template ?
`Instance ID "${apiSettings.getEndpoint()}": ${template}` : errorMessage;
throw new FirebaseInstanceIdError(InstanceIdClientErrorCode.API_ERROR, message);
`Installation ID "${apiSettings.getEndpoint()}": ${template}` : errorMessage;
throw new FirebaseInstallationsError(InstallationsClientErrorCode.API_ERROR, message);
}
// In case of timeouts and other network errors, the HttpClient returns a
// FirebaseError wrapped in the response. Simply throw it here.
Expand All @@ -116,9 +116,9 @@ export class FirebaseInstanceIdRequestHandler {
.then((projectId) => {
if (!validator.isNonEmptyString(projectId)) {
// Assert for an explicit projct ID (either via AppOptions or the cert itself).
throw new FirebaseInstanceIdError(
InstanceIdClientErrorCode.INVALID_PROJECT_ID,
'Failed to determine project ID for InstanceId. Initialize the '
throw new FirebaseInstallationsError(
InstallationsClientErrorCode.INVALID_PROJECT_ID,
'Failed to determine project ID for Installations. Initialize the '
+ 'SDK with service account credentials or set project ID as an app option. '
+ 'Alternatively set the GOOGLE_CLOUD_PROJECT environment variable.',
);
Expand Down

0 comments on commit 0f9a7de

Please sign in to comment.