firebase-tools 10.6.0 → 10.7.0

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 (83) hide show
  1. package/lib/command.js +4 -4
  2. package/lib/commands/deploy.js +1 -1
  3. package/lib/commands/emulators-start.js +7 -2
  4. package/lib/commands/ext-configure.js +15 -5
  5. package/lib/commands/ext-export.js +6 -5
  6. package/lib/commands/ext-install.js +28 -44
  7. package/lib/commands/ext-update.js +9 -1
  8. package/lib/commands/functions-delete.js +2 -5
  9. package/lib/commands/hosting-channel-deploy.js +2 -2
  10. package/lib/deploy/database/deploy.js +4 -0
  11. package/lib/deploy/database/index.js +1 -0
  12. package/lib/deploy/extensions/deploy.js +4 -4
  13. package/lib/deploy/extensions/deploymentSummary.js +8 -5
  14. package/lib/deploy/extensions/planner.js +36 -9
  15. package/lib/deploy/extensions/prepare.js +1 -1
  16. package/lib/deploy/extensions/secrets.js +2 -2
  17. package/lib/deploy/extensions/tasks.js +60 -21
  18. package/lib/deploy/functions/backend.js +17 -2
  19. package/lib/deploy/functions/build.js +162 -0
  20. package/lib/deploy/functions/checkIam.js +6 -5
  21. package/lib/deploy/functions/deploy.js +14 -15
  22. package/lib/deploy/functions/ensure.js +4 -4
  23. package/lib/deploy/functions/functionsDeployHelper.js +54 -23
  24. package/lib/deploy/functions/prepare.js +85 -42
  25. package/lib/deploy/functions/prepareFunctionsUpload.js +16 -21
  26. package/lib/deploy/functions/pricing.js +6 -3
  27. package/lib/deploy/functions/prompts.js +1 -7
  28. package/lib/deploy/functions/release/fabricator.js +43 -2
  29. package/lib/deploy/functions/release/index.js +10 -6
  30. package/lib/deploy/functions/release/planner.js +9 -6
  31. package/lib/deploy/functions/release/reporter.js +14 -11
  32. package/lib/deploy/functions/runtimes/discovery/parsing.js +12 -6
  33. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +50 -3
  34. package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +3 -3
  35. package/lib/deploy/functions/runtimes/node/parseTriggers.js +24 -5
  36. package/lib/deploy/functions/runtimes/node/versioning.js +2 -2
  37. package/lib/deploy/functions/services/auth.js +95 -0
  38. package/lib/deploy/functions/services/index.js +41 -21
  39. package/lib/deploy/functions/validate.js +8 -5
  40. package/lib/deploy/hosting/args.js +2 -0
  41. package/lib/deploy/hosting/convertConfig.js +37 -8
  42. package/lib/deploy/hosting/deploy.js +3 -3
  43. package/lib/deploy/hosting/prepare.js +2 -2
  44. package/lib/deploy/hosting/release.js +6 -2
  45. package/lib/deploy/index.js +82 -93
  46. package/lib/deploy/remoteconfig/deploy.js +4 -0
  47. package/lib/deploy/remoteconfig/index.js +3 -1
  48. package/lib/emulator/auth/operations.js +5 -0
  49. package/lib/emulator/auth/utils.js +3 -25
  50. package/lib/emulator/controller.js +5 -5
  51. package/lib/emulator/downloadableEmulators.js +39 -23
  52. package/lib/emulator/extensions/validation.js +2 -2
  53. package/lib/emulator/extensionsEmulator.js +85 -21
  54. package/lib/emulator/functionsEmulator.js +79 -7
  55. package/lib/emulator/functionsEmulatorShared.js +20 -1
  56. package/lib/emulator/registry.js +34 -12
  57. package/lib/emulator/storage/apis/firebase.js +7 -2
  58. package/lib/emulator/storage/apis/gcloud.js +6 -3
  59. package/lib/emulator/storage/files.js +9 -1
  60. package/lib/ensureApiEnabled.js +8 -4
  61. package/lib/extensions/changelog.js +1 -1
  62. package/lib/extensions/emulator/optionsHelper.js +4 -3
  63. package/lib/extensions/emulator/specHelper.js +7 -1
  64. package/lib/extensions/extensionsHelper.js +30 -24
  65. package/lib/extensions/manifest.js +27 -7
  66. package/lib/extensions/paramHelper.js +5 -5
  67. package/lib/extensions/provisioningHelper.js +2 -2
  68. package/lib/extensions/warnings.js +3 -3
  69. package/lib/functions/events/index.js +7 -0
  70. package/lib/functions/events/v1.js +6 -0
  71. package/lib/functions/projectConfig.js +24 -3
  72. package/lib/gcp/cloudfunctions.js +31 -5
  73. package/lib/gcp/cloudfunctionsv2.js +27 -2
  74. package/lib/gcp/identityPlatform.js +44 -0
  75. package/lib/gcp/secretManager.js +1 -1
  76. package/lib/metaprogramming.js +2 -0
  77. package/lib/previews.js +1 -1
  78. package/lib/serve/hosting.js +25 -12
  79. package/lib/serve/index.js +6 -0
  80. package/lib/track.js +15 -21
  81. package/npm-shrinkwrap.json +44 -2
  82. package/package.json +4 -1
  83. package/schema/firebase-config.json +6 -0
@@ -6,7 +6,7 @@ const clc = require("cli-color");
6
6
  const fs = require("fs");
7
7
  const path = require("path");
8
8
  const logger_1 = require("../logger");
9
- const track = require("../track");
9
+ const track_1 = require("../track");
10
10
  const utils = require("../utils");
11
11
  const registry_1 = require("./registry");
12
12
  const types_1 = require("./types");
@@ -95,7 +95,7 @@ async function getAndCheckAddress(emulator, options) {
95
95
  }
96
96
  async function startEmulator(instance) {
97
97
  const name = instance.getName();
98
- void track("Emulator Run", name);
98
+ void (0, track_1.track)("Emulator Run", name);
99
99
  await registry_1.EmulatorRegistry.start(instance);
100
100
  }
101
101
  exports.startEmulator = startEmulator;
@@ -248,7 +248,7 @@ async function startAll(options, showUI = true) {
248
248
  if (shouldStart(options, types_1.Emulators.HUB)) {
249
249
  const hubAddr = await getAndCheckAddress(types_1.Emulators.HUB, options);
250
250
  const hub = new hub_1.EmulatorHub(Object.assign({ projectId }, hubAddr));
251
- void track("emulators:start", "hub");
251
+ void (0, track_1.track)("emulators:start", "hub");
252
252
  await startEmulator(hub);
253
253
  }
254
254
  let exportMetadata = {
@@ -294,8 +294,8 @@ async function startAll(options, showUI = true) {
294
294
  const extensionsBackends = await extensionEmulator.getExtensionBackends();
295
295
  const filteredExtensionsBackends = extensionEmulator.filterUnemulatedTriggers(options, extensionsBackends);
296
296
  emulatableBackends.push(...filteredExtensionsBackends);
297
- void track("Emulator Run", types_1.Emulators.EXTENSIONS);
298
- registry_1.EmulatorRegistry.registerExtensionsEmulator();
297
+ void (0, track_1.track)("Emulator Run", types_1.Emulators.EXTENSIONS);
298
+ await startEmulator(extensionEmulator);
299
299
  }
300
300
  if (emulatableBackends.length) {
301
301
  const functionsLogger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.FUNCTIONS);
@@ -29,13 +29,13 @@ exports.DownloadDetails = {
29
29
  },
30
30
  },
31
31
  firestore: {
32
- downloadPath: path.join(CACHE_DIR, "cloud-firestore-emulator-v1.14.1.jar"),
33
- version: "1.14.1",
32
+ downloadPath: path.join(CACHE_DIR, "cloud-firestore-emulator-v1.14.3.jar"),
33
+ version: "1.14.3",
34
34
  opts: {
35
35
  cacheDir: CACHE_DIR,
36
- remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.14.1.jar",
37
- expectedSize: 60416634,
38
- expectedChecksum: "33cffe8065d4250816f257eb19245932",
36
+ remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.14.3.jar",
37
+ expectedSize: 60442855,
38
+ expectedChecksum: "63517534875818689639ee5dee57dd52",
39
39
  namePrefix: "cloud-firestore-emulator",
40
40
  },
41
41
  },
@@ -50,15 +50,15 @@ exports.DownloadDetails = {
50
50
  namePrefix: "cloud-storage-rules-emulator",
51
51
  },
52
52
  },
53
- ui: previews_1.previews.emulatoruisnapshot
53
+ ui: previews_1.previews.extensionsemulator
54
54
  ? {
55
- version: "SNAPSHOT",
56
- downloadPath: path.join(CACHE_DIR, "ui-vSNAPSHOT.zip"),
57
- unzipDir: path.join(CACHE_DIR, "ui-vSNAPSHOT"),
58
- binaryPath: path.join(CACHE_DIR, "ui-vSNAPSHOT", "server.bundle.js"),
55
+ version: "EXTENSIONS",
56
+ downloadPath: path.join(CACHE_DIR, "ui-vEXTENSIONS.zip"),
57
+ unzipDir: path.join(CACHE_DIR, "ui-vEXTENSIONS"),
58
+ binaryPath: path.join(CACHE_DIR, "ui-vEXTENSIONS", "server.bundle.js"),
59
59
  opts: {
60
60
  cacheDir: CACHE_DIR,
61
- remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-vSNAPSHOT.zip",
61
+ remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-vEXTENSIONS.zip",
62
62
  expectedSize: -1,
63
63
  expectedChecksum: "",
64
64
  skipCache: true,
@@ -66,19 +66,35 @@ exports.DownloadDetails = {
66
66
  namePrefix: "ui",
67
67
  },
68
68
  }
69
- : {
70
- version: "1.6.5",
71
- downloadPath: path.join(CACHE_DIR, "ui-v1.6.5.zip"),
72
- unzipDir: path.join(CACHE_DIR, "ui-v1.6.5"),
73
- binaryPath: path.join(CACHE_DIR, "ui-v1.6.5", "server.bundle.js"),
74
- opts: {
75
- cacheDir: CACHE_DIR,
76
- remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.6.5.zip",
77
- expectedSize: 3816994,
78
- expectedChecksum: "92dfff4b2ef8ab616e8a60cc93e0a00b",
79
- namePrefix: "ui",
69
+ : previews_1.previews.emulatoruisnapshot
70
+ ? {
71
+ version: "SNAPSHOT",
72
+ downloadPath: path.join(CACHE_DIR, "ui-vSNAPSHOT.zip"),
73
+ unzipDir: path.join(CACHE_DIR, "ui-vSNAPSHOT"),
74
+ binaryPath: path.join(CACHE_DIR, "ui-vSNAPSHOT", "server.bundle.js"),
75
+ opts: {
76
+ cacheDir: CACHE_DIR,
77
+ remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-vSNAPSHOT.zip",
78
+ expectedSize: -1,
79
+ expectedChecksum: "",
80
+ skipCache: true,
81
+ skipChecksumAndSize: true,
82
+ namePrefix: "ui",
83
+ },
84
+ }
85
+ : {
86
+ version: "1.6.5",
87
+ downloadPath: path.join(CACHE_DIR, "ui-v1.6.5.zip"),
88
+ unzipDir: path.join(CACHE_DIR, "ui-v1.6.5"),
89
+ binaryPath: path.join(CACHE_DIR, "ui-v1.6.5", "server.bundle.js"),
90
+ opts: {
91
+ cacheDir: CACHE_DIR,
92
+ remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.6.5.zip",
93
+ expectedSize: 3816994,
94
+ expectedChecksum: "92dfff4b2ef8ab616e8a60cc93e0a00b",
95
+ namePrefix: "ui",
96
+ },
80
97
  },
81
- },
82
98
  pubsub: {
83
99
  downloadPath: path.join(CACHE_DIR, "pubsub-emulator-0.1.0.zip"),
84
100
  version: "0.1.0",
@@ -17,8 +17,8 @@ async function getUnemulatedAPIs(projectId, instances) {
17
17
  var _a;
18
18
  const unemulatedAPIs = {};
19
19
  for (const i of instances) {
20
- const extensionVersion = await planner.getExtensionVersion(i);
21
- for (const api of (_a = extensionVersion.spec.apis) !== null && _a !== void 0 ? _a : []) {
20
+ const extensionSpec = await planner.getExtensionSpec(i);
21
+ for (const api of (_a = extensionSpec.apis) !== null && _a !== void 0 ? _a : []) {
22
22
  if (!EMULATED_APIS.includes(api.apiName)) {
23
23
  if (unemulatedAPIs[api.apiName]) {
24
24
  unemulatedAPIs[api.apiName].instanceIds.push(i.instanceId);
@@ -18,13 +18,37 @@ const validation_1 = require("./extensions/validation");
18
18
  const ensureApiEnabled_1 = require("../ensureApiEnabled");
19
19
  const shortenUrl_1 = require("../shortenUrl");
20
20
  const constants_1 = require("./constants");
21
+ const registry_1 = require("./registry");
21
22
  class ExtensionsEmulator {
22
23
  constructor(args) {
23
24
  this.want = [];
25
+ this.backends = [];
24
26
  this.logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.EXTENSIONS);
25
27
  this.pendingDownloads = new Map();
26
28
  this.args = args;
27
29
  }
30
+ start() {
31
+ this.logger.logLabeled("DEBUG", "Extensions", "Started Extensions emulator, this is a noop.");
32
+ return Promise.resolve();
33
+ }
34
+ stop() {
35
+ this.logger.logLabeled("DEBUG", "Extensions", "Stopping Extensions emulator, this is a noop.");
36
+ return Promise.resolve();
37
+ }
38
+ connect() {
39
+ this.logger.logLabeled("DEBUG", "Extensions", "Connecting Extensions emulator, this is a noop.");
40
+ return Promise.resolve();
41
+ }
42
+ getInfo() {
43
+ const info = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.FUNCTIONS);
44
+ if (!info) {
45
+ throw new error_1.FirebaseError("Extensions Emulator is running but Functions emulator is not. This should never happen.");
46
+ }
47
+ return info;
48
+ }
49
+ getName() {
50
+ return types_1.Emulators.EXTENSIONS;
51
+ }
28
52
  async readManifest() {
29
53
  var _a;
30
54
  this.want = await planner.want({
@@ -37,22 +61,30 @@ class ExtensionsEmulator {
37
61
  });
38
62
  }
39
63
  async ensureSourceCode(instance) {
40
- if (!instance.ref) {
41
- throw new error_1.FirebaseError(`No ref found for ${instance.instanceId}. Emulating local extensions is not yet supported.`);
64
+ if (instance.localPath) {
65
+ if (!this.hasValidSource({ path: instance.localPath, extTarget: instance.localPath })) {
66
+ throw new error_1.FirebaseError(`Tried to emulate local extension at ${instance.localPath}, but it was missing required files.`);
67
+ }
68
+ return instance.localPath;
42
69
  }
43
- const ref = (0, refs_1.toExtensionVersionRef)(instance.ref);
44
- const cacheDir = process.env.FIREBASE_EXTENSIONS_CACHE_PATH ||
45
- path.join(os.homedir(), ".cache", "firebase", "extensions");
46
- const sourceCodePath = path.join(cacheDir, ref);
47
- if (this.pendingDownloads.get(ref)) {
48
- await this.pendingDownloads.get(ref);
70
+ else if (instance.ref) {
71
+ const ref = (0, refs_1.toExtensionVersionRef)(instance.ref);
72
+ const cacheDir = process.env.FIREBASE_EXTENSIONS_CACHE_PATH ||
73
+ path.join(os.homedir(), ".cache", "firebase", "extensions");
74
+ const sourceCodePath = path.join(cacheDir, ref);
75
+ if (this.pendingDownloads.get(ref)) {
76
+ await this.pendingDownloads.get(ref);
77
+ }
78
+ if (!this.hasValidSource({ path: sourceCodePath, extTarget: ref })) {
79
+ const promise = this.downloadSource(instance, ref, sourceCodePath);
80
+ this.pendingDownloads.set(ref, promise);
81
+ await promise;
82
+ }
83
+ return sourceCodePath;
49
84
  }
50
- if (!this.hasValidSource({ path: sourceCodePath, extRef: ref })) {
51
- const promise = this.downloadSource(instance, ref, sourceCodePath);
52
- this.pendingDownloads.set(ref, promise);
53
- await promise;
85
+ else {
86
+ throw new error_1.FirebaseError("Tried to emulate an extension instance without a ref or localPath. This should never happen.");
54
87
  }
55
- return sourceCodePath;
56
88
  }
57
89
  async downloadSource(instance, ref, sourceCodePath) {
58
90
  const extensionVersion = await planner.getExtensionVersion(instance);
@@ -71,7 +103,7 @@ class ExtensionsEmulator {
71
103
  for (const requiredFile of requiredFiles) {
72
104
  const f = path.join(args.path, requiredFile);
73
105
  if (!fs.existsSync(f)) {
74
- emulatorLogger_1.EmulatorLogger.forExtension({ ref: args.extRef }).logLabeled("BULLET", "extensions", `Detected invalid source code for ${args.extRef}, expected to find ${f}`);
106
+ emulatorLogger_1.EmulatorLogger.forExtension({ ref: args.extTarget }).logLabeled("BULLET", "extensions", `Detected invalid source code for ${args.extTarget}, expected to find ${f}`);
75
107
  return false;
76
108
  }
77
109
  }
@@ -92,27 +124,32 @@ class ExtensionsEmulator {
92
124
  async getExtensionBackends() {
93
125
  await this.readManifest();
94
126
  await this.checkAndWarnAPIs(this.want);
95
- return Promise.all(this.want.map((i) => {
127
+ this.backends = await Promise.all(this.want.map((i) => {
96
128
  return this.toEmulatableBackend(i);
97
129
  }));
130
+ return this.backends;
98
131
  }
99
132
  async toEmulatableBackend(instance) {
100
133
  const extensionDir = await this.ensureSourceCode(instance);
101
134
  const functionsDir = path.join(extensionDir, "functions");
102
135
  const env = Object.assign(this.autoPopulatedParams(instance), instance.params);
103
- const { extensionTriggers, nodeMajorVersion, nonSecretEnv, secretEnvVariables } = await (0, optionsHelper_1.getExtensionFunctionInfo)(extensionDir, instance.instanceId, env);
104
- const extension = await planner.getExtension(instance);
105
- const extensionVersion = await planner.getExtensionVersion(instance);
106
- return {
136
+ const { extensionTriggers, nodeMajorVersion, nonSecretEnv, secretEnvVariables } = await (0, optionsHelper_1.getExtensionFunctionInfo)(instance, env);
137
+ const emulatableBackend = {
107
138
  functionsDir,
108
139
  env: nonSecretEnv,
109
140
  secretEnv: secretEnvVariables,
110
141
  predefinedTriggers: extensionTriggers,
111
142
  nodeMajorVersion: nodeMajorVersion,
112
143
  extensionInstanceId: instance.instanceId,
113
- extension,
114
- extensionVersion,
115
144
  };
145
+ if (instance.ref) {
146
+ emulatableBackend.extension = await planner.getExtension(instance);
147
+ emulatableBackend.extensionVersion = await planner.getExtensionVersion(instance);
148
+ }
149
+ else if (instance.localPath) {
150
+ emulatableBackend.extensionSpec = await planner.getExtensionSpec(instance);
151
+ }
152
+ return emulatableBackend;
116
153
  }
117
154
  autoPopulatedParams(instance) {
118
155
  const projectId = this.args.projectId;
@@ -174,5 +211,32 @@ class ExtensionsEmulator {
174
211
  }
175
212
  return filteredBackends;
176
213
  }
214
+ extensionDetailsUILink(backend) {
215
+ const uiInfo = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.UI);
216
+ if (!uiInfo || !backend.extensionInstanceId) {
217
+ return "";
218
+ }
219
+ const uiUrl = registry_1.EmulatorRegistry.getInfoHostString(uiInfo);
220
+ return clc.underline(clc.bold(`http://${uiUrl}/${types_1.Emulators.EXTENSIONS}/${backend.extensionInstanceId}`));
221
+ }
222
+ extensionsInfoTable(options) {
223
+ var _a;
224
+ const filtedBackends = this.filterUnemulatedTriggers(options, this.backends);
225
+ const uiRunning = registry_1.EmulatorRegistry.isRunning(types_1.Emulators.UI);
226
+ const tableHead = ["Extension Instance Name", "Extension Ref"];
227
+ if (uiRunning) {
228
+ tableHead.push("View in Emulator UI");
229
+ }
230
+ const table = new Table({ head: tableHead, style: { head: ["yellow"] } });
231
+ for (const b of filtedBackends) {
232
+ if (b.extensionInstanceId) {
233
+ const tableEntry = [b.extensionInstanceId, ((_a = b.extensionVersion) === null || _a === void 0 ? void 0 : _a.ref) || "Local Extension"];
234
+ if (uiRunning)
235
+ tableEntry.push(this.extensionDetailsUILink(b));
236
+ table.push(tableEntry);
237
+ }
238
+ }
239
+ return table.toString();
240
+ }
177
241
  }
178
242
  exports.ExtensionsEmulator = ExtensionsEmulator;
@@ -13,7 +13,7 @@ const url_1 = require("url");
13
13
  const events_1 = require("events");
14
14
  const api = require("../api");
15
15
  const logger_1 = require("../logger");
16
- const track = require("../track");
16
+ const track_1 = require("../track");
17
17
  const constants_1 = require("./constants");
18
18
  const types_1 = require("./types");
19
19
  const chokidar = require("chokidar");
@@ -34,6 +34,7 @@ const secretManager_1 = require("../gcp/secretManager");
34
34
  const runtimes = require("../deploy/functions/runtimes");
35
35
  const backend = require("../deploy/functions/backend");
36
36
  const functionsEnv = require("../functions/env");
37
+ const v1_1 = require("../functions/events/v1");
37
38
  const EVENT_INVOKE = "functions:invoke";
38
39
  const DATABASE_PATH_PATTERN = new RegExp("^projects/[^/]+/instances/([^/]+)/refs(/.*)$");
39
40
  class FunctionsEmulator {
@@ -54,6 +55,16 @@ class FunctionsEmulator {
54
55
  : types_1.FunctionsExecutionMode.AUTO;
55
56
  this.workerPool = new functionsRuntimeWorker_1.RuntimeWorkerPool(mode);
56
57
  this.workQueue = new workQueue_1.WorkQueue(mode);
58
+ this.blockingFunctionsConfig = {
59
+ triggers: {
60
+ beforeCreate: {
61
+ functionUri: "",
62
+ },
63
+ beforeSignIn: {
64
+ functionUri: "",
65
+ },
66
+ },
67
+ };
57
68
  }
58
69
  static getHttpFunctionUrl(host, port, projectId, name, region) {
59
70
  return `http://${host}:${port}/${projectId}/${region}/${name}`;
@@ -229,6 +240,7 @@ class FunctionsEmulator {
229
240
  loadTriggerPromises.push(this.loadTriggers(backend, true));
230
241
  }
231
242
  await Promise.all(loadTriggerPromises);
243
+ await this.performPostLoadOperations();
232
244
  return;
233
245
  }
234
246
  async stop() {
@@ -271,6 +283,7 @@ class FunctionsEmulator {
271
283
  logger_1.logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`);
272
284
  const discoveredBackend = await runtimeDelegate.discoverSpec(runtimeConfig, Object.assign(Object.assign(Object.assign(Object.assign({}, this.getSystemEnvs()), this.getEmulatorEnvs()), { FIREBASE_CONFIG: this.getFirebaseConfig() }), emulatableBackend.env));
273
285
  const endpoints = backend.allEndpoints(discoveredBackend);
286
+ (0, functionsEmulatorShared_1.prepareEndpoints)(endpoints);
274
287
  triggerDefinitions = (0, functionsEmulatorShared_1.emulatedFunctionsFromEndpoints)(endpoints);
275
288
  }
276
289
  const toSetup = triggerDefinitions.filter((definition) => {
@@ -327,6 +340,11 @@ class FunctionsEmulator {
327
340
  break;
328
341
  }
329
342
  }
343
+ else if (definition.blockingTrigger) {
344
+ const { host, port } = this.getInfo();
345
+ url = FunctionsEmulator.getHttpFunctionUrl(host, port, this.args.projectId, definition.name, definition.region);
346
+ added = this.addBlockingTrigger(url, definition.blockingTrigger);
347
+ }
330
348
  else {
331
349
  this.logger.log("WARN", `Unsupported function type on ${definition.name}. Expected either httpsTrigger or eventTrigger.`);
332
350
  }
@@ -350,6 +368,34 @@ class FunctionsEmulator {
350
368
  this.startRuntime(emulatableBackend, { nodeBinary: emulatableBackend.nodeBinary });
351
369
  }
352
370
  }
371
+ async performPostLoadOperations() {
372
+ var _a, _b, _c, _d;
373
+ if (((_b = (_a = this.blockingFunctionsConfig.triggers) === null || _a === void 0 ? void 0 : _a.beforeCreate) === null || _b === void 0 ? void 0 : _b.functionUri) === "" &&
374
+ ((_d = (_c = this.blockingFunctionsConfig.triggers) === null || _c === void 0 ? void 0 : _c.beforeSignIn) === null || _d === void 0 ? void 0 : _d.functionUri) === "") {
375
+ return;
376
+ }
377
+ const authEmu = registry_1.EmulatorRegistry.get(types_1.Emulators.AUTH);
378
+ if (!authEmu) {
379
+ return;
380
+ }
381
+ const path = `/identitytoolkit.googleapis.com/v2/projects/${this.getProjectId()}/config?updateMask=blockingFunctions`;
382
+ try {
383
+ await api.request("PATCH", path, {
384
+ origin: `http://${registry_1.EmulatorRegistry.getInfoHostString(authEmu.getInfo())}`,
385
+ headers: {
386
+ Authorization: "Bearer owner",
387
+ },
388
+ data: {
389
+ blockingFunctions: this.blockingFunctionsConfig,
390
+ },
391
+ json: true,
392
+ });
393
+ }
394
+ catch (err) {
395
+ this.logger.log("WARN", "Error updating blocking functions config to the auth emulator: " + err);
396
+ throw err;
397
+ }
398
+ }
353
399
  addRealtimeDatabaseTrigger(projectId, key, eventTrigger) {
354
400
  const databaseEmu = registry_1.EmulatorRegistry.get(types_1.Emulators.DATABASE);
355
401
  if (!databaseEmu) {
@@ -416,11 +462,10 @@ class FunctionsEmulator {
416
462
  });
417
463
  }
418
464
  async addPubsubTrigger(triggerName, key, eventTrigger, signatureType, schedule) {
419
- const pubsubPort = registry_1.EmulatorRegistry.getPort(types_1.Emulators.PUBSUB);
420
- if (!pubsubPort) {
465
+ const pubsubEmulator = registry_1.EmulatorRegistry.get(types_1.Emulators.PUBSUB);
466
+ if (!pubsubEmulator) {
421
467
  return false;
422
468
  }
423
- const pubsubEmulator = registry_1.EmulatorRegistry.get(types_1.Emulators.PUBSUB);
424
469
  logger_1.logger.debug(`addPubsubTrigger`, JSON.stringify({ eventTrigger }));
425
470
  const resource = eventTrigger.resource;
426
471
  let topic;
@@ -458,6 +503,31 @@ class FunctionsEmulator {
458
503
  this.multicastTriggers[eventTriggerId] = triggers;
459
504
  return true;
460
505
  }
506
+ addBlockingTrigger(url, blockingTrigger) {
507
+ logger_1.logger.debug(`addBlockingTrigger`, JSON.stringify({ blockingTrigger }));
508
+ const eventType = blockingTrigger.eventType;
509
+ if (v1_1.AUTH_BLOCKING_EVENTS.includes(eventType)) {
510
+ if (blockingTrigger.eventType === v1_1.BEFORE_CREATE_EVENT) {
511
+ this.blockingFunctionsConfig.triggers = Object.assign(Object.assign({}, this.blockingFunctionsConfig.triggers), { beforeCreate: {
512
+ functionUri: url,
513
+ } });
514
+ }
515
+ else {
516
+ this.blockingFunctionsConfig.triggers = Object.assign(Object.assign({}, this.blockingFunctionsConfig.triggers), { beforeSignIn: {
517
+ functionUri: url,
518
+ } });
519
+ }
520
+ this.blockingFunctionsConfig.forwardInboundCredentials = {
521
+ accessToken: blockingTrigger.options.accessToken,
522
+ idToken: blockingTrigger.options.idToken,
523
+ refreshToken: blockingTrigger.options.refreshToken,
524
+ };
525
+ }
526
+ else {
527
+ return false;
528
+ }
529
+ return true;
530
+ }
461
531
  getProjectId() {
462
532
  return this.args.projectId;
463
533
  }
@@ -781,7 +851,9 @@ class FunctionsEmulator {
781
851
  for (const backend of this.args.emulatableBackends) {
782
852
  loadTriggerPromises.push(this.loadTriggers(backend));
783
853
  }
784
- return Promise.all(loadTriggerPromises);
854
+ await Promise.all(loadTriggerPromises);
855
+ await this.performPostLoadOperations();
856
+ return;
785
857
  }
786
858
  async handleBackgroundTrigger(projectId, triggerKey, proto) {
787
859
  const record = this.getTriggerRecordByKey(triggerKey);
@@ -809,7 +881,7 @@ class FunctionsEmulator {
809
881
  reject({ code: 500, body: el.text });
810
882
  }
811
883
  });
812
- void track(EVENT_INVOKE, (0, functionsEmulatorShared_1.getFunctionService)(trigger));
884
+ void (0, track_1.track)(EVENT_INVOKE, (0, functionsEmulatorShared_1.getFunctionService)(trigger));
813
885
  worker.waitForDone().then(() => {
814
886
  resolve({ status: "acknowledged" });
815
887
  });
@@ -884,7 +956,7 @@ class FunctionsEmulator {
884
956
  }
885
957
  });
886
958
  await worker.waitForSocketReady();
887
- void track(EVENT_INVOKE, "https");
959
+ void (0, track_1.track)(EVENT_INVOKE, "https");
888
960
  this.logger.log("DEBUG", `[functions] Runtime ready! Sending request!`);
889
961
  if (!worker.lastArgs) {
890
962
  throw new error_1.FirebaseError("Cannot execute on a worker with no arguments");
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toBackendInfo = exports.getSecretLocalPath = exports.getSignatureType = exports.formatHost = exports.findModuleRoot = exports.waitForBody = exports.getServiceFromEventType = exports.getFunctionService = exports.getTemporarySocketPath = exports.getEmulatedTriggersFromDefinitions = exports.emulatedFunctionsByRegion = exports.emulatedFunctionsFromEndpoints = exports.EmulatedTrigger = exports.HttpConstants = void 0;
3
+ exports.toBackendInfo = exports.getSecretLocalPath = exports.getSignatureType = exports.formatHost = exports.findModuleRoot = exports.waitForBody = exports.getServiceFromEventType = exports.getFunctionService = exports.getTemporarySocketPath = exports.getEmulatedTriggersFromDefinitions = exports.emulatedFunctionsByRegion = exports.emulatedFunctionsFromEndpoints = exports.prepareEndpoints = exports.EmulatedTrigger = exports.HttpConstants = void 0;
4
4
  const _ = require("lodash");
5
5
  const os = require("os");
6
6
  const path = require("path");
@@ -11,6 +11,8 @@ const proto_1 = require("../gcp/proto");
11
11
  const manifest_1 = require("../extensions/manifest");
12
12
  const extensionsHelper_1 = require("../extensions/extensionsHelper");
13
13
  const postinstall_1 = require("./extensions/postinstall");
14
+ const services_1 = require("../deploy/functions/services");
15
+ const prepare_1 = require("../deploy/functions/prepare");
14
16
  const memoryLookup = {
15
17
  "128MB": 128,
16
18
  "256MB": 256,
@@ -44,6 +46,14 @@ class EmulatedTrigger {
44
46
  }
45
47
  }
46
48
  exports.EmulatedTrigger = EmulatedTrigger;
49
+ function prepareEndpoints(endpoints) {
50
+ const bkend = backend.of(...endpoints);
51
+ for (const ep of endpoints) {
52
+ (0, services_1.serviceForEndpoint)(ep).validateTrigger(ep, bkend);
53
+ }
54
+ (0, prepare_1.inferBlockingDetails)(bkend);
55
+ }
56
+ exports.prepareEndpoints = prepareEndpoints;
47
57
  function emulatedFunctionsFromEndpoints(endpoints) {
48
58
  const regionDefinitions = [];
49
59
  for (const endpoint of endpoints) {
@@ -89,6 +99,12 @@ function emulatedFunctionsFromEndpoints(endpoints) {
89
99
  def.eventTrigger = { eventType: "pubsub", resource: "" };
90
100
  def.schedule = endpoint.scheduleTrigger;
91
101
  }
102
+ else if (backend.isBlockingTriggered(endpoint)) {
103
+ def.blockingTrigger = {
104
+ eventType: endpoint.blockingTrigger.eventType,
105
+ options: endpoint.blockingTrigger.options || {},
106
+ };
107
+ }
92
108
  else {
93
109
  }
94
110
  regionDefinitions.push(def);
@@ -136,6 +152,9 @@ function getFunctionService(def) {
136
152
  if (def.eventTrigger) {
137
153
  return (_a = def.eventTrigger.service) !== null && _a !== void 0 ? _a : getServiceFromEventType(def.eventTrigger.eventType);
138
154
  }
155
+ if (def.blockingTrigger) {
156
+ return def.blockingTrigger.eventType;
157
+ }
139
158
  return "unknown";
140
159
  }
141
160
  exports.getFunctionService = getFunctionService;
@@ -14,11 +14,10 @@ class EmulatorRegistry {
14
14
  }
15
15
  this.set(instance.getName(), instance);
16
16
  await instance.start();
17
- const info = instance.getInfo();
18
- await portUtils.waitForPortClosed(info.port, info.host);
19
- }
20
- static registerExtensionsEmulator() {
21
- this.extensionsEmulatorRegistered = true;
17
+ if (instance.getName() !== types_1.Emulators.EXTENSIONS) {
18
+ const info = instance.getInfo();
19
+ await portUtils.waitForPortClosed(info.port, info.host);
20
+ }
22
21
  }
23
22
  static async stop(name) {
24
23
  emulatorLogger_1.EmulatorLogger.forEmulator(name).logLabeled("BULLET", name, `Stopping ${constants_1.Constants.description(name)}`);
@@ -57,7 +56,7 @@ class EmulatorRegistry {
57
56
  }
58
57
  static isRunning(emulator) {
59
58
  if (emulator === types_1.Emulators.EXTENSIONS) {
60
- return this.extensionsEmulatorRegistered && this.isRunning(types_1.Emulators.FUNCTIONS);
59
+ return this.INSTANCES.get(emulator) !== undefined && this.isRunning(types_1.Emulators.FUNCTIONS);
61
60
  }
62
61
  const instance = this.INSTANCES.get(emulator);
63
62
  return instance !== undefined;
@@ -89,12 +88,36 @@ class EmulatorRegistry {
89
88
  return `${host}:${port}`;
90
89
  }
91
90
  }
92
- static getPort(emulator) {
93
- const instance = this.INSTANCES.get(emulator);
94
- if (!instance) {
95
- return undefined;
91
+ static url(emulator, req) {
92
+ const url = new URL("http://unknown/");
93
+ if (req) {
94
+ url.protocol = req.protocol;
95
+ const host = req.headers.host;
96
+ if (host) {
97
+ url.host = host;
98
+ return url;
99
+ }
100
+ }
101
+ const info = EmulatorRegistry.getInfo(emulator);
102
+ if (info) {
103
+ if (info.host === "0.0.0.0") {
104
+ url.hostname = "127.0.0.1";
105
+ }
106
+ else if (info.host === "::") {
107
+ url.hostname = "[::1]";
108
+ }
109
+ else if (info.host.includes(":")) {
110
+ url.hostname = `[${info.host}]`;
111
+ }
112
+ else {
113
+ url.hostname = info.host;
114
+ }
115
+ url.port = info.port.toString();
116
+ }
117
+ else {
118
+ console.warn(`Cannot determine host and port of ${emulator}`);
96
119
  }
97
- return instance.getInfo().port;
120
+ return url;
98
121
  }
99
122
  static set(emulator, instance) {
100
123
  this.INSTANCES.set(emulator, instance);
@@ -104,5 +127,4 @@ class EmulatorRegistry {
104
127
  }
105
128
  }
106
129
  exports.EmulatorRegistry = EmulatorRegistry;
107
- EmulatorRegistry.extensionsEmulatorRegistered = false;
108
130
  EmulatorRegistry.INSTANCES = new Map();
@@ -219,9 +219,13 @@ function createFirebaseEndpoints(emulator) {
219
219
  res.header("x-goog-upload-chunk-granularity", "10000");
220
220
  res.header("x-goog-upload-control-url", "");
221
221
  res.header("x-goog-upload-status", "active");
222
- const emulatorInfo = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE);
223
- res.header("x-goog-upload-url", `http://${req.hostname}:${emulatorInfo === null || emulatorInfo === void 0 ? void 0 : emulatorInfo.port}/v0/b/${bucketId}/o?name=${objectId}&upload_id=${upload.id}&upload_protocol=resumable`);
224
222
  res.header("x-gupload-uploadid", upload.id);
223
+ const uploadUrl = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE, req);
224
+ uploadUrl.pathname = `/v0/b/${bucketId}/o`;
225
+ uploadUrl.searchParams.set("name", objectId);
226
+ uploadUrl.searchParams.set("upload_id", upload.id);
227
+ uploadUrl.searchParams.set("upload_protocol", "resumable");
228
+ res.header("x-goog-upload-url", uploadUrl.toString());
225
229
  return res.sendStatus(200);
226
230
  }
227
231
  if (!req.query.upload_id) {
@@ -306,6 +310,7 @@ function createFirebaseEndpoints(emulator) {
306
310
  }
307
311
  throw err;
308
312
  }
313
+ res.header("x-goog-upload-status", "final");
309
314
  storedMetadata.addDownloadToken(false);
310
315
  return res.status(200).json(new metadata_1.OutgoingFirebaseMetadata(storedMetadata));
311
316
  }
@@ -200,9 +200,12 @@ function createCloudEndpoints(emulator) {
200
200
  metadataRaw: JSON.stringify(req.body),
201
201
  authorization: req.header("authorization"),
202
202
  });
203
- const { host, port } = emulatorInfo;
204
- const uploadUrl = `http://${host}:${port}/upload/storage/v1/b/${req.params.bucketId}/o?name=${name}&uploadType=resumable&upload_id=${upload.id}`;
205
- return res.header("location", uploadUrl).sendStatus(200);
203
+ const uploadUrl = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE, req);
204
+ uploadUrl.pathname = `/upload/storage/v1/b/${req.params.bucketId}/o`;
205
+ uploadUrl.searchParams.set("name", name);
206
+ uploadUrl.searchParams.set("uploadType", "resumable");
207
+ uploadUrl.searchParams.set("upload_id", upload.id);
208
+ return res.header("location", uploadUrl.toString()).sendStatus(200);
206
209
  }
207
210
  let metadataRaw;
208
211
  let dataRaw;
@@ -348,7 +348,11 @@ class StorageLayer {
348
348
  logger_1.logger.warn(`Could not find file "${blobPath}" in storage export.`);
349
349
  continue;
350
350
  }
351
- const decodedBlobPath = decodeURIComponent(blobPath);
351
+ let decodedBlobPath = decodeURIComponent(blobPath);
352
+ const decodedBlobPathSep = getPathSep(decodedBlobPath);
353
+ if (decodedBlobPathSep !== path.sep) {
354
+ decodedBlobPath = decodedBlobPath.split(decodedBlobPathSep).join(path.sep);
355
+ }
352
356
  const blobDiskPath = this._persistence.getDiskPath(decodedBlobPath);
353
357
  const file = new StoredFile(metadata, blobDiskPath);
354
358
  this._files.set(decodedBlobPath, file);
@@ -369,3 +373,7 @@ class StorageLayer {
369
373
  }
370
374
  }
371
375
  exports.StorageLayer = StorageLayer;
376
+ function getPathSep(decodedPath) {
377
+ const firstSepIndex = decodedPath.search(/[^a-z0-9-_.]/g);
378
+ return decodedPath[firstSepIndex];
379
+ }