Skip to content
1 change: 1 addition & 0 deletions src/auth/credential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ export class CertCredential implements Credential {
private createAuthJwt_(): string {
const claims = {
scope: [
'https://www.googleapis.com/auth/cloud-platform',
'https://www.googleapis.com/auth/firebase.database',
'https://www.googleapis.com/auth/firebase.messaging',
'https://www.googleapis.com/auth/identitytoolkit',
Expand Down
12 changes: 12 additions & 0 deletions src/firebase-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {Database} from '@firebase/database';
import {DatabaseService} from './database/database';
import {Firestore} from '@google-cloud/firestore';
import {FirestoreService} from './firestore/firestore';
import {InstanceId} from './instance-id/instance-id';

/**
* Type representing a callback which is called every time an app lifecycle event occurs.
Expand Down Expand Up @@ -333,6 +334,17 @@ export class FirebaseApp {
return service.client;
}

/**
* Returns the InstanceId service instance associated with this app.
*
* @return {InstanceId} The InstanceId service instance of this app.
*/
public instanceId(): InstanceId {
return this.ensureService_('iid', () => {
return new InstanceId(this);
});
}

/**
* Returns the name of the FirebaseApp instance.
*
Expand Down
13 changes: 13 additions & 0 deletions src/firebase-namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {Messaging} from './messaging/messaging';
import {Storage} from './storage/storage';
import {Database} from '@firebase/database';
import {Firestore} from '@google-cloud/firestore';
import {InstanceId} from './instance-id/instance-id';

const DEFAULT_APP_NAME = '[DEFAULT]';

Expand Down Expand Up @@ -338,6 +339,18 @@ export class FirebaseNamespace {
return Object.assign(fn, require('@google-cloud/firestore'));
}

/**
* 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.
*/
get instanceId(): FirebaseServiceNamespace<InstanceId> {
const ns: FirebaseNamespace = this;
let fn: FirebaseServiceNamespace<InstanceId> = (app?: FirebaseApp) => {
return ns.ensureApp(app).instanceId();
};
return Object.assign(fn, {InstanceId});
}

/**
* Initializes the FirebaseApp instance.
*
Expand Down
10 changes: 10 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ declare namespace admin {
function messaging(app?: admin.app.App): admin.messaging.Messaging;
function storage(app?: admin.app.App): admin.storage.Storage;
function firestore(app?: admin.app.App): admin.firestore.Firestore;
function instanceId(app?: admin.app.App): admin.instanceId.InstanceId;
function initializeApp(options: admin.AppOptions, name?: string): admin.app.App;
}

Expand All @@ -69,6 +70,7 @@ declare namespace admin.app {
auth(): admin.auth.Auth;
database(url?: string): admin.database.Database;
firestore(): admin.firestore.Firestore;
instanceId(): admin.instanceId.InstanceId;
messaging(): admin.messaging.Messaging;
storage(): admin.storage.Storage;
delete(): Promise<void>;
Expand Down Expand Up @@ -415,6 +417,14 @@ declare namespace admin.firestore {
export import setLogFunction = _firestore.setLogFunction;
}

declare namespace admin.instanceId {
interface InstanceId {
app: admin.app.App;

deleteInstanceId(instanceId: string): Promise<void>;
}
}

declare module 'firebase-admin' {
}

Expand Down
111 changes: 111 additions & 0 deletions src/instance-id/instance-id-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*!
* Copyright 2017 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 {FirebaseApp} from '../firebase-app';
import {FirebaseError, FirebaseInstanceIdError, InstanceIdClientErrorCode} from '../utils/error';
import {
HttpMethod, SignedApiRequestHandler, ApiSettings,
} from '../utils/api-request';

import * as validator from '../utils/validator';

/** Firebase IID backend host. */
const FIREBASE_IID_HOST = 'console.firebase.google.com';
/** Firebase IID backend port number. */
const FIREBASE_IID_PORT = 443;
/** Firebase IID backend path. */
const FIREBASE_IID_PATH = '/v1/';
/** Firebase IID request timeout duration in milliseconds. */
const FIREBASE_IID_TIMEOUT = 10000;

/**
* Class that provides mechanism to send requests to the Firebase Auth backend endpoints.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update comment: Firebase IID backend endpoints.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

*/
export class FirebaseInstanceIdRequestHandler {

private host: string = FIREBASE_IID_HOST;
private port: number = FIREBASE_IID_PORT;
private timeout: number = FIREBASE_IID_TIMEOUT;
private signedApiRequestHandler: SignedApiRequestHandler;
private path: string;

/**
* @param {FirebaseApp} app The app used to fetch access tokens to sign API requests.
* @param {string} projectId A Firebase project ID string.
*
* @constructor
*/
constructor(app: FirebaseApp, projectId: string) {
this.signedApiRequestHandler = new SignedApiRequestHandler(app);
this.path = FIREBASE_IID_PATH + `project/${projectId}/instanceId/`;
}

public deleteInstanceId(instanceId: string): Promise<Object> {
if (!validator.isNonEmptyString(instanceId)) {
return Promise.reject(new FirebaseInstanceIdError(
InstanceIdClientErrorCode.INVALID_INSTANCE_ID,
'Instance ID must be a non-empty string.'
));
}
return this.invokeRequestHandler(new ApiSettings(instanceId, '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.
* @param {Object} requestData The request data.
* @return {Promise<Object>} A promise that resolves with the response.
*/
private invokeRequestHandler(apiSettings: ApiSettings): Promise<Object> {
let path: string = this.path + apiSettings.getEndpoint();
let httpMethod: HttpMethod = apiSettings.getHttpMethod();
return Promise.resolve()
.then(() => {
return this.signedApiRequestHandler.sendRequest(
this.host, this.port, path, httpMethod, undefined, undefined, this.timeout);
})
.then((response) => {
return response;
})
.catch((response) => {
let error;
if (typeof response === 'object' && 'error' in response) {
error = response.error;
} else {
error = response;
}

if (error instanceof FirebaseError) {
// In case of timeouts and other network errors, the API request handler returns a
// FirebaseError wrapped in the response. Simply throw it here.
throw error;
}

let message: string;
if (response.statusCode === 404) {
message = 'Failed to find the instance ID: ' + apiSettings.getEndpoint();
} else if (response.statusCode === 429) {
message = 'Request throttled out by the backend server';
} else if (response.statusCode === 500) {
message = 'Internal server error';
} else {
message = JSON.stringify(error);
}
throw new FirebaseInstanceIdError(InstanceIdClientErrorCode.API_ERROR, message);
});
}
}
96 changes: 96 additions & 0 deletions src/instance-id/instance-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*!
* Copyright 2017 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 {FirebaseApp} from '../firebase-app';
import {FirebaseInstanceIdError, InstanceIdClientErrorCode} from '../utils/error';
import {FirebaseServiceInterface, FirebaseServiceInternalsInterface} from '../firebase-service';
import {FirebaseInstanceIdRequestHandler} from './instance-id-request';

import * as utils from '../utils/index';
import * as validator from '../utils/validator';

/**
* Internals of an InstanceId service instance.
*/
class InstanceIdInternals implements FirebaseServiceInternalsInterface {
/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update the indentation to be consistent. We use 2 spaces in this case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

* Deletes the service and its associated resources.
*
* @return {Promise<()>} An empty Promise that will be fulfilled when the service is deleted.
*/
public delete(): Promise<void> {
// There are no resources to clean up
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix indentation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

return Promise.resolve(undefined);
}
}

export class InstanceId implements FirebaseServiceInterface {
public INTERNAL: InstanceIdInternals = new InstanceIdInternals();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation needs to be fixed here too and the rest of the class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


private app_: FirebaseApp;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's stick with no trailing _ for private variables. This is also the recommended style for ts.

Copy link
Contributor Author

@hiranya911 hiranya911 Dec 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is going to conflict with the get app() getter. Is it better to remove that, and expose app as a public attribute of this class? But I guess that would make app writable, which we don't want.

private requestHandler: FirebaseInstanceIdRequestHandler;

/**
* @param {Object} app The app for this Auth service.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update comment: instance ID service?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

* @constructor
*/
constructor(app: FirebaseApp) {
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.'
);
}

const projectId: string = utils.getProjectId(app);
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 '
+ 'SDK with service account credentials or set project ID as an app option. '
+ 'Alternatively set the GCLOUD_PROJECT environment variable.',
);
}

this.app_ = app;
this.requestHandler = new FirebaseInstanceIdRequestHandler(app, projectId);
}

/**
* Deletes the specified instance ID from Firebase. This can be used to delete an instance ID
* and associated user data from a Firebase project, pursuant to the General Data Protection
* Regulation (GDPR).
*
* @param {string} instanceId The instance ID to be deleted
* @return {Promise<void>} A promise that resolves when the instance ID is successfully deleted.
*/
public deleteInstanceId(instanceId: string): Promise<void> {
return this.requestHandler.deleteInstanceId(instanceId)
.then((result) => {
// Return nothing on success
});
}

/**
* Returns the app associated with this InstanceId instance.
*
* @return {FirebaseApp} The app associated with this InstanceId instance.
*/
get app(): FirebaseApp {
return this.app_;
}
}
15 changes: 10 additions & 5 deletions src/utils/api-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {OutgoingHttpHeaders} from 'http';
import https = require('https');

/** Http method type definition. */
export type HttpMethod = 'GET' | 'POST';
export type HttpMethod = 'GET' | 'POST' | 'DELETE';
/** API callback function type definition. */
export type ApiCallbackFunction = (data: Object) => void;

Expand Down Expand Up @@ -224,11 +224,16 @@ export class SignedApiRequestHandler extends HttpRequestHandler {
port: number,
path: string,
httpMethod: HttpMethod,
data: Object,
headers: Object,
timeout: number): Promise<Object> {
data?: Object,
headers?: Object,
timeout?: number): Promise<Object> {
return this.app_.INTERNAL.getToken().then((accessTokenObj) => {
let headersCopy: Object = deepCopy(headers);
let headersCopy: Object;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: You can also do:
let headersCopy: Object = (headers && deepCopy(headers)) || {};

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much cleaner. Thanks.

if (typeof headers !== 'undefined') {
headersCopy = deepCopy(headers);
} else {
headersCopy = {};
}
let authorizationHeaderKey = 'Authorization';
headersCopy[authorizationHeaderKey] = 'Bearer ' + accessTokenObj.accessToken;
return super.sendRequest(host, port, path, httpMethod, data, headersCopy, timeout);
Expand Down
34 changes: 34 additions & 0 deletions src/utils/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,21 @@ export class FirebaseFirestoreError extends FirebaseError {
}
}

/**
* Firebase instance ID error code structure. This extends FirebaseError.
*
* @param {ErrorInfo} info The error code info.
* @param {string} [message] The error message. This will override the default
* message if provided.
* @constructor
*/
export class FirebaseInstanceIdError extends FirebaseError {
constructor(info: ErrorInfo, message?: string) {
// Override default message if custom message provided.
super({code: 'instance-id/' + info.code, message: message || info.message});
}
}


/**
* Firebase Messaging error code structure. This extends PrefixedFirebaseError.
Expand Down Expand Up @@ -472,6 +487,25 @@ export class MessagingClientErrorCode {
};
};

export class InstanceIdClientErrorCode {
public static INVALID_ARGUMENT = {
code: 'invalid-argument',
message: 'Invalid argument provided.',
};
public static INVALID_PROJECT_ID = {
code: 'invalid-project-id',
message: 'Invalid project ID provided.',
};
public static INVALID_INSTANCE_ID = {
code: 'invalid-instance-id',
message: 'Invalid instance ID provided.',
};
public static API_ERROR = {
code: 'api-error',
message: 'Instance ID API call failed.',
};
}

/** @const {ServerToClientCode} Auth server to client enum error codes. */
const AUTH_SERVER_TO_CLIENT_CODE: ServerToClientCode = {
// Claims payload is too large.
Expand Down
2 changes: 2 additions & 0 deletions test/integration/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ var utils = require('./utils');
var app = require('./app');
var auth = require('./auth');
var database = require('./database');
var instanceId = require('./instance-id');
var messaging = require('./messaging');
var storage = require('./storage');
var firestore = require('./firestore');
Expand Down Expand Up @@ -239,6 +240,7 @@ return promptForUpdateRules(flags['overwrite'])
.then(_.partial(app.test, utils))
.then(_.partial(auth.test, utils))
.then(_.partial(database.test, utils))
.then(_.partial(instanceId.test, utils))
.then(_.partial(messaging.test, utils))
.then(_.partial(storage.test, utils))
.then(_.partial(firestore.test, utils))
Expand Down
Loading