firebase-tools 10.4.2 → 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 (108) hide show
  1. package/lib/bin/firebase.js +1 -1
  2. package/lib/command.js +4 -4
  3. package/lib/commands/deploy.js +1 -1
  4. package/lib/commands/emulators-start.js +13 -3
  5. package/lib/commands/ext-configure.js +15 -5
  6. package/lib/commands/ext-dev-emulators-start.js +5 -1
  7. package/lib/commands/ext-export.js +6 -5
  8. package/lib/commands/ext-install.js +28 -44
  9. package/lib/commands/ext-update.js +9 -1
  10. package/lib/commands/functions-delete.js +2 -5
  11. package/lib/commands/functions-secrets-destroy.js +23 -3
  12. package/lib/commands/functions-secrets-prune.js +15 -12
  13. package/lib/commands/functions-secrets-set.js +51 -4
  14. package/lib/commands/hosting-channel-deploy.js +2 -2
  15. package/lib/deploy/database/deploy.js +4 -0
  16. package/lib/deploy/database/index.js +1 -0
  17. package/lib/deploy/extensions/deploy.js +4 -4
  18. package/lib/deploy/extensions/deploymentSummary.js +8 -5
  19. package/lib/deploy/extensions/planner.js +36 -9
  20. package/lib/deploy/extensions/prepare.js +1 -1
  21. package/lib/deploy/extensions/secrets.js +2 -2
  22. package/lib/deploy/extensions/tasks.js +60 -21
  23. package/lib/deploy/functions/backend.js +17 -6
  24. package/lib/deploy/functions/build.js +162 -0
  25. package/lib/deploy/functions/checkIam.js +6 -5
  26. package/lib/deploy/functions/deploy.js +14 -15
  27. package/lib/deploy/functions/ensure.js +4 -4
  28. package/lib/deploy/functions/functionsDeployHelper.js +54 -23
  29. package/lib/deploy/functions/prepare.js +92 -39
  30. package/lib/deploy/functions/prepareFunctionsUpload.js +16 -21
  31. package/lib/deploy/functions/pricing.js +6 -3
  32. package/lib/deploy/functions/prompts.js +1 -7
  33. package/lib/deploy/functions/release/fabricator.js +44 -5
  34. package/lib/deploy/functions/release/index.js +31 -6
  35. package/lib/deploy/functions/release/planner.js +10 -8
  36. package/lib/deploy/functions/release/reporter.js +14 -11
  37. package/lib/deploy/functions/runtimes/discovery/parsing.js +12 -6
  38. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +61 -13
  39. package/lib/deploy/functions/runtimes/node/index.js +1 -1
  40. package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +3 -3
  41. package/lib/deploy/functions/runtimes/node/parseTriggers.js +29 -24
  42. package/lib/deploy/functions/runtimes/node/versioning.js +2 -2
  43. package/lib/deploy/functions/services/auth.js +95 -0
  44. package/lib/deploy/functions/services/index.js +41 -21
  45. package/lib/deploy/functions/services/storage.js +1 -6
  46. package/lib/deploy/functions/validate.js +8 -5
  47. package/lib/deploy/hosting/args.js +2 -0
  48. package/lib/deploy/hosting/convertConfig.js +37 -8
  49. package/lib/deploy/hosting/deploy.js +3 -3
  50. package/lib/deploy/hosting/prepare.js +2 -2
  51. package/lib/deploy/hosting/release.js +6 -2
  52. package/lib/deploy/index.js +82 -93
  53. package/lib/deploy/remoteconfig/deploy.js +4 -0
  54. package/lib/deploy/remoteconfig/index.js +3 -1
  55. package/lib/emulator/auth/operations.js +26 -20
  56. package/lib/emulator/auth/state.js +79 -43
  57. package/lib/emulator/auth/utils.js +3 -25
  58. package/lib/emulator/commandUtils.js +72 -2
  59. package/lib/emulator/controller.js +14 -5
  60. package/lib/emulator/downloadableEmulators.js +47 -24
  61. package/lib/emulator/extensions/postinstall.js +41 -0
  62. package/lib/emulator/extensions/validation.js +2 -2
  63. package/lib/emulator/extensionsEmulator.js +85 -21
  64. package/lib/emulator/functionsEmulator.js +79 -7
  65. package/lib/emulator/functionsEmulatorShared.js +36 -21
  66. package/lib/emulator/registry.js +34 -12
  67. package/lib/emulator/shared/request.js +19 -0
  68. package/lib/emulator/storage/apis/firebase.js +32 -35
  69. package/lib/emulator/storage/apis/gcloud.js +84 -66
  70. package/lib/emulator/storage/files.js +56 -52
  71. package/lib/emulator/storage/index.js +23 -3
  72. package/lib/emulator/storage/metadata.js +18 -8
  73. package/lib/emulator/storage/rules/manager.js +7 -17
  74. package/lib/emulator/storage/rules/utils.js +11 -3
  75. package/lib/emulator/storage/server.js +38 -12
  76. package/lib/ensureApiEnabled.js +8 -4
  77. package/lib/extensions/askUserForParam.js +14 -11
  78. package/lib/extensions/changelog.js +1 -1
  79. package/lib/extensions/emulator/optionsHelper.js +9 -10
  80. package/lib/extensions/emulator/specHelper.js +7 -1
  81. package/lib/extensions/emulator/triggerHelper.js +11 -14
  82. package/lib/extensions/extensionsApi.js +2 -1
  83. package/lib/extensions/extensionsHelper.js +30 -24
  84. package/lib/extensions/manifest.js +28 -8
  85. package/lib/extensions/paramHelper.js +19 -13
  86. package/lib/extensions/provisioningHelper.js +2 -2
  87. package/lib/extensions/warnings.js +3 -3
  88. package/lib/functions/env.js +10 -2
  89. package/lib/functions/events/index.js +7 -0
  90. package/lib/functions/events/v1.js +6 -0
  91. package/lib/functions/projectConfig.js +24 -3
  92. package/lib/functions/runtimeConfigExport.js +10 -6
  93. package/lib/functions/secrets.js +99 -6
  94. package/lib/gcp/cloudfunctions.js +37 -18
  95. package/lib/gcp/cloudfunctionsv2.js +41 -25
  96. package/lib/gcp/cloudtasks.js +5 -3
  97. package/lib/gcp/identityPlatform.js +44 -0
  98. package/lib/gcp/secretManager.js +2 -2
  99. package/lib/metaprogramming.js +2 -0
  100. package/lib/previews.js +1 -1
  101. package/lib/serve/hosting.js +25 -12
  102. package/lib/serve/index.js +6 -0
  103. package/lib/track.js +15 -21
  104. package/lib/utils.js +30 -1
  105. package/npm-shrinkwrap.json +44 -2
  106. package/package.json +4 -1
  107. package/schema/firebase-config.json +6 -0
  108. package/lib/emulator/storage/list.js +0 -18
@@ -4,7 +4,7 @@ exports.triggerTag = exports.printAbortedErrors = exports.printErrors = exports.
4
4
  const backend = require("../backend");
5
5
  const clc = require("cli-color");
6
6
  const logger_1 = require("../../../logger");
7
- const track = require("../../../track");
7
+ const track_1 = require("../../../track");
8
8
  const utils = require("../../../utils");
9
9
  const functionsDeployHelper_1 = require("../functionsDeployHelper");
10
10
  class DeploymentError extends Error {
@@ -36,23 +36,23 @@ async function logAndTrackDeployStats(summary) {
36
36
  totalTime += result.durationMs;
37
37
  if (!result.error) {
38
38
  totalSuccesses++;
39
- reports.push(track.track("function_deploy_success", tag, result.durationMs));
39
+ reports.push((0, track_1.track)("function_deploy_success", tag, result.durationMs));
40
40
  }
41
41
  else if (result.error instanceof AbortedDeploymentError) {
42
42
  totalAborts++;
43
- reports.push(track.track("function_deploy_abort", tag, result.durationMs));
43
+ reports.push((0, track_1.track)("function_deploy_abort", tag, result.durationMs));
44
44
  }
45
45
  else {
46
46
  totalErrors++;
47
- reports.push(track.track("function_deploy_failure", tag, result.durationMs));
47
+ reports.push((0, track_1.track)("function_deploy_failure", tag, result.durationMs));
48
48
  }
49
49
  }
50
50
  const regionCountTag = regions.size < 5 ? regions.size.toString() : ">=5";
51
- reports.push(track.track("functions_region_count", regionCountTag, 1));
51
+ reports.push((0, track_1.track)("functions_region_count", regionCountTag, 1));
52
52
  const gcfv1 = summary.results.find((r) => r.endpoint.platform === "gcfv1");
53
53
  const gcfv2 = summary.results.find((r) => r.endpoint.platform === "gcfv2");
54
54
  const tag = gcfv1 && gcfv2 ? "v1+v2" : gcfv1 ? "v1" : "v2";
55
- reports.push(track.track("functions_codebase_deploy", tag, summary.results.length));
55
+ reports.push((0, track_1.track)("functions_codebase_deploy", tag, summary.results.length));
56
56
  const avgTime = totalTime / (totalSuccesses + totalErrors);
57
57
  logger_1.logger.debug(`Total Function Deployment time: ${summary.totalTime}`);
58
58
  logger_1.logger.debug(`${totalErrors + totalSuccesses + totalAborts} Functions Deployed`);
@@ -61,15 +61,15 @@ async function logAndTrackDeployStats(summary) {
61
61
  logger_1.logger.debug(`Average Function Deployment time: ${avgTime}`);
62
62
  if (totalErrors + totalSuccesses > 0) {
63
63
  if (totalErrors === 0) {
64
- reports.push(track.track("functions_deploy_result", "success", totalSuccesses));
64
+ reports.push((0, track_1.track)("functions_deploy_result", "success", totalSuccesses));
65
65
  }
66
66
  else if (totalSuccesses > 0) {
67
- reports.push(track.track("functions_deploy_result", "partial_success", totalSuccesses));
68
- reports.push(track.track("functions_deploy_result", "partial_failure", totalErrors));
69
- reports.push(track.track("functions_deploy_result", "partial_error_ratio", totalErrors / (totalSuccesses + totalErrors)));
67
+ reports.push((0, track_1.track)("functions_deploy_result", "partial_success", totalSuccesses));
68
+ reports.push((0, track_1.track)("functions_deploy_result", "partial_failure", totalErrors));
69
+ reports.push((0, track_1.track)("functions_deploy_result", "partial_error_ratio", totalErrors / (totalSuccesses + totalErrors)));
70
70
  }
71
71
  else {
72
- reports.push(track.track("functions_deploy_result", "failure", totalErrors));
72
+ reports.push((0, track_1.track)("functions_deploy_result", "failure", totalErrors));
73
73
  }
74
74
  }
75
75
  await utils.allSettled(reports);
@@ -171,6 +171,9 @@ function triggerTag(endpoint) {
171
171
  }
172
172
  return `${prefix}.https`;
173
173
  }
174
+ if (backend.isBlockingTriggered(endpoint)) {
175
+ return `${prefix}.blocking`;
176
+ }
174
177
  return endpoint.eventTrigger.eventType;
175
178
  }
176
179
  exports.triggerTag = triggerTag;
@@ -19,31 +19,37 @@ function assertKeyTypes(prefix, yaml, schema) {
19
19
  }
20
20
  for (const [keyAsString, value] of Object.entries(yaml)) {
21
21
  const key = keyAsString;
22
- const fullKey = prefix ? prefix + "." + key : key;
22
+ const fullKey = prefix ? `${prefix}.${keyAsString}` : keyAsString;
23
23
  if (!schema[key] || schema[key] === "omit") {
24
24
  throw new error_1.FirebaseError(`Unexpected key ${fullKey}. You may need to install a newer version of the Firebase CLI.`);
25
25
  }
26
- if (schema[key] === "string") {
26
+ const schemaType = schema[key];
27
+ if (typeof schemaType === "function") {
28
+ if (!schemaType(value)) {
29
+ throw new error_1.FirebaseError(`${Array.isArray(value) ? "array" : typeof value} ${fullKey} failed validation`);
30
+ }
31
+ }
32
+ else if (schemaType === "string") {
27
33
  if (typeof value !== "string") {
28
34
  throw new error_1.FirebaseError(`Expected ${fullKey} to be string; was ${typeof value}`);
29
35
  }
30
36
  }
31
- else if (schema[key] === "number") {
37
+ else if (schemaType === "number") {
32
38
  if (typeof value !== "number") {
33
39
  throw new error_1.FirebaseError(`Expected ${fullKey} to be a number; was ${typeof value}`);
34
40
  }
35
41
  }
36
- else if (schema[key] === "boolean") {
42
+ else if (schemaType === "boolean") {
37
43
  if (typeof value !== "boolean") {
38
44
  throw new error_1.FirebaseError(`Expected ${fullKey} to be a boolean; was ${typeof value}`);
39
45
  }
40
46
  }
41
- else if (schema[key] === "array") {
47
+ else if (schemaType === "array") {
42
48
  if (!Array.isArray(value)) {
43
49
  throw new error_1.FirebaseError(`Expected ${fullKey} to be an array; was ${typeof value}`);
44
50
  }
45
51
  }
46
- else if (schema[key] === "object") {
52
+ else if (schemaType === "object") {
47
53
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
48
54
  throw new error_1.FirebaseError(`Expected ${fullKey} to be an object; was ${typeof value}`);
49
55
  }
@@ -5,6 +5,12 @@ const backend = require("../../backend");
5
5
  const proto_1 = require("../../../../gcp/proto");
6
6
  const parsing_1 = require("./parsing");
7
7
  const error_1 = require("../../../../error");
8
+ const CHANNEL_NAME_REGEX = new RegExp("(projects\\/" +
9
+ "(?<project>(?:\\d+)|(?:[A-Za-z]+[A-Za-z\\d-]*[A-Za-z\\d]?))\\/)?" +
10
+ "locations\\/" +
11
+ "(?<location>[A-Za-z\\d\\-_]+)\\/" +
12
+ "channels\\/" +
13
+ "(?<channel>[A-Za-z\\d\\-_]+)");
8
14
  function backendFromV1Alpha1(yaml, project, region, runtime) {
9
15
  const manifest = JSON.parse(JSON.stringify(yaml));
10
16
  const bkend = backend.empty();
@@ -42,17 +48,17 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
42
48
  const ep = manifest.endpoints[id];
43
49
  (0, parsing_1.assertKeyTypes)(prefix, ep, {
44
50
  region: "array",
45
- platform: "string",
51
+ platform: (platform) => backend.AllFunctionsPlatforms.includes(platform),
46
52
  entryPoint: "string",
47
- availableMemoryMb: "number",
53
+ availableMemoryMb: (mem) => backend.AllMemoryOptions.includes(mem),
48
54
  maxInstances: "number",
49
55
  minInstances: "number",
50
56
  concurrency: "number",
51
57
  serviceAccountEmail: "string",
52
- timeout: "string",
58
+ timeoutSeconds: "number",
53
59
  vpc: "object",
54
60
  labels: "object",
55
- ingressSettings: "string",
61
+ ingressSettings: (setting) => backend.AllIngressSettings.includes(setting),
56
62
  environmentVariables: "object",
57
63
  secretEnvironmentVariables: "array",
58
64
  httpsTrigger: "object",
@@ -60,7 +66,16 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
60
66
  eventTrigger: "object",
61
67
  scheduleTrigger: "object",
62
68
  taskQueueTrigger: "object",
69
+ blockingTrigger: "object",
70
+ cpu: (cpu) => typeof cpu === "number" || cpu === "gcf_gen1",
63
71
  });
72
+ if (ep.vpc) {
73
+ (0, parsing_1.assertKeyTypes)(prefix + ".vpc", ep.vpc, {
74
+ connector: "string",
75
+ egressSettings: (setting) => backend.AllVpcEgressSettings.includes(setting),
76
+ });
77
+ (0, parsing_1.requireKeys)(prefix + ".vpc", ep.vpc, "connector");
78
+ }
64
79
  let triggerCount = 0;
65
80
  if (ep.httpsTrigger) {
66
81
  triggerCount++;
@@ -77,6 +92,9 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
77
92
  if (ep.taskQueueTrigger) {
78
93
  triggerCount++;
79
94
  }
95
+ if (ep.blockingTrigger) {
96
+ triggerCount++;
97
+ }
80
98
  if (!triggerCount) {
81
99
  throw new error_1.FirebaseError("Expected trigger in endpoint " + id);
82
100
  }
@@ -88,16 +106,19 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
88
106
  if (backend.isEventTriggered(ep)) {
89
107
  (0, parsing_1.requireKeys)(prefix + ".eventTrigger", ep.eventTrigger, "eventType", "eventFilters");
90
108
  (0, parsing_1.assertKeyTypes)(prefix + ".eventTrigger", ep.eventTrigger, {
91
- eventFilters: "array",
109
+ eventFilters: "object",
110
+ eventFilterPathPatterns: "object",
92
111
  eventType: "string",
93
112
  retry: "boolean",
94
113
  region: "string",
95
114
  serviceAccountEmail: "string",
115
+ channel: "string",
96
116
  });
97
117
  triggered = { eventTrigger: ep.eventTrigger };
98
- for (const eventFilter of triggered.eventTrigger.eventFilters) {
99
- if (eventFilter.attribute === "topic" && !eventFilter.value.startsWith("projects/")) {
100
- eventFilter.value = `projects/${project}/topics/${eventFilter.value}`;
118
+ (0, proto_1.renameIfPresent)(triggered.eventTrigger, ep.eventTrigger, "channel", "channel", (c) => resolveChannelName(project, c, defaultRegion));
119
+ for (const [k, v] of Object.entries(triggered.eventTrigger.eventFilters)) {
120
+ if (k === "topic" && !v.startsWith("projects/")) {
121
+ triggered.eventTrigger.eventFilters[k] = `projects/${project}/topics/${v}`;
101
122
  }
102
123
  }
103
124
  }
@@ -134,7 +155,6 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
134
155
  });
135
156
  if (ep.taskQueueTrigger.rateLimits) {
136
157
  (0, parsing_1.assertKeyTypes)(prefix + ".taskQueueTrigger.rateLimits", ep.taskQueueTrigger.rateLimits, {
137
- maxBurstSize: "number",
138
158
  maxConcurrentDispatches: "number",
139
159
  maxDispatchesPerSecond: "number",
140
160
  });
@@ -142,14 +162,22 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
142
162
  if (ep.taskQueueTrigger.retryConfig) {
143
163
  (0, parsing_1.assertKeyTypes)(prefix + ".taskQueueTrigger.retryConfig", ep.taskQueueTrigger.retryConfig, {
144
164
  maxAttempts: "number",
145
- maxRetryDuration: "string",
146
- minBackoff: "string",
147
- maxBackoff: "string",
165
+ maxRetrySeconds: "number",
166
+ minBackoffSeconds: "number",
167
+ maxBackoffSeconds: "number",
148
168
  maxDoublings: "number",
149
169
  });
150
170
  }
151
171
  triggered = { taskQueueTrigger: ep.taskQueueTrigger };
152
172
  }
173
+ else if (backend.isBlockingTriggered(ep)) {
174
+ (0, parsing_1.requireKeys)(prefix + ".blockingTrigger", ep.blockingTrigger, "eventType");
175
+ (0, parsing_1.assertKeyTypes)(prefix + ".blockingTrigger", ep.blockingTrigger, {
176
+ eventType: "string",
177
+ options: "object",
178
+ });
179
+ triggered = { blockingTrigger: ep.blockingTrigger };
180
+ }
153
181
  else {
154
182
  throw new error_1.FirebaseError(`Do not recognize trigger type for endpoint ${id}. Try upgrading ` +
155
183
  "firebase-tools with npm install -g firebase-tools@latest");
@@ -159,8 +187,28 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
159
187
  region,
160
188
  project,
161
189
  runtime, entryPoint: ep.entryPoint }, triggered);
162
- (0, proto_1.copyIfPresent)(parsed, ep, "availableMemoryMb", "maxInstances", "minInstances", "concurrency", "serviceAccountEmail", "timeout", "vpc", "labels", "ingressSettings", "environmentVariables");
190
+ (0, proto_1.copyIfPresent)(parsed, ep, "availableMemoryMb", "maxInstances", "minInstances", "concurrency", "serviceAccountEmail", "timeoutSeconds", "vpc", "labels", "ingressSettings", "environmentVariables");
163
191
  allParsed.push(parsed);
164
192
  }
165
193
  return allParsed;
166
194
  }
195
+ function resolveChannelName(projectId, channel, defaultRegion) {
196
+ if (!channel.includes("/")) {
197
+ const location = defaultRegion;
198
+ const channelId = channel;
199
+ return "projects/" + projectId + "/locations/" + location + "/channels/" + channelId;
200
+ }
201
+ const match = CHANNEL_NAME_REGEX.exec(channel);
202
+ if (!(match === null || match === void 0 ? void 0 : match.groups)) {
203
+ throw new error_1.FirebaseError("Invalid channel name format.");
204
+ }
205
+ const matchedProjectId = match.groups.project;
206
+ const location = match.groups.location;
207
+ const channelId = match.groups.channel;
208
+ if (matchedProjectId) {
209
+ return "projects/" + matchedProjectId + "/locations/" + location + "/channels/" + channelId;
210
+ }
211
+ else {
212
+ return "projects/" + projectId + "/locations/" + location + "/channels/" + channelId;
213
+ }
214
+ }
@@ -17,7 +17,7 @@ const discovery = require("../discovery");
17
17
  const validate = require("./validate");
18
18
  const versioning = require("./versioning");
19
19
  const parseTriggers = require("./parseTriggers");
20
- const MIN_FUNCTIONS_SDK_VERSION = "3.19.0";
20
+ const MIN_FUNCTIONS_SDK_VERSION = "3.20.0";
21
21
  async function tryCreateDelegate(context) {
22
22
  const packageJsonPath = path.join(context.sourceDir, "package.json");
23
23
  if (!(await (0, util_1.promisify)(fs.exists)(packageJsonPath))) {
@@ -4,7 +4,7 @@ exports.getRuntimeChoice = exports.DEPRECATED_NODE_VERSION_INFO = exports.UNSUPP
4
4
  const path = require("path");
5
5
  const clc = require("cli-color");
6
6
  const error_1 = require("../../../../error");
7
- const track = require("../../../../track");
7
+ const track_1 = require("../../../../track");
8
8
  const runtimes = require("../../runtimes");
9
9
  const cjson = require("cjson");
10
10
  const ENGINE_RUNTIMES = {
@@ -46,11 +46,11 @@ function getRuntimeChoice(sourceDir, runtimeFromConfig) {
46
46
  ? exports.UNSUPPORTED_NODE_VERSION_FIREBASE_JSON_MSG
47
47
  : exports.UNSUPPORTED_NODE_VERSION_PACKAGE_JSON_MSG) + exports.DEPRECATED_NODE_VERSION_INFO;
48
48
  if (!runtime || !ENGINE_RUNTIMES_NAMES.includes(runtime)) {
49
- void track("functions_runtime_notices", "package_missing_runtime");
49
+ void (0, track_1.track)("functions_runtime_notices", "package_missing_runtime");
50
50
  throw new error_1.FirebaseError(errorMessage, { exit: 1 });
51
51
  }
52
52
  if (runtimes.isDeprecatedRuntime(runtime) || !runtimes.isValidRuntime(runtime)) {
53
- void track("functions_runtime_notices", `${runtime}_deploy_prohibited`);
53
+ void (0, track_1.track)("functions_runtime_notices", `${runtime}_deploy_prohibited`);
54
54
  throw new error_1.FirebaseError(errorMessage, { exit: 1 });
55
55
  }
56
56
  return runtime;
@@ -9,7 +9,7 @@ const logger_1 = require("../../../../logger");
9
9
  const backend = require("../../backend");
10
10
  const api = require("../../../../api");
11
11
  const proto = require("../../../../gcp/proto");
12
- const v2events = require("../../../../functions/events/v2");
12
+ const events = require("../../../../functions/events");
13
13
  const TRIGGER_PARSER = path.resolve(__dirname, "./triggerParser.js");
14
14
  function removeInspectOptions(options) {
15
15
  return options.filter((opt) => !opt.startsWith("--inspect"));
@@ -62,7 +62,9 @@ function mergeRequiredAPIs(backend) {
62
62
  const apiToReasons = {};
63
63
  for (const { api, reason } of backend.requiredAPIs) {
64
64
  const reasons = apiToReasons[api] || new Set();
65
- reasons.add(reason);
65
+ if (reason) {
66
+ reasons.add(reason);
67
+ }
66
68
  apiToReasons[api] = reasons;
67
69
  }
68
70
  const merged = [];
@@ -77,7 +79,10 @@ function addResourcesToBackend(projectId, runtime, annotation, want) {
77
79
  Object.freeze(annotation);
78
80
  for (const region of annotation.regions || [api.functionsDefaultRegion]) {
79
81
  let triggered;
80
- const triggerCount = +!!annotation.httpsTrigger + +!!annotation.eventTrigger + +!!annotation.taskQueueTrigger;
82
+ const triggerCount = +!!annotation.httpsTrigger +
83
+ +!!annotation.eventTrigger +
84
+ +!!annotation.taskQueueTrigger +
85
+ +!!annotation.blockingTrigger;
81
86
  if (triggerCount !== 1) {
82
87
  throw new error_1.FirebaseError("Unexpected annotation generated by the Firebase Functions SDK. This should never happen.");
83
88
  }
@@ -109,35 +114,34 @@ function addResourcesToBackend(projectId, runtime, annotation, want) {
109
114
  });
110
115
  triggered = { scheduleTrigger: annotation.schedule };
111
116
  }
117
+ else if (annotation.blockingTrigger) {
118
+ if (events.v1.AUTH_BLOCKING_EVENTS.includes(annotation.blockingTrigger.eventType)) {
119
+ want.requiredAPIs.push({
120
+ api: "identitytoolkit.googleapis.com",
121
+ reason: "Needed for auth blocking functions.",
122
+ });
123
+ }
124
+ triggered = {
125
+ blockingTrigger: {
126
+ eventType: annotation.blockingTrigger.eventType,
127
+ options: annotation.blockingTrigger.options,
128
+ },
129
+ };
130
+ }
112
131
  else {
113
132
  triggered = {
114
133
  eventTrigger: {
115
134
  eventType: annotation.eventTrigger.eventType,
116
- eventFilters: [
117
- {
118
- attribute: "resource",
119
- value: annotation.eventTrigger.resource,
120
- },
121
- ],
135
+ eventFilters: { resource: annotation.eventTrigger.resource },
122
136
  retry: !!annotation.failurePolicy,
123
137
  },
124
138
  };
125
139
  if (annotation.platform === "gcfv2") {
126
- if (annotation.eventTrigger.eventType === v2events.PUBSUB_PUBLISH_EVENT) {
127
- triggered.eventTrigger.eventFilters = [
128
- {
129
- attribute: "topic",
130
- value: annotation.eventTrigger.resource,
131
- },
132
- ];
140
+ if (annotation.eventTrigger.eventType === events.v2.PUBSUB_PUBLISH_EVENT) {
141
+ triggered.eventTrigger.eventFilters = { topic: annotation.eventTrigger.resource };
133
142
  }
134
- if (v2events.STORAGE_EVENTS.find((event) => { var _a; return event === (((_a = annotation.eventTrigger) === null || _a === void 0 ? void 0 : _a.eventType) || ""); })) {
135
- triggered.eventTrigger.eventFilters = [
136
- {
137
- attribute: "bucket",
138
- value: annotation.eventTrigger.resource,
139
- },
140
- ];
143
+ if (events.v2.STORAGE_EVENTS.find((event) => { var _a; return event === (((_a = annotation.eventTrigger) === null || _a === void 0 ? void 0 : _a.eventType) || ""); })) {
144
+ triggered.eventTrigger.eventFilters = { bucket: annotation.eventTrigger.resource };
141
145
  }
142
146
  }
143
147
  }
@@ -162,7 +166,8 @@ function addResourcesToBackend(projectId, runtime, annotation, want) {
162
166
  }
163
167
  endpoint.secretEnvironmentVariables = secretEnvs;
164
168
  }
165
- proto.copyIfPresent(endpoint, annotation, "concurrency", "serviceAccountEmail", "labels", "ingressSettings", "timeout", "maxInstances", "minInstances", "availableMemoryMb");
169
+ proto.copyIfPresent(endpoint, annotation, "concurrency", "serviceAccountEmail", "labels", "ingressSettings", "maxInstances", "minInstances", "availableMemoryMb");
170
+ proto.renameIfPresent(endpoint, annotation, "timeoutSeconds", "timeout", proto.secondsFromDuration);
166
171
  want.endpoints[region] = want.endpoints[region] || {};
167
172
  want.endpoints[region][endpoint.id] = endpoint;
168
173
  mergeRequiredAPIs(want);
@@ -7,7 +7,7 @@ const semver = require("semver");
7
7
  const spawn = require("cross-spawn");
8
8
  const utils = require("../../../../utils");
9
9
  const logger_1 = require("../../../../logger");
10
- const track = require("../../../../track");
10
+ const track_1 = require("../../../../track");
11
11
  const MIN_SDK_VERSION = "2.0.0";
12
12
  exports.FUNCTIONS_SDK_VERSION_TOO_OLD_WARNING = clc.bold.yellow("functions: ") +
13
13
  "You must have a " +
@@ -52,7 +52,7 @@ exports.getLatestSDKVersion = getLatestSDKVersion;
52
52
  function checkFunctionsSDKVersion(currentVersion) {
53
53
  try {
54
54
  if (semver.lt(currentVersion, MIN_SDK_VERSION)) {
55
- void track("functions_runtime_notices", "functions_sdk_too_old");
55
+ void (0, track_1.track)("functions_runtime_notices", "functions_sdk_too_old");
56
56
  utils.logWarning(exports.FUNCTIONS_SDK_VERSION_TOO_OLD_WARNING);
57
57
  }
58
58
  const latest = exports.getLatestSDKVersion();
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AuthBlockingService = void 0;
4
+ const backend = require("../backend");
5
+ const identityPlatform = require("../../../gcp/identityPlatform");
6
+ const events = require("../../../functions/events");
7
+ const error_1 = require("../../../error");
8
+ const utils_1 = require("../../../utils");
9
+ const index_1 = require("./index");
10
+ class AuthBlockingService {
11
+ constructor() {
12
+ this.name = "authblocking";
13
+ this.api = "identitytoolkit.googleapis.com";
14
+ this.triggerQueue = Promise.resolve();
15
+ this.ensureTriggerRegion = index_1.noop;
16
+ }
17
+ validateTrigger(endpoint, wantBackend) {
18
+ if (!backend.isBlockingTriggered(endpoint)) {
19
+ return;
20
+ }
21
+ const blockingEndpoints = backend
22
+ .allEndpoints(wantBackend)
23
+ .filter((ep) => backend.isBlockingTriggered(ep));
24
+ if (blockingEndpoints.find((ep) => ep.blockingTrigger.eventType === endpoint.blockingTrigger.eventType &&
25
+ ep.id !== endpoint.id)) {
26
+ throw new error_1.FirebaseError(`Can only create at most one Auth Blocking Trigger for ${endpoint.blockingTrigger.eventType} events`);
27
+ }
28
+ }
29
+ configChanged(newConfig, config) {
30
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
31
+ if (((_b = (_a = newConfig.triggers) === null || _a === void 0 ? void 0 : _a.beforeCreate) === null || _b === void 0 ? void 0 : _b.functionUri) !==
32
+ ((_d = (_c = config.triggers) === null || _c === void 0 ? void 0 : _c.beforeCreate) === null || _d === void 0 ? void 0 : _d.functionUri) ||
33
+ ((_f = (_e = newConfig.triggers) === null || _e === void 0 ? void 0 : _e.beforeSignIn) === null || _f === void 0 ? void 0 : _f.functionUri) !== ((_h = (_g = config.triggers) === null || _g === void 0 ? void 0 : _g.beforeSignIn) === null || _h === void 0 ? void 0 : _h.functionUri)) {
34
+ return true;
35
+ }
36
+ if (!!((_j = newConfig.forwardInboundCredentials) === null || _j === void 0 ? void 0 : _j.accessToken) !==
37
+ !!((_k = config.forwardInboundCredentials) === null || _k === void 0 ? void 0 : _k.accessToken) ||
38
+ !!((_l = newConfig.forwardInboundCredentials) === null || _l === void 0 ? void 0 : _l.idToken) !==
39
+ !!((_m = config.forwardInboundCredentials) === null || _m === void 0 ? void 0 : _m.idToken) ||
40
+ !!((_o = newConfig.forwardInboundCredentials) === null || _o === void 0 ? void 0 : _o.refreshToken) !==
41
+ !!((_p = config.forwardInboundCredentials) === null || _p === void 0 ? void 0 : _p.refreshToken)) {
42
+ return true;
43
+ }
44
+ return false;
45
+ }
46
+ async registerTriggerLocked(endpoint) {
47
+ const newBlockingConfig = await identityPlatform.getBlockingFunctionsConfig(endpoint.project);
48
+ const oldBlockingConfig = (0, utils_1.cloneDeep)(newBlockingConfig);
49
+ if (endpoint.blockingTrigger.eventType === events.v1.BEFORE_CREATE_EVENT) {
50
+ newBlockingConfig.triggers = Object.assign(Object.assign({}, newBlockingConfig.triggers), { beforeCreate: {
51
+ functionUri: endpoint.uri,
52
+ } });
53
+ }
54
+ else {
55
+ newBlockingConfig.triggers = Object.assign(Object.assign({}, newBlockingConfig.triggers), { beforeSignIn: {
56
+ functionUri: endpoint.uri,
57
+ } });
58
+ }
59
+ newBlockingConfig.forwardInboundCredentials = Object.assign(Object.assign({}, oldBlockingConfig.forwardInboundCredentials), endpoint.blockingTrigger.options);
60
+ if (!this.configChanged(newBlockingConfig, oldBlockingConfig)) {
61
+ return;
62
+ }
63
+ await identityPlatform.setBlockingFunctionsConfig(endpoint.project, newBlockingConfig);
64
+ }
65
+ registerTrigger(ep) {
66
+ if (!backend.isBlockingTriggered(ep)) {
67
+ return Promise.resolve();
68
+ }
69
+ this.triggerQueue = this.triggerQueue.then(() => this.registerTriggerLocked(ep));
70
+ return this.triggerQueue;
71
+ }
72
+ async unregisterTriggerLocked(endpoint) {
73
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
74
+ const blockingConfig = await identityPlatform.getBlockingFunctionsConfig(endpoint.project);
75
+ if (endpoint.uri !== ((_b = (_a = blockingConfig.triggers) === null || _a === void 0 ? void 0 : _a.beforeCreate) === null || _b === void 0 ? void 0 : _b.functionUri) &&
76
+ endpoint.uri !== ((_d = (_c = blockingConfig.triggers) === null || _c === void 0 ? void 0 : _c.beforeSignIn) === null || _d === void 0 ? void 0 : _d.functionUri)) {
77
+ return;
78
+ }
79
+ if (endpoint.uri === ((_f = (_e = blockingConfig.triggers) === null || _e === void 0 ? void 0 : _e.beforeCreate) === null || _f === void 0 ? void 0 : _f.functionUri)) {
80
+ (_g = blockingConfig.triggers) === null || _g === void 0 ? true : delete _g.beforeCreate;
81
+ }
82
+ if (endpoint.uri === ((_j = (_h = blockingConfig.triggers) === null || _h === void 0 ? void 0 : _h.beforeSignIn) === null || _j === void 0 ? void 0 : _j.functionUri)) {
83
+ (_k = blockingConfig.triggers) === null || _k === void 0 ? true : delete _k.beforeSignIn;
84
+ }
85
+ await identityPlatform.setBlockingFunctionsConfig(endpoint.project, blockingConfig);
86
+ }
87
+ unregisterTrigger(ep) {
88
+ if (!backend.isBlockingTriggered(ep)) {
89
+ return Promise.resolve();
90
+ }
91
+ this.triggerQueue = this.triggerQueue.then(() => this.unregisterTriggerLocked(ep));
92
+ return this.triggerQueue;
93
+ }
94
+ }
95
+ exports.AuthBlockingService = AuthBlockingService;
@@ -1,47 +1,67 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.serviceForEndpoint = exports.EVENT_SERVICE_MAPPING = exports.FirebaseAlertsService = exports.StorageService = exports.PubSubService = exports.NoOpService = void 0;
3
+ exports.serviceForEndpoint = exports.noopProjectBindings = exports.noop = void 0;
4
4
  const backend = require("../backend");
5
+ const auth_1 = require("./auth");
5
6
  const storage_1 = require("./storage");
6
7
  const firebaseAlerts_1 = require("./firebaseAlerts");
7
8
  const noop = () => Promise.resolve();
9
+ exports.noop = noop;
8
10
  const noopProjectBindings = () => Promise.resolve([]);
9
- exports.NoOpService = {
11
+ exports.noopProjectBindings = noopProjectBindings;
12
+ const noOpService = {
10
13
  name: "noop",
11
14
  api: "",
12
- requiredProjectBindings: undefined,
13
- ensureTriggerRegion: noop,
15
+ ensureTriggerRegion: exports.noop,
16
+ validateTrigger: exports.noop,
17
+ registerTrigger: exports.noop,
18
+ unregisterTrigger: exports.noop,
14
19
  };
15
- exports.PubSubService = {
20
+ const pubSubService = {
16
21
  name: "pubsub",
17
22
  api: "pubsub.googleapis.com",
18
- requiredProjectBindings: undefined,
19
- ensureTriggerRegion: noop,
23
+ requiredProjectBindings: exports.noopProjectBindings,
24
+ ensureTriggerRegion: exports.noop,
25
+ validateTrigger: exports.noop,
26
+ registerTrigger: exports.noop,
27
+ unregisterTrigger: exports.noop,
20
28
  };
21
- exports.StorageService = {
29
+ const storageService = {
22
30
  name: "storage",
23
31
  api: "storage.googleapis.com",
24
32
  requiredProjectBindings: storage_1.obtainStorageBindings,
25
33
  ensureTriggerRegion: storage_1.ensureStorageTriggerRegion,
34
+ validateTrigger: exports.noop,
35
+ registerTrigger: exports.noop,
36
+ unregisterTrigger: exports.noop,
26
37
  };
27
- exports.FirebaseAlertsService = {
38
+ const firebaseAlertsService = {
28
39
  name: "firebasealerts",
29
- api: "logging.googleapis.com",
30
- requiredProjectBindings: noopProjectBindings,
40
+ api: "firebasealerts.googleapis.com",
41
+ requiredProjectBindings: exports.noopProjectBindings,
31
42
  ensureTriggerRegion: firebaseAlerts_1.ensureFirebaseAlertsTriggerRegion,
43
+ validateTrigger: exports.noop,
44
+ registerTrigger: exports.noop,
45
+ unregisterTrigger: exports.noop,
32
46
  };
33
- exports.EVENT_SERVICE_MAPPING = {
34
- "google.cloud.pubsub.topic.v1.messagePublished": exports.PubSubService,
35
- "google.cloud.storage.object.v1.finalized": exports.StorageService,
36
- "google.cloud.storage.object.v1.archived": exports.StorageService,
37
- "google.cloud.storage.object.v1.deleted": exports.StorageService,
38
- "google.cloud.storage.object.v1.metadataUpdated": exports.StorageService,
39
- "google.firebase.firebasealerts.alerts.v1.published": exports.FirebaseAlertsService,
47
+ const authBlockingService = new auth_1.AuthBlockingService();
48
+ const EVENT_SERVICE_MAPPING = {
49
+ "google.cloud.pubsub.topic.v1.messagePublished": pubSubService,
50
+ "google.cloud.storage.object.v1.finalized": storageService,
51
+ "google.cloud.storage.object.v1.archived": storageService,
52
+ "google.cloud.storage.object.v1.deleted": storageService,
53
+ "google.cloud.storage.object.v1.metadataUpdated": storageService,
54
+ "google.firebase.firebasealerts.alerts.v1.published": firebaseAlertsService,
55
+ "providers/cloud.auth/eventTypes/user.beforeCreate": authBlockingService,
56
+ "providers/cloud.auth/eventTypes/user.beforeSignIn": authBlockingService,
40
57
  };
41
58
  function serviceForEndpoint(endpoint) {
42
- if (!backend.isEventTriggered(endpoint)) {
43
- return exports.NoOpService;
59
+ if (backend.isEventTriggered(endpoint)) {
60
+ return EVENT_SERVICE_MAPPING[endpoint.eventTrigger.eventType] || noOpService;
44
61
  }
45
- return exports.EVENT_SERVICE_MAPPING[endpoint.eventTrigger.eventType] || exports.NoOpService;
62
+ if (backend.isBlockingTriggered(endpoint)) {
63
+ return EVENT_SERVICE_MAPPING[endpoint.blockingTrigger.eventType] || noOpService;
64
+ }
65
+ return noOpService;
46
66
  }
47
67
  exports.serviceForEndpoint = serviceForEndpoint;
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ensureStorageTriggerRegion = exports.obtainStorageBindings = void 0;
4
4
  const storage = require("../../../gcp/storage");
5
- const backend = require("../backend");
6
5
  const logger_1 = require("../../../logger");
7
6
  const error_1 = require("../../../error");
8
7
  const location_1 = require("../../../gcp/location");
@@ -27,12 +26,8 @@ async function ensureStorageTriggerRegion(endpoint) {
27
26
  const { eventTrigger } = endpoint;
28
27
  if (!eventTrigger.region) {
29
28
  logger_1.logger.debug("Looking up bucket region for the storage event trigger");
30
- const bucketFilter = backend.findEventFilter(endpoint, "bucket");
31
- if (!bucketFilter) {
32
- throw new error_1.FirebaseError("Storage event trigger unexpectedly missing event filter with bucket attribute.");
33
- }
34
29
  try {
35
- const bucket = await storage.getBucket(bucketFilter.value);
30
+ const bucket = await storage.getBucket(eventTrigger.eventFilters.bucket);
36
31
  eventTrigger.region = bucket.location.toLowerCase();
37
32
  logger_1.logger.debug("Setting the event trigger region to", eventTrigger.region, ".");
38
33
  }
@@ -10,18 +10,21 @@ const fsutils = require("../../fsutils");
10
10
  const backend = require("./backend");
11
11
  const utils = require("../../utils");
12
12
  const secrets = require("../../functions/secrets");
13
+ const services_1 = require("./services");
13
14
  function endpointsAreValid(wantBackend) {
14
- functionIdsAreValid(backend.allEndpoints(wantBackend));
15
- const gcfV1WithConcurrency = backend
16
- .allEndpoints(wantBackend)
15
+ const endpoints = backend.allEndpoints(wantBackend);
16
+ functionIdsAreValid(endpoints);
17
+ for (const ep of endpoints) {
18
+ (0, services_1.serviceForEndpoint)(ep).validateTrigger(ep, wantBackend);
19
+ }
20
+ const gcfV1WithConcurrency = endpoints
17
21
  .filter((endpoint) => (endpoint.concurrency || 1) !== 1 && endpoint.platform === "gcfv1")
18
22
  .map((endpoint) => endpoint.id);
19
23
  if (gcfV1WithConcurrency.length) {
20
24
  const msg = `Cannot set concurrency on the functions ${gcfV1WithConcurrency.join(",")} because they are GCF gen 1`;
21
25
  throw new error_1.FirebaseError(msg);
22
26
  }
23
- const tooSmallForConcurrency = backend
24
- .allEndpoints(wantBackend)
27
+ const tooSmallForConcurrency = endpoints
25
28
  .filter((endpoint) => {
26
29
  if ((endpoint.concurrency || 1) === 1) {
27
30
  return false;