Skip to content

Commit 61eb553

Browse files
samtsterndevpeerapong
authored andcommitted
Implement support for demo project ID namespace (firebase#3291)
1 parent 028641d commit 61eb553

File tree

7 files changed

+75
-11
lines changed

7 files changed

+75
-11
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
- Include `appId` in web app configuration when using the Hosting emulator (#2798).
2+
- Add support for emulating the `demo-*` project ID namespace with fake Admin and Web SDK configurations (#3291).

src/emulator/adminSdkConfig.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as apiv2 from "../apiv2";
33
import { configstore } from "../configstore";
44
import { FirebaseError } from "../error";
55
import { logger } from "../logger";
6+
import { Constants } from "./constants";
67

78
export type AdminSdkConfig = {
89
projectId: string;
@@ -33,6 +34,11 @@ export function constructDefaultAdminSdkConfig(projectId: string): AdminSdkConfi
3334
export async function getProjectAdminSdkConfigOrCached(
3435
projectId: string
3536
): Promise<AdminSdkConfig | undefined> {
37+
// When using the emulators with a fake project Id, use a fake project config.
38+
if (Constants.isDemoProject(projectId)) {
39+
return constructDefaultAdminSdkConfig(projectId);
40+
}
41+
3642
try {
3743
const config = await getProjectAdminSdkConfig(projectId);
3844
setCacheAdminSdkConfig(projectId, config);
@@ -46,7 +52,7 @@ export async function getProjectAdminSdkConfigOrCached(
4652
/**
4753
* Gets the Admin SDK configuration associated with a project.
4854
*/
49-
export async function getProjectAdminSdkConfig(projectId: string): Promise<AdminSdkConfig> {
55+
async function getProjectAdminSdkConfig(projectId: string): Promise<AdminSdkConfig> {
5056
const apiClient = new apiv2.Client({
5157
auth: true,
5258
apiVersion: "v1beta1",

src/emulator/constants.ts

+8
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ export const EMULATOR_DESCRIPTION: Record<Emulators, string> = {
4444
const DEFAULT_HOST = "localhost";
4545

4646
export class Constants {
47+
// GCP projects cannot start with 'demo' so we use 'demo-' as a prefix to denote
48+
// an intentionally fake project.
49+
static FAKE_PROJECT_ID_PREFIX = "demo-";
50+
4751
static DEFAULT_DATABASE_EMULATOR_NAMESPACE = "fake-server";
4852

4953
// Environment variable to override SDK/CLI to point at the Firestore emulator.
@@ -124,4 +128,8 @@ export class Constants {
124128
const u = url.parse(normalized);
125129
return u.hostname || DEFAULT_HOST;
126130
}
131+
132+
static isDemoProject(projectId?: string): boolean {
133+
return !!projectId && projectId.startsWith(this.FAKE_PROJECT_ID_PREFIX);
134+
}
127135
}

src/emulator/controller.ts

+14-10
Original file line numberDiff line numberDiff line change
@@ -327,19 +327,23 @@ export async function startAll(options: any, showUI: boolean = true): Promise<vo
327327
const targets = filterEmulatorTargets(options);
328328
options.targets = targets;
329329

330-
const projectId: string | undefined = getProjectId(options, true);
331-
332330
if (targets.length === 0) {
333331
throw new FirebaseError(
334332
`No emulators to start, run ${clc.bold("firebase init emulators")} to get started.`
335333
);
336334
}
335+
const hubLogger = EmulatorLogger.forEmulator(Emulators.HUB);
336+
hubLogger.logLabeled("BULLET", "emulators", `Starting emulators: ${targets.join(", ")}`);
337+
338+
const projectId: string | undefined = getProjectId(options, true);
339+
if (Constants.isDemoProject(projectId)) {
340+
hubLogger.logLabeled(
341+
"BULLET",
342+
"emulators",
343+
`Detected demo project ID "${projectId}", emulated services will use a demo configuration and attempts to access non-emulated services for this project will fail.`
344+
);
345+
}
337346

338-
EmulatorLogger.forEmulator(Emulators.HUB).logLabeled(
339-
"BULLET",
340-
"emulators",
341-
`Starting emulators: ${targets.join(", ")}`
342-
);
343347
const onlyOptions: string = options.only;
344348
if (onlyOptions) {
345349
const requested: string[] = onlyOptions.split(",").map((o) => {
@@ -385,7 +389,7 @@ export async function startAll(options: any, showUI: boolean = true): Promise<vo
385389
if (foundMetadata) {
386390
exportMetadata = foundMetadata;
387391
} else {
388-
EmulatorLogger.forEmulator(Emulators.HUB).logLabeled(
392+
hubLogger.logLabeled(
389393
"WARN",
390394
"emulators",
391395
`Could not find import/export metadata file, ${clc.bold("skipping data import!")}`
@@ -418,7 +422,7 @@ export async function startAll(options: any, showUI: boolean = true): Promise<vo
418422
const emulatorsNotRunning = ALL_SERVICE_EMULATORS.filter((e) => {
419423
return e !== Emulators.FUNCTIONS && !shouldStart(options, e);
420424
});
421-
if (emulatorsNotRunning.length > 0) {
425+
if (emulatorsNotRunning.length > 0 && !Constants.isDemoProject(projectId)) {
422426
functionsLogger.logLabeled(
423427
"WARN",
424428
"functions",
@@ -652,7 +656,7 @@ export async function startAll(options: any, showUI: boolean = true): Promise<vo
652656
}
653657

654658
if (showUI && !shouldStart(options, Emulators.UI)) {
655-
EmulatorLogger.forEmulator(Emulators.HUB).logLabeled(
659+
hubLogger.logLabeled(
656660
"WARN",
657661
"emulators",
658662
"The Emulator UI requires a project ID to start. Configure your default project with 'firebase use' or pass the --project flag."

src/fetchWebSetup.ts

+19
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { configstore } from "./configstore";
33
import { firebaseApiOrigin, hostingApiOrigin } from "./api";
44
import * as getProjectId from "./getProjectId";
55
import { logger } from "./logger";
6+
import { Constants } from "./emulator/constants";
67

78
export interface WebConfig {
89
projectId: string;
@@ -76,6 +77,19 @@ async function listAllSites(projectId: string, nextPageToken?: string): Promise<
7677
return sites;
7778
}
7879

80+
/**
81+
* Construct a fake configuration based on the project ID.
82+
*/
83+
function constructDefaultWebSetup(projectId: string): WebConfig {
84+
return {
85+
projectId,
86+
databaseURL: `https://${projectId}.firebaseio.com`,
87+
storageBucket: `${projectId}.appspot.com`,
88+
apiKey: "fake-api-key",
89+
authDomain: `${projectId}.firebaseapp.com`,
90+
};
91+
}
92+
7993
/**
8094
* TODO: deprecate this function in favor of `getAppConfig()` in `/src/management/apps.ts`
8195
* @param options CLI options.
@@ -84,6 +98,11 @@ async function listAllSites(projectId: string, nextPageToken?: string): Promise<
8498
export async function fetchWebSetup(options: any): Promise<WebConfig> {
8599
const projectId = getProjectId(options, false);
86100

101+
// When using the emulators with a fake project ID, use a fake web config
102+
if (Constants.isDemoProject(projectId)) {
103+
return constructDefaultWebSetup(projectId);
104+
}
105+
87106
// Try to determine the appId from the default Hosting site, if it is linked.
88107
let hostingAppId: string | undefined = undefined;
89108
try {
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { expect } from "chai";
2+
import { getProjectAdminSdkConfigOrCached } from "../../emulator/adminSdkConfig";
3+
4+
describe("adminSdkConfig", () => {
5+
describe("getProjectAdminSdkConfigOrCached", () => {
6+
it("should return a fake config for a demo project id", async () => {
7+
const projectId = "demo-project-1234";
8+
await expect(getProjectAdminSdkConfigOrCached(projectId)).to.eventually.deep.equal({
9+
projectId: "demo-project-1234",
10+
databaseURL: "https://demo-project-1234.firebaseio.com",
11+
storageBucket: "demo-project-1234.appspot.com",
12+
});
13+
});
14+
});
15+
});

src/test/fetchWebSetup.spec.ts

+11
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,17 @@ describe("fetchWebSetup module", () => {
6363
"Not Found"
6464
);
6565
});
66+
67+
it("should return a fake config for a demo project id", async () => {
68+
const projectId = "demo-project-1234";
69+
await expect(fetchWebSetup({ project: projectId })).to.eventually.deep.equal({
70+
projectId: "demo-project-1234",
71+
databaseURL: "https://demo-project-1234.firebaseio.com",
72+
storageBucket: "demo-project-1234.appspot.com",
73+
apiKey: "fake-api-key",
74+
authDomain: "demo-project-1234.firebaseapp.com",
75+
});
76+
});
6677
});
6778

6879
describe("getCachedWebSetup", () => {

0 commit comments

Comments
 (0)