Skip to content

Commit 018b7c5

Browse files
authored
Add new Firebase project management service. (#378)
Add app-scoped actions for Firebase apps within Firebase projects. Uses Firebase Management REST APIs (https://firebase.google.com/docs/projects/api/reference/rest/).
1 parent a69424b commit 018b7c5

19 files changed

+2647
-4
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
- [changed] Upgraded Cloud Firestore client to v0.18.0.
1919
- [added] Exposed the `CollectionReference`, `WriteBatch`, `WriteResult` and
2020
`QueryDocumentSnapshot` types from the `admin.firestore` namespace.
21+
- [added] A new `ProjectManagement` service, which includes the ability to
22+
create, list, and get details about Android and iOS apps associated with your
23+
Firebase Project.
2124

2225
# v6.0.0
2326

src/firebase-app.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {DatabaseService} from './database/database';
2929
import {Firestore} from '@google-cloud/firestore';
3030
import {FirestoreService} from './firestore/firestore';
3131
import {InstanceId} from './instance-id/instance-id';
32+
import {ProjectManagement} from './project-management/project-management';
3233

3334
/**
3435
* Type representing a callback which is called every time an app lifecycle event occurs.
@@ -349,10 +350,23 @@ export class FirebaseApp {
349350
});
350351
}
351352

353+
/**
354+
* Returns the ProjectManagement service instance associated with this app.
355+
*
356+
* @return {ProjectManagement} The ProjectManagement service instance of this app.
357+
*/
358+
public projectManagement(): ProjectManagement {
359+
return this.ensureService_('project-management', () => {
360+
const projectManagementService: typeof ProjectManagement =
361+
require('./project-management/project-management').ProjectManagement;
362+
return new projectManagementService(this);
363+
});
364+
}
365+
352366
/**
353367
* Returns the name of the FirebaseApp instance.
354368
*
355-
* @returns {string} The name of the FirebaseApp instance.
369+
* @return {string} The name of the FirebaseApp instance.
356370
*/
357371
get name(): string {
358372
this.checkDestroyed_();
@@ -362,7 +376,7 @@ export class FirebaseApp {
362376
/**
363377
* Returns the options for the FirebaseApp instance.
364378
*
365-
* @returns {FirebaseAppOptions} The options for the FirebaseApp instance.
379+
* @return {FirebaseAppOptions} The options for the FirebaseApp instance.
366380
*/
367381
get options(): FirebaseAppOptions {
368382
this.checkDestroyed_();
@@ -372,7 +386,7 @@ export class FirebaseApp {
372386
/**
373387
* Deletes the FirebaseApp instance.
374388
*
375-
* @returns {Promise<void>} An empty Promise fulfilled once the FirebaseApp instance is deleted.
389+
* @return {Promise<void>} An empty Promise fulfilled once the FirebaseApp instance is deleted.
376390
*/
377391
public delete(): Promise<void> {
378392
this.checkDestroyed_();

src/firebase-namespace.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {Storage} from './storage/storage';
3232
import {Database} from '@firebase/database';
3333
import {Firestore} from '@google-cloud/firestore';
3434
import {InstanceId} from './instance-id/instance-id';
35+
import {ProjectManagement} from './project-management/project-management';
3536

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

@@ -387,6 +388,18 @@ export class FirebaseNamespace {
387388
return Object.assign(fn, {InstanceId: instanceId});
388389
}
389390

391+
/**
392+
* Gets the `ProjectManagement` service namespace. The returned namespace can be used to get the
393+
* `ProjectManagement` service for the default app or an explicitly specified app.
394+
*/
395+
get projectManagement(): FirebaseServiceNamespace<ProjectManagement> {
396+
const fn: FirebaseServiceNamespace<ProjectManagement> = (app?: FirebaseApp) => {
397+
return this.ensureApp(app).projectManagement();
398+
};
399+
const projectManagement = require('./project-management/project-management').ProjectManagement;
400+
return Object.assign(fn, {ProjectManagement: projectManagement});
401+
}
402+
390403
/**
391404
* Initializes the FirebaseApp instance.
392405
*

src/index.d.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ declare namespace admin {
6161
function storage(app?: admin.app.App): admin.storage.Storage;
6262
function firestore(app?: admin.app.App): admin.firestore.Firestore;
6363
function instanceId(app?: admin.app.App): admin.instanceId.InstanceId;
64+
function projectManagement(app?: admin.app.App): admin.projectManagement.ProjectManagement;
6465
function initializeApp(options?: admin.AppOptions, name?: string): admin.app.App;
6566
}
6667

@@ -74,6 +75,7 @@ declare namespace admin.app {
7475
firestore(): admin.firestore.Firestore;
7576
instanceId(): admin.instanceId.InstanceId;
7677
messaging(): admin.messaging.Messaging;
78+
projectManagement(): admin.projectManagement.ProjectManagement;
7779
storage(): admin.storage.Storage;
7880
delete(): Promise<void>;
7981
}
@@ -650,6 +652,61 @@ declare namespace admin.instanceId {
650652
}
651653
}
652654

655+
declare namespace admin.projectManagement {
656+
interface ShaCertificate {
657+
certType: ('sha1' | 'sha256');
658+
shaHash: string;
659+
resourceName?: string;
660+
}
661+
662+
interface AndroidAppMetadata {
663+
resourceName: string;
664+
appId: string;
665+
displayName: string | null;
666+
projectId: string;
667+
packageName: string;
668+
}
669+
670+
interface AndroidApp {
671+
appId: string;
672+
673+
getMetadata(): Promise<admin.projectManagement.AndroidAppMetadata>;
674+
setDisplayName(newDisplayName: string): Promise<void>;
675+
getShaCertificates(): Promise<admin.projectManagement.ShaCertificate[]>;
676+
addShaCertificate(certificateToAdd: ShaCertificate): Promise<void>;
677+
deleteShaCertificate(certificateToRemove: ShaCertificate): Promise<void>;
678+
getConfig(): Promise<string>;
679+
}
680+
681+
interface IosAppMetadata {
682+
resourceName: string;
683+
appId: string;
684+
displayName: string;
685+
projectId: string;
686+
bundleId: string;
687+
}
688+
689+
interface IosApp {
690+
appId: string;
691+
692+
getMetadata(): Promise<admin.projectManagement.IosAppMetadata>;
693+
setDisplayName(newDisplayName: string): Promise<void>;
694+
getConfig(): Promise<string>;
695+
}
696+
697+
interface ProjectManagement {
698+
app: admin.app.App;
699+
700+
listAndroidApps(): Promise<admin.projectManagement.AndroidApp[]>;
701+
listIosApps(): Promise<admin.projectManagement.IosApp[]>;
702+
androidApp(appId: string): admin.projectManagement.AndroidApp;
703+
iosApp(appId: string): admin.projectManagement.IosApp;
704+
createAndroidApp(
705+
packageName: string, displayName?: string): Promise<admin.projectManagement.AndroidApp>;
706+
createIosApp(bundleId: string, displayName?: string): Promise<admin.projectManagement.IosApp>;
707+
}
708+
}
709+
653710
declare module 'firebase-admin' {
654711
}
655712

src/project-management/android-app.ts

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*!
2+
* Copyright 2018 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { FirebaseProjectManagementError } from '../utils/error';
18+
import * as validator from '../utils/validator';
19+
import { ProjectManagementRequestHandler, assertServerResponse } from './project-management-api-request';
20+
21+
export class AndroidApp {
22+
constructor(
23+
public readonly appId: string,
24+
private readonly requestHandler: ProjectManagementRequestHandler) {
25+
if (!validator.isNonEmptyString(appId)) {
26+
throw new FirebaseProjectManagementError(
27+
'invalid-argument', 'appId must be a non-empty string.');
28+
}
29+
}
30+
31+
public getMetadata(): Promise<AndroidAppMetadata> {
32+
return this.requestHandler.getAndroidMetadata(this.appId)
33+
.then((responseData: any) => {
34+
assertServerResponse(
35+
validator.isNonNullObject(responseData),
36+
responseData,
37+
'getMetadata()\'s responseData must be a non-null object.');
38+
39+
const requiredFieldsList = ['name', 'appId', 'projectId', 'packageName'];
40+
requiredFieldsList.forEach((requiredField) => {
41+
assertServerResponse(
42+
validator.isNonEmptyString(responseData[requiredField]),
43+
responseData,
44+
`getMetadata()\'s responseData.${requiredField} must be a non-empty string.`);
45+
});
46+
47+
const metadata: AndroidAppMetadata = {
48+
resourceName: responseData.name,
49+
appId: responseData.appId,
50+
displayName: responseData.displayName || null,
51+
projectId: responseData.projectId,
52+
packageName: responseData.packageName,
53+
};
54+
return metadata;
55+
});
56+
}
57+
58+
public setDisplayName(newDisplayName: string): Promise<void> {
59+
return this.requestHandler.setAndroidDisplayName(this.appId, newDisplayName);
60+
}
61+
62+
public getShaCertificates(): Promise<ShaCertificate[]> {
63+
return this.requestHandler.getAndroidShaCertificates(this.appId)
64+
.then((responseData: any) => {
65+
assertServerResponse(
66+
validator.isNonNullObject(responseData),
67+
responseData,
68+
'getShaCertificates()\'s responseData must be a non-null object.');
69+
70+
if (!responseData.certificates) {
71+
return [];
72+
}
73+
74+
assertServerResponse(
75+
validator.isArray(responseData.certificates),
76+
responseData,
77+
'"certificates" field must be present in the getShaCertificates() response data.');
78+
79+
const requiredFieldsList = ['name', 'shaHash'];
80+
81+
return responseData.certificates.map((certificateJson: any) => {
82+
requiredFieldsList.forEach((requiredField) => {
83+
assertServerResponse(
84+
validator.isNonEmptyString(certificateJson[requiredField]),
85+
responseData,
86+
`getShaCertificates()\'s responseData.certificates[].${requiredField} must be a `
87+
+ `non-empty string.`);
88+
});
89+
90+
return new ShaCertificate(certificateJson.shaHash, certificateJson.name);
91+
});
92+
});
93+
}
94+
95+
public addShaCertificate(certificateToAdd: ShaCertificate): Promise<void> {
96+
return this.requestHandler.addAndroidShaCertificate(this.appId, certificateToAdd);
97+
}
98+
99+
public deleteShaCertificate(certificateToDelete: ShaCertificate): Promise<void> {
100+
return this.requestHandler.deleteAndroidShaCertificate(certificateToDelete);
101+
}
102+
103+
/**
104+
* @return {Promise<string>} A promise that resolves to a UTF-8 JSON string, typically intended to
105+
* be written to a JSON file.
106+
*/
107+
public getConfig(): Promise<string> {
108+
return this.requestHandler.getAndroidConfig(this.appId)
109+
.then((responseData: any) => {
110+
assertServerResponse(
111+
validator.isNonNullObject(responseData),
112+
responseData,
113+
'getConfig()\'s responseData must be a non-null object.');
114+
115+
const base64ConfigFileContents = responseData.configFileContents;
116+
assertServerResponse(
117+
validator.isBase64String(base64ConfigFileContents),
118+
responseData,
119+
`getConfig()\'s responseData.configFileContents must be a base64 string.`);
120+
121+
return Buffer.from(base64ConfigFileContents, 'base64').toString('utf8');
122+
});
123+
}
124+
}
125+
126+
export interface AndroidAppMetadata {
127+
readonly resourceName: string;
128+
readonly appId: string;
129+
readonly displayName: string | null;
130+
readonly projectId: string;
131+
readonly packageName: string;
132+
}
133+
134+
export class ShaCertificate {
135+
public readonly certType: ('sha1' | 'sha256');
136+
137+
/**
138+
* Creates a ShaCertificate using the given hash. The ShaCertificate's type (eg. 'sha256') is
139+
* automatically determined from the hash itself.
140+
*
141+
* @param shaHash The sha256 or sha1 hash for this certificate.
142+
* @param resourceName The Firebase resource name for this certificate. This does not need to be
143+
* set when creating a new certificate.
144+
*/
145+
constructor(public readonly shaHash: string, public readonly resourceName?: string) {
146+
if (/^[a-fA-F0-9]{40}$/.test(shaHash)) {
147+
this.certType = 'sha1';
148+
} else if (/^[a-fA-F0-9]{64}$/.test(shaHash)) {
149+
this.certType = 'sha256';
150+
} else {
151+
throw new FirebaseProjectManagementError(
152+
'invalid-argument', 'shaHash must be either a sha256 hash or a sha1 hash.');
153+
}
154+
}
155+
}

0 commit comments

Comments
 (0)