firebase-tools 11.14.1 → 11.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/lib/bin/firebase.js +3 -3
  2. package/lib/commands/deploy.js +3 -3
  3. package/lib/commands/emulators-start.js +20 -16
  4. package/lib/deploy/functions/params.js +9 -1
  5. package/lib/deploy/functions/release/fabricator.js +1 -0
  6. package/lib/deploy/hosting/convertConfig.js +2 -1
  7. package/lib/deploy/index.js +10 -0
  8. package/lib/emulator/ExpressBasedEmulator.js +92 -0
  9. package/lib/emulator/auth/cloudFunctions.js +3 -12
  10. package/lib/emulator/auth/index.js +2 -2
  11. package/lib/emulator/auth/utils.js +1 -1
  12. package/lib/emulator/commandUtils.js +21 -44
  13. package/lib/emulator/constants.js +2 -9
  14. package/lib/emulator/controller.js +129 -130
  15. package/lib/emulator/databaseEmulator.js +3 -8
  16. package/lib/emulator/dns.js +49 -0
  17. package/lib/emulator/downloadableEmulators.js +19 -13
  18. package/lib/emulator/emulatorLogger.js +14 -7
  19. package/lib/emulator/env.js +35 -0
  20. package/lib/emulator/eventarcEmulator.js +2 -8
  21. package/lib/emulator/extensions/postinstall.js +8 -8
  22. package/lib/emulator/extensionsEmulator.js +7 -7
  23. package/lib/emulator/firestoreEmulator.js +3 -11
  24. package/lib/emulator/functionsEmulator.js +24 -60
  25. package/lib/emulator/functionsEmulatorShared.js +5 -3
  26. package/lib/emulator/functionsEmulatorShell.js +1 -1
  27. package/lib/emulator/hub.js +50 -58
  28. package/lib/emulator/hubClient.js +24 -10
  29. package/lib/emulator/hubExport.js +3 -11
  30. package/lib/emulator/portUtils.js +208 -29
  31. package/lib/emulator/pubsubEmulator.js +11 -12
  32. package/lib/emulator/registry.js +11 -20
  33. package/lib/emulator/storage/apis/gcloud.js +3 -6
  34. package/lib/emulator/storage/cloudFunctions.js +2 -7
  35. package/lib/emulator/storage/metadata.js +11 -5
  36. package/lib/emulator/storage/rules/runtime.js +1 -6
  37. package/lib/emulator/ui.js +6 -7
  38. package/lib/experiments.js +10 -1
  39. package/lib/extensions/displayExtensionInfo.js +43 -6
  40. package/lib/extensions/secretsUtils.js +2 -1
  41. package/lib/frameworks/angular/index.js +1 -1
  42. package/lib/frameworks/express/index.js +1 -1
  43. package/lib/frameworks/index.js +33 -17
  44. package/lib/frameworks/next/index.js +1 -1
  45. package/lib/frameworks/nuxt/index.js +1 -1
  46. package/lib/frameworks/vite/index.js +1 -1
  47. package/lib/functions/ensureTargeted.js +21 -0
  48. package/lib/functions/env.js +33 -19
  49. package/lib/functionsShellCommandAction.js +12 -5
  50. package/lib/gcp/run.js +0 -1
  51. package/lib/handlePreviewToggles.js +2 -2
  52. package/lib/hosting/functionsProxy.js +2 -3
  53. package/lib/hosting/implicitInit.js +2 -11
  54. package/lib/hosting/runTags.js +3 -4
  55. package/lib/serve/functions.js +8 -10
  56. package/lib/serve/hosting.js +10 -4
  57. package/lib/serve/index.js +2 -2
  58. package/lib/utils.js +14 -1
  59. package/npm-shrinkwrap.json +2 -2
  60. package/package.json +1 -1
  61. package/templates/hosting/init.js +3 -2
  62. package/lib/emulator/emulatorServer.js +0 -29
@@ -7,7 +7,6 @@ const types_1 = require("./types");
7
7
  const utils_1 = require("../utils");
8
8
  const emulatorLogger_1 = require("./emulatorLogger");
9
9
  const registry_1 = require("./registry");
10
- const apiv2_1 = require("../apiv2");
11
10
  const error_1 = require("../error");
12
11
  class EventarcEmulator {
13
12
  constructor(args) {
@@ -77,21 +76,16 @@ class EventarcEmulator {
77
76
  return hub;
78
77
  }
79
78
  async triggerCustomEventFunction(channel, event) {
80
- const functionsEmulator = registry_1.EmulatorRegistry.get(types_1.Emulators.FUNCTIONS);
81
- if (!functionsEmulator) {
79
+ if (!registry_1.EmulatorRegistry.isRunning(types_1.Emulators.FUNCTIONS)) {
82
80
  this.logger.log("INFO", "Functions emulator not found. This should not happen.");
83
81
  return Promise.reject();
84
82
  }
85
83
  const key = `${event.type}-${channel}`;
86
84
  const triggers = this.customEvents[key] || [];
87
- const apiClient = new apiv2_1.Client({
88
- urlPrefix: `http://${registry_1.EmulatorRegistry.getInfoHostString(functionsEmulator.getInfo())}`,
89
- auth: false,
90
- });
91
85
  return await Promise.all(triggers
92
86
  .filter((trigger) => !trigger.eventTrigger.eventFilters ||
93
87
  this.matchesAll(event, trigger.eventTrigger.eventFilters))
94
- .map((trigger) => apiClient
88
+ .map((trigger) => registry_1.EmulatorRegistry.client(types_1.Emulators.FUNCTIONS)
95
89
  .request({
96
90
  method: "POST",
97
91
  path: `/functions/projects/${trigger.projectId}/triggers/${trigger.triggerName}`,
@@ -4,33 +4,33 @@ exports.replaceConsoleLinks = void 0;
4
4
  const registry_1 = require("../registry");
5
5
  const types_1 = require("../types");
6
6
  function replaceConsoleLinks(postinstall) {
7
- const uiInfo = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.UI);
8
- const uiUrl = uiInfo ? `http://${registry_1.EmulatorRegistry.getInfoHostString(uiInfo)}` : "unknown";
7
+ const uiRunning = registry_1.EmulatorRegistry.isRunning(types_1.Emulators.UI);
8
+ const uiUrl = uiRunning ? registry_1.EmulatorRegistry.url(types_1.Emulators.UI).toString() : "unknown";
9
9
  let subbedPostinstall = postinstall;
10
10
  const linkReplacements = new Map([
11
11
  [
12
12
  /(http[s]?:\/\/)?console\.firebase\.google\.com\/(u\/[0-9]\/)?project\/[A-Za-z0-9-]+\/storage[A-Za-z0-9\/-]*(?=[\)\]\s])/,
13
- `${uiUrl}/${types_1.Emulators.STORAGE}`,
13
+ `${uiUrl}${types_1.Emulators.STORAGE}`,
14
14
  ],
15
15
  [
16
16
  /(http[s]?:\/\/)?console\.firebase\.google\.com\/(u\/[0-9]\/)?project\/[A-Za-z0-9-]+\/firestore[A-Za-z0-9\/-]*(?=[\)\]\s])/,
17
- `${uiUrl}/${types_1.Emulators.FIRESTORE}`,
17
+ `${uiUrl}${types_1.Emulators.FIRESTORE}`,
18
18
  ],
19
19
  [
20
20
  /(http[s]?:\/\/)?console\.firebase\.google\.com\/(u\/[0-9]\/)?project\/[A-Za-z0-9-]+\/database[A-Za-z0-9\/-]*(?=[\)\]\s])/,
21
- `${uiUrl}/${types_1.Emulators.DATABASE}`,
21
+ `${uiUrl}${types_1.Emulators.DATABASE}`,
22
22
  ],
23
23
  [
24
24
  /(http[s]?:\/\/)?console\.firebase\.google\.com\/(u\/[0-9]\/)?project\/[A-Za-z0-9-]+\/authentication[A-Za-z0-9\/-]*(?=[\)\]\s])/,
25
- `${uiUrl}/${types_1.Emulators.AUTH}`,
25
+ `${uiUrl}${types_1.Emulators.AUTH}`,
26
26
  ],
27
27
  [
28
28
  /(http[s]?:\/\/)?console\.firebase\.google\.com\/(u\/[0-9]\/)?project\/[A-Za-z0-9-]+\/functions[A-Za-z0-9\/-]*(?=[\)\]\s])/,
29
- `${uiUrl}/logs`,
29
+ `${uiUrl}logs`,
30
30
  ],
31
31
  [
32
32
  /(http[s]?:\/\/)?console\.firebase\.google\.com\/(u\/[0-9]\/)?project\/[A-Za-z0-9-]+\/extensions[A-Za-z0-9\/-]*(?=[\)\]\s])/,
33
- `${uiUrl}/${types_1.Emulators.EXTENSIONS}`,
33
+ `${uiUrl}${types_1.Emulators.EXTENSIONS}`,
34
34
  ],
35
35
  ]);
36
36
  for (const [consoleLinkRegex, replacement] of linkReplacements) {
@@ -40,11 +40,11 @@ class ExtensionsEmulator {
40
40
  return Promise.resolve();
41
41
  }
42
42
  getInfo() {
43
- const info = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.FUNCTIONS);
44
- if (!info) {
43
+ const functionsEmulator = registry_1.EmulatorRegistry.get(types_1.Emulators.FUNCTIONS);
44
+ if (!functionsEmulator) {
45
45
  throw new error_1.FirebaseError("Extensions Emulator is running but Functions emulator is not. This should never happen.");
46
46
  }
47
- return info;
47
+ return functionsEmulator.getInfo();
48
48
  }
49
49
  getName() {
50
50
  return types_1.Emulators.EXTENSIONS;
@@ -222,12 +222,12 @@ class ExtensionsEmulator {
222
222
  return filteredBackends;
223
223
  }
224
224
  extensionDetailsUILink(backend) {
225
- const uiInfo = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.UI);
226
- if (!uiInfo || !backend.extensionInstanceId) {
225
+ if (!registry_1.EmulatorRegistry.isRunning(types_1.Emulators.UI) || !backend.extensionInstanceId) {
227
226
  return "";
228
227
  }
229
- const uiUrl = registry_1.EmulatorRegistry.getInfoHostString(uiInfo);
230
- return clc.underline(clc.bold(`http://${uiUrl}/${types_1.Emulators.EXTENSIONS}/${backend.extensionInstanceId}`));
228
+ const uiUrl = registry_1.EmulatorRegistry.url(types_1.Emulators.UI);
229
+ uiUrl.pathname = `/${types_1.Emulators.EXTENSIONS}/${backend.extensionInstanceId}`;
230
+ return clc.underline(clc.bold(uiUrl.toString()));
231
231
  }
232
232
  extensionsInfoTable(options) {
233
233
  var _a;
@@ -10,15 +10,13 @@ const downloadableEmulators = require("./downloadableEmulators");
10
10
  const types_1 = require("../emulator/types");
11
11
  const registry_1 = require("./registry");
12
12
  const constants_1 = require("./constants");
13
- const apiv2_1 = require("../apiv2");
14
13
  class FirestoreEmulator {
15
14
  constructor(args) {
16
15
  this.args = args;
17
16
  }
18
17
  async start() {
19
- const functionsInfo = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.FUNCTIONS);
20
- if (functionsInfo) {
21
- this.args.functions_emulator = registry_1.EmulatorRegistry.getInfoHostString(functionsInfo);
18
+ if (registry_1.EmulatorRegistry.isRunning(types_1.Emulators.FUNCTIONS)) {
19
+ this.args.functions_emulator = registry_1.EmulatorRegistry.url(types_1.Emulators.FUNCTIONS).host;
22
20
  }
23
21
  if (this.args.rules && this.args.project_id) {
24
22
  const rulesPath = this.args.rules;
@@ -71,7 +69,6 @@ class FirestoreEmulator {
71
69
  }
72
70
  async updateRules(content) {
73
71
  const projectId = this.args.project_id;
74
- const info = this.getInfo();
75
72
  const body = {
76
73
  ignore_errors: true,
77
74
  rules: {
@@ -83,11 +80,7 @@ class FirestoreEmulator {
83
80
  ],
84
81
  },
85
82
  };
86
- const client = new apiv2_1.Client({
87
- urlPrefix: `http://${registry_1.EmulatorRegistry.getInfoHostString(info)}`,
88
- auth: false,
89
- });
90
- const res = await client.put(`/emulator/v1/projects/${projectId}:securityRules`, body);
83
+ const res = await registry_1.EmulatorRegistry.client(types_1.Emulators.FIRESTORE).put(`/emulator/v1/projects/${projectId}:securityRules`, body);
91
84
  if (res.body && Array.isArray(res.body.issues)) {
92
85
  return res.body.issues;
93
86
  }
@@ -101,4 +94,3 @@ class FirestoreEmulator {
101
94
  }
102
95
  }
103
96
  exports.FirestoreEmulator = FirestoreEmulator;
104
- FirestoreEmulator.FIRESTORE_EMULATOR_ENV_ALT = "FIREBASE_FIRESTORE_EMULATOR_ADDRESS";
@@ -31,8 +31,8 @@ const runtimes = require("../deploy/functions/runtimes");
31
31
  const backend = require("../deploy/functions/backend");
32
32
  const functionsEnv = require("../functions/env");
33
33
  const v1_1 = require("../functions/events/v1");
34
- const apiv2_1 = require("../apiv2");
35
34
  const build_1 = require("../deploy/functions/build");
35
+ const env_1 = require("./env");
36
36
  const EVENT_INVOKE = "functions:invoke";
37
37
  const EVENT_INVOKE_GA4 = "functions_invoke";
38
38
  const DATABASE_PATH_PATTERN = new RegExp("^projects/[^/]+/instances/([^/]+)/refs(/.*)$");
@@ -60,8 +60,16 @@ class FunctionsEmulator {
60
60
  }
61
61
  this.workQueue = new workQueue_1.WorkQueue(mode);
62
62
  }
63
- static getHttpFunctionUrl(host, port, projectId, name, region) {
64
- return `http://${host}:${port}/${projectId}/${region}/${name}`;
63
+ static getHttpFunctionUrl(projectId, name, region, info) {
64
+ let url;
65
+ if (info) {
66
+ url = new url_1.URL("http://" + (0, functionsEmulatorShared_1.formatHost)(info));
67
+ }
68
+ else {
69
+ url = registry_1.EmulatorRegistry.url(types_1.Emulators.FUNCTIONS);
70
+ }
71
+ url.pathname = `/${projectId}/${region}/${name}`;
72
+ return url.toString();
65
73
  }
66
74
  async getCredentialsEnvironment() {
67
75
  const credentialEnv = {};
@@ -124,7 +132,7 @@ class FunctionsEmulator {
124
132
  this.workQueue.submit(() => {
125
133
  return new Promise((resolve, reject) => {
126
134
  const trigReq = http.request({
127
- host,
135
+ host: (0, utils_1.connectableHostname)(host),
128
136
  port,
129
137
  method: req.method,
130
138
  path: `/functions/projects/${projectId}/triggers/${triggerId}`,
@@ -312,10 +320,9 @@ class FunctionsEmulator {
312
320
  }
313
321
  let added = false;
314
322
  let url = undefined;
315
- const { host, port } = this.getInfo();
316
323
  if (definition.httpsTrigger) {
317
324
  added = true;
318
- url = FunctionsEmulator.getHttpFunctionUrl(host, port, this.args.projectId, definition.name, definition.region);
325
+ url = FunctionsEmulator.getHttpFunctionUrl(this.args.projectId, definition.name, definition.region);
319
326
  }
320
327
  else if (definition.eventTrigger) {
321
328
  const service = (0, functionsEmulatorShared_1.getFunctionService)(definition);
@@ -346,8 +353,7 @@ class FunctionsEmulator {
346
353
  }
347
354
  }
348
355
  else if (definition.blockingTrigger) {
349
- const { host, port } = this.getInfo();
350
- url = FunctionsEmulator.getHttpFunctionUrl(host, port, this.args.projectId, definition.name, definition.region);
356
+ url = FunctionsEmulator.getHttpFunctionUrl(this.args.projectId, definition.name, definition.region);
351
357
  added = this.addBlockingTrigger(url, definition.blockingTrigger);
352
358
  }
353
359
  else {
@@ -374,19 +380,14 @@ class FunctionsEmulator {
374
380
  }
375
381
  }
376
382
  addEventarcTrigger(projectId, key, eventTrigger) {
377
- const eventarcEmu = registry_1.EmulatorRegistry.get(types_1.Emulators.EVENTARC);
378
- if (!eventarcEmu) {
383
+ if (!registry_1.EmulatorRegistry.isRunning(types_1.Emulators.EVENTARC)) {
379
384
  return Promise.resolve(false);
380
385
  }
381
386
  const bundle = {
382
387
  eventTrigger: Object.assign(Object.assign({}, eventTrigger), { service: "eventarc.googleapis.com" }),
383
388
  };
384
389
  logger_1.logger.debug(`addEventarcTrigger`, JSON.stringify(bundle));
385
- const client = new apiv2_1.Client({
386
- urlPrefix: `http://${registry_1.EmulatorRegistry.getInfoHostString(eventarcEmu.getInfo())}`,
387
- auth: false,
388
- });
389
- return client
390
+ return registry_1.EmulatorRegistry.client(types_1.Emulators.EVENTARC)
390
391
  .post(`/emulator/v1/projects/${projectId}/triggers/${key}`, bundle)
391
392
  .then(() => true)
392
393
  .catch((err) => {
@@ -399,16 +400,12 @@ class FunctionsEmulator {
399
400
  !this.blockingFunctionsConfig.forwardInboundCredentials) {
400
401
  return;
401
402
  }
402
- const authEmu = registry_1.EmulatorRegistry.get(types_1.Emulators.AUTH);
403
- if (!authEmu) {
403
+ if (!registry_1.EmulatorRegistry.isRunning(types_1.Emulators.AUTH)) {
404
404
  return;
405
405
  }
406
406
  const path = `/identitytoolkit.googleapis.com/v2/projects/${this.getProjectId()}/config?updateMask=blockingFunctions`;
407
407
  try {
408
- const client = new apiv2_1.Client({
409
- urlPrefix: `http://${registry_1.EmulatorRegistry.getInfoHostString(authEmu.getInfo())}`,
410
- auth: false,
411
- });
408
+ const client = registry_1.EmulatorRegistry.client(types_1.Emulators.AUTH);
412
409
  await client.patch(path, { blockingFunctions: this.blockingFunctionsConfig }, {
413
410
  headers: { Authorization: "Bearer owner" },
414
411
  });
@@ -461,18 +458,14 @@ class FunctionsEmulator {
461
458
  return { bundle, apiPath, instance };
462
459
  }
463
460
  async addRealtimeDatabaseTrigger(projectId, key, eventTrigger, signature, region) {
464
- const databaseEmu = registry_1.EmulatorRegistry.get(types_1.Emulators.DATABASE);
465
- if (!databaseEmu) {
461
+ if (!registry_1.EmulatorRegistry.isRunning(types_1.Emulators.DATABASE)) {
466
462
  return false;
467
463
  }
468
464
  const { bundle, apiPath, instance } = signature === "cloudevent"
469
465
  ? this.getV2DatabaseApiAttributes(projectId, key, eventTrigger, region)
470
466
  : this.getV1DatabaseApiAttributes(projectId, key, eventTrigger);
471
467
  logger_1.logger.debug(`addRealtimeDatabaseTrigger[${instance}]`, JSON.stringify(bundle));
472
- const client = new apiv2_1.Client({
473
- urlPrefix: `http://${registry_1.EmulatorRegistry.getInfoHostString(databaseEmu.getInfo())}`,
474
- auth: false,
475
- });
468
+ const client = registry_1.EmulatorRegistry.client(types_1.Emulators.DATABASE);
476
469
  try {
477
470
  await client.post(apiPath, bundle, { headers: { Authorization: "Bearer owner" } });
478
471
  }
@@ -483,18 +476,14 @@ class FunctionsEmulator {
483
476
  return true;
484
477
  }
485
478
  async addFirestoreTrigger(projectId, key, eventTrigger) {
486
- const firestoreEmu = registry_1.EmulatorRegistry.get(types_1.Emulators.FIRESTORE);
487
- if (!firestoreEmu) {
479
+ if (!registry_1.EmulatorRegistry.isRunning(types_1.Emulators.FIRESTORE)) {
488
480
  return Promise.resolve(false);
489
481
  }
490
482
  const bundle = JSON.stringify({
491
483
  eventTrigger: Object.assign(Object.assign({}, eventTrigger), { service: "firestore.googleapis.com" }),
492
484
  });
493
485
  logger_1.logger.debug(`addFirestoreTrigger`, JSON.stringify(bundle));
494
- const client = new apiv2_1.Client({
495
- urlPrefix: `http://${registry_1.EmulatorRegistry.getInfoHostString(firestoreEmu.getInfo())}`,
496
- auth: false,
497
- });
486
+ const client = registry_1.EmulatorRegistry.client(types_1.Emulators.FIRESTORE);
498
487
  try {
499
488
  await client.put(`/emulator/v1/projects/${projectId}/triggers/${key}`, bundle);
500
489
  }
@@ -718,32 +707,7 @@ class FunctionsEmulator {
718
707
  skipTokenVerification: true,
719
708
  enableCors: true,
720
709
  });
721
- const firestoreEmulator = this.getEmulatorInfo(types_1.Emulators.FIRESTORE);
722
- if (firestoreEmulator != null) {
723
- envs[constants_1.Constants.FIRESTORE_EMULATOR_HOST] = (0, functionsEmulatorShared_1.formatHost)(firestoreEmulator);
724
- }
725
- const databaseEmulator = this.getEmulatorInfo(types_1.Emulators.DATABASE);
726
- if (databaseEmulator) {
727
- envs[constants_1.Constants.FIREBASE_DATABASE_EMULATOR_HOST] = (0, functionsEmulatorShared_1.formatHost)(databaseEmulator);
728
- }
729
- const authEmulator = this.getEmulatorInfo(types_1.Emulators.AUTH);
730
- if (authEmulator) {
731
- envs[constants_1.Constants.FIREBASE_AUTH_EMULATOR_HOST] = (0, functionsEmulatorShared_1.formatHost)(authEmulator);
732
- }
733
- const storageEmulator = this.getEmulatorInfo(types_1.Emulators.STORAGE);
734
- if (storageEmulator) {
735
- envs[constants_1.Constants.FIREBASE_STORAGE_EMULATOR_HOST] = (0, functionsEmulatorShared_1.formatHost)(storageEmulator);
736
- envs[constants_1.Constants.CLOUD_STORAGE_EMULATOR_HOST] = `http://${(0, functionsEmulatorShared_1.formatHost)(storageEmulator)}`;
737
- }
738
- const pubsubEmulator = this.getEmulatorInfo(types_1.Emulators.PUBSUB);
739
- if (pubsubEmulator) {
740
- const pubsubHost = (0, functionsEmulatorShared_1.formatHost)(pubsubEmulator);
741
- process.env.PUBSUB_EMULATOR_HOST = pubsubHost;
742
- }
743
- const eventarcEmulator = this.getEmulatorInfo(types_1.Emulators.EVENTARC);
744
- if (eventarcEmulator) {
745
- envs[constants_1.Constants.CLOUD_EVENTARC_EMULATOR_HOST] = `http://${(0, functionsEmulatorShared_1.formatHost)(eventarcEmulator)}`;
746
- }
710
+ (0, env_1.setEnvVarsForEmulators)(envs);
747
711
  if (this.args.debugPort) {
748
712
  envs["FUNCTION_DEBUG_MODE"] = "true";
749
713
  }
@@ -821,7 +785,7 @@ class FunctionsEmulator {
821
785
  }
822
786
  else {
823
787
  const { host } = this.getInfo();
824
- args.unshift(`--inspect=${host}:${this.args.debugPort}`);
788
+ args.unshift(`--inspect=${(0, utils_1.connectableHostname)(host)}:${this.args.debugPort}`);
825
789
  }
826
790
  }
827
791
  const pnpPath = path.join(backend.functionsDir, ".pnp.js");
@@ -14,6 +14,7 @@ const postinstall_1 = require("./extensions/postinstall");
14
14
  const services_1 = require("../deploy/functions/services");
15
15
  const prepare_1 = require("../deploy/functions/prepare");
16
16
  const events = require("../functions/events");
17
+ const utils_1 = require("../utils");
17
18
  const V2_EVENTS = [
18
19
  events.v2.PUBSUB_PUBLISH_EVENT,
19
20
  ...events.v2.STORAGE_EVENTS,
@@ -246,11 +247,12 @@ function findModuleRoot(moduleName, filepath) {
246
247
  }
247
248
  exports.findModuleRoot = findModuleRoot;
248
249
  function formatHost(info) {
249
- if (info.host.includes(":")) {
250
- return `[${info.host}]:${info.port}`;
250
+ const host = (0, utils_1.connectableHostname)(info.host);
251
+ if (host.includes(":")) {
252
+ return `[${host}]:${info.port}`;
251
253
  }
252
254
  else {
253
- return `${info.host}:${info.port}`;
255
+ return `${host}:${info.port}`;
254
256
  }
255
257
  }
256
258
  exports.formatHost = formatHost;
@@ -16,7 +16,7 @@ class FunctionsEmulatorShell {
16
16
  utils.logLabeledBullet("functions", `Loaded functions: ${entryPoints.join(", ")}`);
17
17
  for (const trigger of this.triggers) {
18
18
  if (trigger.httpsTrigger) {
19
- this.urls[trigger.id] = functionsEmulator_1.FunctionsEmulator.getHttpFunctionUrl(this.emu.getInfo().host, this.emu.getInfo().port, this.emu.getProjectId(), trigger.name, trigger.region);
19
+ this.urls[trigger.id] = functionsEmulator_1.FunctionsEmulator.getHttpFunctionUrl(this.emu.getProjectId(), trigger.name, trigger.region, this.emu.getInfo());
20
20
  }
21
21
  }
22
22
  }
@@ -1,37 +1,58 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EmulatorHub = void 0;
4
- const cors = require("cors");
5
- const express = require("express");
6
4
  const os = require("os");
7
5
  const fs = require("fs");
8
6
  const path = require("path");
9
- const bodyParser = require("body-parser");
10
7
  const utils = require("../utils");
11
8
  const logger_1 = require("../logger");
12
- const constants_1 = require("./constants");
13
9
  const types_1 = require("./types");
14
10
  const hubExport_1 = require("./hubExport");
15
11
  const registry_1 = require("./registry");
12
+ const ExpressBasedEmulator_1 = require("./ExpressBasedEmulator");
16
13
  const pkg = require("../../package.json");
17
- class EmulatorHub {
14
+ class EmulatorHub extends ExpressBasedEmulator_1.ExpressBasedEmulator {
18
15
  constructor(args) {
16
+ super({
17
+ listen: args.listen,
18
+ });
19
19
  this.args = args;
20
- this.hub = express();
21
- this.hub.use(cors({ origin: true }));
22
- this.hub.use(bodyParser.json());
23
- this.hub.get("/", (req, res) => {
24
- res.json(this.getLocator());
20
+ }
21
+ static readLocatorFile(projectId) {
22
+ const locatorPath = this.getLocatorFilePath(projectId);
23
+ if (!fs.existsSync(locatorPath)) {
24
+ return undefined;
25
+ }
26
+ const data = fs.readFileSync(locatorPath, "utf8").toString();
27
+ const locator = JSON.parse(data);
28
+ if (locator.version !== this.CLI_VERSION) {
29
+ logger_1.logger.debug(`Found locator with mismatched version, ignoring: ${JSON.stringify(locator)}`);
30
+ return undefined;
31
+ }
32
+ return locator;
33
+ }
34
+ static getLocatorFilePath(projectId) {
35
+ const dir = os.tmpdir();
36
+ const filename = `hub-${projectId}.json`;
37
+ return path.join(dir, filename);
38
+ }
39
+ async start() {
40
+ await super.start();
41
+ await this.writeLocatorFile();
42
+ }
43
+ async createExpressApp() {
44
+ const app = await super.createExpressApp();
45
+ app.get("/", (req, res) => {
46
+ res.json(Object.assign(Object.assign({}, this.getLocator()), { host: utils.connectableHostname(this.args.listen[0].address), port: this.args.listen[0].port }));
25
47
  });
26
- this.hub.get(EmulatorHub.PATH_EMULATORS, (req, res) => {
48
+ app.get(EmulatorHub.PATH_EMULATORS, (req, res) => {
27
49
  const body = {};
28
- for (const emulator of registry_1.EmulatorRegistry.listRunning()) {
29
- const info = registry_1.EmulatorRegistry.getInfo(emulator);
30
- body[emulator] = info;
50
+ for (const info of registry_1.EmulatorRegistry.listRunningWithInfo()) {
51
+ body[info.name] = Object.assign({ listen: this.args.listenForEmulator[info.name] }, info);
31
52
  }
32
53
  res.json(body);
33
54
  });
34
- this.hub.post(EmulatorHub.PATH_EXPORT, async (req, res) => {
55
+ app.post(EmulatorHub.PATH_EXPORT, async (req, res) => {
35
56
  const path = req.body.path;
36
57
  const initiatedBy = req.body.initiatedBy || "unknown";
37
58
  utils.logLabeledBullet("emulators", `Received export request. Exporting data to ${path}.`);
@@ -53,7 +74,7 @@ class EmulatorHub {
53
74
  });
54
75
  }
55
76
  });
56
- this.hub.put(EmulatorHub.PATH_DISABLE_FUNCTIONS, async (req, res) => {
77
+ app.put(EmulatorHub.PATH_DISABLE_FUNCTIONS, async (req, res) => {
57
78
  utils.logLabeledBullet("emulators", `Disabling Cloud Functions triggers, non-HTTP functions will not execute.`);
58
79
  const instance = registry_1.EmulatorRegistry.get(types_1.Emulators.FUNCTIONS);
59
80
  if (!instance) {
@@ -64,7 +85,7 @@ class EmulatorHub {
64
85
  await emu.disableBackgroundTriggers();
65
86
  res.status(200).json({ enabled: false });
66
87
  });
67
- this.hub.put(EmulatorHub.PATH_ENABLE_FUNCTIONS, async (req, res) => {
88
+ app.put(EmulatorHub.PATH_ENABLE_FUNCTIONS, async (req, res) => {
68
89
  utils.logLabeledBullet("emulators", `Enabling Cloud Functions triggers, non-HTTP functions will execute.`);
69
90
  const instance = registry_1.EmulatorRegistry.get(types_1.Emulators.FUNCTIONS);
70
91
  if (!instance) {
@@ -75,58 +96,29 @@ class EmulatorHub {
75
96
  await emu.reloadTriggers();
76
97
  res.status(200).json({ enabled: true });
77
98
  });
78
- }
79
- static readLocatorFile(projectId) {
80
- const locatorPath = this.getLocatorFilePath(projectId);
81
- if (!fs.existsSync(locatorPath)) {
82
- return undefined;
83
- }
84
- const data = fs.readFileSync(locatorPath, "utf8").toString();
85
- const locator = JSON.parse(data);
86
- if (locator.version !== this.CLI_VERSION) {
87
- logger_1.logger.debug(`Found locator with mismatched version, ignoring: ${JSON.stringify(locator)}`);
88
- return undefined;
89
- }
90
- return locator;
91
- }
92
- static getLocatorFilePath(projectId) {
93
- const dir = os.tmpdir();
94
- const filename = `hub-${projectId}.json`;
95
- return path.join(dir, filename);
96
- }
97
- async start() {
98
- const { host, port } = this.getInfo();
99
- const server = this.hub.listen(port, host);
100
- this.destroyServer = utils.createDestroyer(server);
101
- await this.writeLocatorFile();
102
- }
103
- async connect() {
99
+ return app;
104
100
  }
105
101
  async stop() {
106
- if (this.destroyServer) {
107
- await this.destroyServer();
108
- }
102
+ await super.stop();
109
103
  await this.deleteLocatorFile();
110
104
  }
111
- getInfo() {
112
- const host = this.args.host || constants_1.Constants.getDefaultHost();
113
- const port = this.args.port || constants_1.Constants.getDefaultPort(types_1.Emulators.HUB);
114
- return {
115
- name: this.getName(),
116
- host,
117
- port,
118
- };
119
- }
120
105
  getName() {
121
106
  return types_1.Emulators.HUB;
122
107
  }
123
108
  getLocator() {
124
- const { host, port } = this.getInfo();
125
109
  const version = pkg.version;
110
+ const origins = [];
111
+ for (const spec of this.args.listen) {
112
+ if (spec.family === "IPv6") {
113
+ origins.push(`http://[${utils.connectableHostname(spec.address)}]:${spec.port}`);
114
+ }
115
+ else {
116
+ origins.push(`http://${utils.connectableHostname(spec.address)}:${spec.port}`);
117
+ }
118
+ }
126
119
  return {
127
120
  version,
128
- host,
129
- port,
121
+ origins,
130
122
  };
131
123
  }
132
124
  async writeLocatorFile() {
@@ -12,23 +12,37 @@ class EmulatorHubClient {
12
12
  foundHub() {
13
13
  return this.locator !== undefined;
14
14
  }
15
- async getStatus() {
16
- const apiClient = new apiv2_1.Client({ urlPrefix: this.origin, auth: false });
17
- await apiClient.get("/");
15
+ getStatus() {
16
+ return this.tryOrigins(async (client, origin) => {
17
+ await client.get("/");
18
+ return origin;
19
+ });
20
+ }
21
+ async tryOrigins(task) {
22
+ const origins = this.assertLocator().origins;
23
+ let err = undefined;
24
+ for (const origin of origins) {
25
+ try {
26
+ const apiClient = new apiv2_1.Client({ urlPrefix: origin, auth: false });
27
+ return await task(apiClient, origin);
28
+ }
29
+ catch (e) {
30
+ if (!err) {
31
+ err = e;
32
+ }
33
+ }
34
+ }
35
+ throw err !== null && err !== void 0 ? err : new Error("Cannot find working hub origin. Tried:" + origins.join(" "));
18
36
  }
19
37
  async getEmulators() {
20
- const apiClient = new apiv2_1.Client({ urlPrefix: this.origin, auth: false });
21
- const res = await apiClient.get(hub_1.EmulatorHub.PATH_EMULATORS);
38
+ const res = await this.tryOrigins((client) => client.get(hub_1.EmulatorHub.PATH_EMULATORS));
22
39
  return res.body;
23
40
  }
24
41
  async postExport(options) {
25
- const apiClient = new apiv2_1.Client({ urlPrefix: this.origin, auth: false });
42
+ const origin = await this.getStatus();
43
+ const apiClient = new apiv2_1.Client({ urlPrefix: origin, auth: false });
26
44
  await apiClient.post(hub_1.EmulatorHub.PATH_EXPORT, options);
27
45
  }
28
- get origin() {
29
- const locator = this.assertLocator();
30
- return `http://${locator.host}:${locator.port}`;
31
- }
32
46
  assertLocator() {
33
47
  if (this.locator === undefined) {
34
48
  throw new error_1.FirebaseError(`Cannot contact the Emulator Hub for project ${this.projectId}`);
@@ -12,7 +12,6 @@ const error_1 = require("../error");
12
12
  const hub_1 = require("./hub");
13
13
  const downloadableEmulators_1 = require("./downloadableEmulators");
14
14
  const rimraf = require("rimraf");
15
- const apiv2_1 = require("../apiv2");
16
15
  const track_1 = require("../track");
17
16
  class HubExport {
18
17
  constructor(projectId, options) {
@@ -83,20 +82,16 @@ class HubExport {
83
82
  initiated_by: this.options.initiatedBy,
84
83
  emulator_name: types_1.Emulators.FIRESTORE,
85
84
  });
86
- const firestoreInfo = registry_1.EmulatorRegistry.get(types_1.Emulators.FIRESTORE).getInfo();
87
- const firestoreHost = `http://${registry_1.EmulatorRegistry.getInfoHostString(firestoreInfo)}`;
88
85
  const firestoreExportBody = {
89
86
  database: `projects/${this.projectId}/databases/(default)`,
90
87
  export_directory: this.tmpDir,
91
88
  export_name: metadata.firestore.path,
92
89
  };
93
- const client = new apiv2_1.Client({ urlPrefix: firestoreHost, auth: false });
94
- await client.post(`/emulator/v1/projects/${this.projectId}:export`, firestoreExportBody);
90
+ await registry_1.EmulatorRegistry.client(types_1.Emulators.FIRESTORE).post(`/emulator/v1/projects/${this.projectId}:export`, firestoreExportBody);
95
91
  }
96
92
  async exportDatabase(metadata) {
97
93
  const databaseEmulator = registry_1.EmulatorRegistry.get(types_1.Emulators.DATABASE);
98
- const databaseAddr = `http://${registry_1.EmulatorRegistry.getInfoHostString(databaseEmulator.getInfo())}`;
99
- const client = new apiv2_1.Client({ urlPrefix: databaseAddr, auth: true });
94
+ const client = registry_1.EmulatorRegistry.client(types_1.Emulators.DATABASE, { auth: true });
100
95
  const inspectURL = `/.inspect/databases.json`;
101
96
  const inspectRes = await client.get(inspectURL, {
102
97
  queryParams: { ns: this.projectId },
@@ -174,19 +169,16 @@ class HubExport {
174
169
  }, configFile);
175
170
  }
176
171
  async exportStorage(metadata) {
177
- const storageEmulator = registry_1.EmulatorRegistry.get(types_1.Emulators.STORAGE);
178
172
  const storageExportPath = path.join(this.tmpDir, metadata.storage.path);
179
173
  if (fs.existsSync(storageExportPath)) {
180
174
  fse.removeSync(storageExportPath);
181
175
  }
182
176
  fs.mkdirSync(storageExportPath, { recursive: true });
183
- const storageHost = `http://${registry_1.EmulatorRegistry.getInfoHostString(storageEmulator.getInfo())}`;
184
177
  const storageExportBody = {
185
178
  path: storageExportPath,
186
179
  initiatedBy: this.options.initiatedBy,
187
180
  };
188
- const client = new apiv2_1.Client({ urlPrefix: storageHost, auth: false });
189
- const res = await client.request({
181
+ const res = await registry_1.EmulatorRegistry.client(types_1.Emulators.STORAGE).request({
190
182
  method: "POST",
191
183
  path: "/internal/export",
192
184
  headers: { "Content-Type": "application/json" },