firebase-tools 10.7.0 → 10.8.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 (64) hide show
  1. package/lib/commands/ext-configure.js +26 -15
  2. package/lib/commands/ext-export.js +14 -5
  3. package/lib/commands/ext-install.js +31 -2
  4. package/lib/commands/ext-update.js +17 -10
  5. package/lib/commands/functions-delete.js +9 -2
  6. package/lib/commands/functions-secrets-set.js +1 -13
  7. package/lib/deploy/extensions/planner.js +12 -0
  8. package/lib/deploy/extensions/tasks.js +13 -0
  9. package/lib/deploy/functions/backend.js +67 -10
  10. package/lib/deploy/functions/build.js +28 -9
  11. package/lib/deploy/functions/checkIam.js +71 -56
  12. package/lib/deploy/functions/containerCleaner.js +8 -7
  13. package/lib/deploy/functions/deploy.js +49 -27
  14. package/lib/deploy/functions/functionsDeployHelper.js +48 -4
  15. package/lib/deploy/functions/prepare.js +125 -74
  16. package/lib/deploy/functions/pricing.js +2 -2
  17. package/lib/deploy/functions/release/executor.js +1 -1
  18. package/lib/deploy/functions/release/fabricator.js +94 -36
  19. package/lib/deploy/functions/release/index.js +16 -27
  20. package/lib/deploy/functions/release/planner.js +12 -7
  21. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +13 -1
  22. package/lib/deploy/functions/runtimes/golang/index.js +3 -0
  23. package/lib/deploy/functions/runtimes/node/index.js +7 -0
  24. package/lib/deploy/functions/runtimes/node/parseTriggers.js +108 -1
  25. package/lib/deploy/functions/services/storage.js +6 -12
  26. package/lib/deploy/functions/validate.js +58 -8
  27. package/lib/deploy/hosting/convertConfig.js +6 -4
  28. package/lib/emulator/auth/cloudFunctions.js +6 -2
  29. package/lib/emulator/auth/operations.js +0 -1
  30. package/lib/emulator/auth/server.js +8 -1
  31. package/lib/emulator/auth/state.js +27 -24
  32. package/lib/emulator/controller.js +12 -9
  33. package/lib/emulator/databaseEmulator.js +36 -3
  34. package/lib/emulator/downloadableEmulators.js +7 -7
  35. package/lib/emulator/extensionsEmulator.js +3 -0
  36. package/lib/emulator/functionsEmulator.js +11 -9
  37. package/lib/emulator/functionsEmulatorRuntime.js +1 -1
  38. package/lib/emulator/functionsEmulatorShared.js +5 -1
  39. package/lib/emulator/functionsEmulatorShell.js +2 -3
  40. package/lib/emulator/functionsEmulatorUtils.js +5 -1
  41. package/lib/emulator/pubsubEmulator.js +13 -9
  42. package/lib/emulator/storage/apis/firebase.js +26 -4
  43. package/lib/ensureApiEnabled.js +1 -1
  44. package/lib/extensions/askUserForEventsConfig.js +97 -0
  45. package/lib/extensions/export.js +7 -0
  46. package/lib/extensions/extensionsApi.js +47 -7
  47. package/lib/extensions/manifest.js +1 -1
  48. package/lib/extensions/paramHelper.js +2 -0
  49. package/lib/extensions/updateHelper.js +7 -1
  50. package/lib/extensions/warnings.js +11 -4
  51. package/lib/functions/projectConfig.js +13 -8
  52. package/lib/functionsShellCommandAction.js +1 -1
  53. package/lib/gcp/cloudfunctions.js +9 -2
  54. package/lib/gcp/cloudfunctionsv2.js +28 -10
  55. package/lib/gcp/serviceusage.js +24 -0
  56. package/lib/previews.js +1 -1
  57. package/lib/serve/functions.js +16 -19
  58. package/lib/throttler/throttler.js +2 -1
  59. package/npm-shrinkwrap.json +214 -527
  60. package/package.json +3 -3
  61. package/templates/extensions/typescript/package.lint.json +2 -1
  62. package/templates/extensions/typescript/package.nolint.json +2 -1
  63. package/templates/init/functions/typescript/package.lint.json +1 -0
  64. package/templates/init/functions/typescript/package.nolint.json +1 -0
@@ -48,20 +48,25 @@ function calculateUpdate(want, have) {
48
48
  return update;
49
49
  }
50
50
  exports.calculateUpdate = calculateUpdate;
51
- function createDeploymentPlan(want, have, filters, deleteAll) {
51
+ function createDeploymentPlan(args) {
52
+ let { wantBackend, haveBackend, codebase, filters, deleteAll } = args;
52
53
  let deployment = {};
53
- want = backend.matchingBackend(want, (endpoint) => {
54
+ wantBackend = backend.matchingBackend(wantBackend, (endpoint) => {
54
55
  return (0, functionsDeployHelper_1.endpointMatchesAnyFilter)(endpoint, filters);
55
56
  });
56
- have = backend.matchingBackend(have, (endpoint) => {
57
- return (0, functionsDeployHelper_1.endpointMatchesAnyFilter)(endpoint, filters);
57
+ const wantedEndpoint = backend.hasEndpoint(wantBackend);
58
+ haveBackend = backend.matchingBackend(haveBackend, (endpoint) => {
59
+ return wantedEndpoint(endpoint) || (0, functionsDeployHelper_1.endpointMatchesAnyFilter)(endpoint, filters);
58
60
  });
59
- const regions = new Set([...Object.keys(want.endpoints), ...Object.keys(have.endpoints)]);
61
+ const regions = new Set([
62
+ ...Object.keys(wantBackend.endpoints),
63
+ ...Object.keys(haveBackend.endpoints),
64
+ ]);
60
65
  for (const region of regions) {
61
- const changesets = calculateChangesets(want.endpoints[region] || {}, have.endpoints[region] || {}, (e) => `${e.region}-${e.availableMemoryMb || "default"}`, deleteAll);
66
+ const changesets = calculateChangesets(wantBackend.endpoints[region] || {}, haveBackend.endpoints[region] || {}, (e) => `${codebase}-${e.region}-${e.availableMemoryMb || "default"}`, deleteAll);
62
67
  deployment = Object.assign(Object.assign({}, deployment), changesets);
63
68
  }
64
- if (upgradedToGCFv2WithoutSettingConcurrency(want, have)) {
69
+ if (upgradedToGCFv2WithoutSettingConcurrency(wantBackend, haveBackend)) {
65
70
  utils.logLabeledBullet("functions", "You are updating one or more functions to Google Cloud Functions v2, " +
66
71
  "which introduces support for concurrent execution. New functions " +
67
72
  "default to 80 concurrent executions, but existing functions keep the " +
@@ -187,7 +187,19 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
187
187
  region,
188
188
  project,
189
189
  runtime, entryPoint: ep.entryPoint }, triggered);
190
- (0, proto_1.copyIfPresent)(parsed, ep, "availableMemoryMb", "maxInstances", "minInstances", "concurrency", "serviceAccountEmail", "timeoutSeconds", "vpc", "labels", "ingressSettings", "environmentVariables");
190
+ (0, proto_1.copyIfPresent)(parsed, ep, "availableMemoryMb", "maxInstances", "minInstances", "concurrency", "serviceAccountEmail", "timeoutSeconds", "vpc", "labels", "ingressSettings", "environmentVariables", "cpu");
191
+ (0, proto_1.renameIfPresent)(parsed, ep, "secretEnvironmentVariables", "secretEnvironmentVariables", (senvs) => {
192
+ if (senvs && senvs.length > 0) {
193
+ ep.secretEnvironmentVariables = [];
194
+ for (const { key, secret } of senvs) {
195
+ ep.secretEnvironmentVariables.push({
196
+ key,
197
+ secret: secret || key,
198
+ projectId: project,
199
+ });
200
+ }
201
+ }
202
+ });
191
203
  allParsed.push(parsed);
192
204
  }
193
205
  return allParsed;
@@ -105,6 +105,9 @@ class Delegate {
105
105
  return p;
106
106
  });
107
107
  }
108
+ async discoverBuild(configValues, envs) {
109
+ return { requiredAPIs: [], endpoints: {}, params: [] };
110
+ }
108
111
  async discoverSpec(configValues, envs) {
109
112
  let discovered = await discovery.detectFromYaml(this.sourceDir, this.projectId, this.runtime);
110
113
  if (!discovered) {
@@ -84,6 +84,10 @@ class Delegate {
84
84
  }
85
85
  async discoverSpec(config, env) {
86
86
  if (previews_1.previews.functionsv2) {
87
+ if (!semver.valid(this.sdkVersion)) {
88
+ logger_1.logger.debug(`Could not parse firebase-functions version '${this.sdkVersion}' into semver. Falling back to parseTriggers.`);
89
+ return parseTriggers.discoverBackend(this.projectId, this.sourceDir, this.runtime, config, env);
90
+ }
87
91
  if (semver.lt(this.sdkVersion, MIN_FUNCTIONS_SDK_VERSION)) {
88
92
  (0, utils_1.logLabeledWarning)("functions", `You are using an old version of firebase-functions SDK (${this.sdkVersion}). ` +
89
93
  `Please update firebase-functions SDK to >=${MIN_FUNCTIONS_SDK_VERSION}`);
@@ -106,5 +110,8 @@ class Delegate {
106
110
  }
107
111
  return parseTriggers.discoverBackend(this.projectId, this.sourceDir, this.runtime, config, env);
108
112
  }
113
+ async discoverBuild(config, env) {
114
+ return parseTriggers.discoverBuild(this.projectId, this.sourceDir, this.runtime, config, env);
115
+ }
109
116
  }
110
117
  exports.Delegate = Delegate;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.addResourcesToBackend = exports.mergeRequiredAPIs = exports.discoverBackend = exports.useStrategy = void 0;
3
+ exports.addResourcesToBackend = exports.addResourcesToBuild = exports.mergeRequiredAPIs = exports.discoverBackend = exports.discoverBuild = exports.useStrategy = void 0;
4
4
  const path = require("path");
5
5
  const _ = require("lodash");
6
6
  const child_process_1 = require("child_process");
@@ -49,6 +49,19 @@ function useStrategy(context) {
49
49
  return Promise.resolve(true);
50
50
  }
51
51
  exports.useStrategy = useStrategy;
52
+ async function discoverBuild(projectId, sourceDir, runtime, configValues, envs) {
53
+ const triggerAnnotations = await parseTriggers(projectId, sourceDir, configValues, envs);
54
+ const want = {
55
+ requiredAPIs: [],
56
+ endpoints: {},
57
+ params: [],
58
+ };
59
+ for (const annotation of triggerAnnotations) {
60
+ addResourcesToBuild(projectId, runtime, annotation, want);
61
+ }
62
+ return want;
63
+ }
64
+ exports.discoverBuild = discoverBuild;
52
65
  async function discoverBackend(projectId, sourceDir, runtime, configValues, envs) {
53
66
  const triggerAnnotations = await parseTriggers(projectId, sourceDir, configValues, envs);
54
67
  const want = Object.assign(Object.assign({}, backend.empty()), { environmentVariables: envs });
@@ -74,6 +87,100 @@ function mergeRequiredAPIs(backend) {
74
87
  backend.requiredAPIs = merged;
75
88
  }
76
89
  exports.mergeRequiredAPIs = mergeRequiredAPIs;
90
+ function addResourcesToBuild(projectId, runtime, annotation, want) {
91
+ var _a;
92
+ Object.freeze(annotation);
93
+ const regions = annotation.regions || [api.functionsDefaultRegion];
94
+ let triggered;
95
+ const triggerCount = +!!annotation.httpsTrigger +
96
+ +!!annotation.eventTrigger +
97
+ +!!annotation.taskQueueTrigger +
98
+ +!!annotation.blockingTrigger;
99
+ if (triggerCount !== 1) {
100
+ throw new error_1.FirebaseError("Unexpected annotation generated by the Firebase Functions SDK. This should never happen.");
101
+ }
102
+ if (annotation.taskQueueTrigger) {
103
+ want.requiredAPIs.push({
104
+ api: "cloudtasks.googleapis.com",
105
+ reason: "Needed for task queue functions.",
106
+ });
107
+ triggered = {
108
+ taskQueueTrigger: {},
109
+ };
110
+ proto.copyIfPresent(triggered.taskQueueTrigger, annotation.taskQueueTrigger, "invoker");
111
+ proto.copyIfPresent(triggered.taskQueueTrigger, annotation.taskQueueTrigger, "rateLimits");
112
+ if (annotation.taskQueueTrigger.retryConfig) {
113
+ triggered.taskQueueTrigger.retryConfig = Object.assign(annotation.taskQueueTrigger.retryConfig, {
114
+ maxRetryDurationSeconds: proto.secondsFromDuration(annotation.taskQueueTrigger.retryConfig.maxRetryDuration || "0"),
115
+ });
116
+ }
117
+ }
118
+ else if (annotation.httpsTrigger) {
119
+ if ((_a = annotation.labels) === null || _a === void 0 ? void 0 : _a["deployment-callable"]) {
120
+ delete annotation.labels["deployment-callable"];
121
+ triggered = { callableTrigger: {} };
122
+ }
123
+ else {
124
+ const trigger = {};
125
+ if (annotation.failurePolicy) {
126
+ logger_1.logger.warn(`Ignoring retry policy for HTTPS function ${annotation.name}`);
127
+ }
128
+ if (annotation.httpsTrigger.invoker) {
129
+ trigger.invoker = annotation.httpsTrigger.invoker[0];
130
+ }
131
+ triggered = { httpsTrigger: trigger };
132
+ }
133
+ }
134
+ else if (annotation.schedule) {
135
+ want.requiredAPIs.push({
136
+ api: "cloudscheduler.googleapis.com",
137
+ reason: "Needed for scheduled functions.",
138
+ });
139
+ triggered = {
140
+ scheduleTrigger: {
141
+ schedule: annotation.schedule.schedule,
142
+ timeZone: annotation.schedule.timeZone || "what's the default timezone?",
143
+ retryConfig: annotation.schedule.retryConfig || {},
144
+ },
145
+ };
146
+ }
147
+ else if (annotation.blockingTrigger) {
148
+ if (events.v1.AUTH_BLOCKING_EVENTS.includes(annotation.blockingTrigger.eventType)) {
149
+ want.requiredAPIs.push({
150
+ api: "identitytoolkit.googleapis.com",
151
+ reason: "Needed for auth blocking functions.",
152
+ });
153
+ }
154
+ triggered = {
155
+ blockingTrigger: {
156
+ eventType: annotation.blockingTrigger.eventType,
157
+ },
158
+ };
159
+ }
160
+ else {
161
+ triggered = {
162
+ eventTrigger: {
163
+ eventType: annotation.eventTrigger.eventType,
164
+ eventFilters: { resource: annotation.eventTrigger.resource },
165
+ retry: !!annotation.failurePolicy,
166
+ },
167
+ };
168
+ }
169
+ const endpointId = annotation.name;
170
+ const endpoint = Object.assign({ platform: annotation.platform || "gcfv1", region: regions, project: projectId, entryPoint: annotation.entryPoint, runtime: runtime, serviceAccount: annotation.serviceAccountEmail || "default" }, triggered);
171
+ if (annotation.vpcConnector != null) {
172
+ let maybeId = annotation.vpcConnector;
173
+ if (maybeId && !maybeId.includes("/")) {
174
+ maybeId = `projects/${projectId}/locations/$REGION/connectors/${maybeId}`;
175
+ }
176
+ endpoint.vpc = { connector: maybeId };
177
+ proto.renameIfPresent(endpoint.vpc, annotation, "egressSettings", "vpcConnectorEgressSettings");
178
+ }
179
+ proto.copyIfPresent(endpoint, annotation, "concurrency", "labels", "ingressSettings", "maxInstances", "minInstances", "availableMemoryMb");
180
+ proto.renameIfPresent(endpoint, annotation, "timeoutSeconds", "timeout", proto.secondsFromDuration);
181
+ want.endpoints[endpointId] = endpoint;
182
+ }
183
+ exports.addResourcesToBuild = addResourcesToBuild;
77
184
  function addResourcesToBackend(projectId, runtime, annotation, want) {
78
185
  var _a;
79
186
  Object.freeze(annotation);
@@ -6,20 +6,14 @@ const logger_1 = require("../../../logger");
6
6
  const error_1 = require("../../../error");
7
7
  const location_1 = require("../../../gcp/location");
8
8
  const PUBSUB_PUBLISHER_ROLE = "roles/pubsub.publisher";
9
- async function obtainStorageBindings(projectNumber, existingPolicy) {
9
+ async function obtainStorageBindings(projectNumber) {
10
10
  const storageResponse = await storage.getServiceAccount(projectNumber);
11
11
  const storageServiceAgent = `serviceAccount:${storageResponse.email_address}`;
12
- let pubsubBinding = existingPolicy.bindings.find((b) => b.role === PUBSUB_PUBLISHER_ROLE);
13
- if (!pubsubBinding) {
14
- pubsubBinding = {
15
- role: PUBSUB_PUBLISHER_ROLE,
16
- members: [],
17
- };
18
- }
19
- if (!pubsubBinding.members.find((m) => m === storageServiceAgent)) {
20
- pubsubBinding.members.push(storageServiceAgent);
21
- }
22
- return [pubsubBinding];
12
+ const pubsubPublisherBinding = {
13
+ role: PUBSUB_PUBLISHER_ROLE,
14
+ members: [storageServiceAgent],
15
+ };
16
+ return [pubsubPublisherBinding];
23
17
  }
24
18
  exports.obtainStorageBindings = obtainStorageBindings;
25
19
  async function ensureStorageTriggerRegion(endpoint) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.secretsAreValid = exports.functionIdsAreValid = exports.functionsDirectoryExists = exports.endpointsAreValid = void 0;
3
+ exports.secretsAreValid = exports.functionIdsAreValid = exports.functionsDirectoryExists = exports.endpointsAreUnique = exports.endpointsAreValid = void 0;
4
4
  const path = require("path");
5
5
  const clc = require("cli-color");
6
6
  const error_1 = require("../../error");
@@ -29,16 +29,66 @@ function endpointsAreValid(wantBackend) {
29
29
  if ((endpoint.concurrency || 1) === 1) {
30
30
  return false;
31
31
  }
32
- const mem = endpoint.availableMemoryMb || backend.DEFAULT_MEMORY;
33
- return mem < backend.MIN_MEMORY_FOR_CONCURRENCY;
32
+ return endpoint.cpu < backend.MIN_CPU_FOR_CONCURRENCY;
34
33
  })
35
34
  .map((endpoint) => endpoint.id);
36
35
  if (tooSmallForConcurrency.length) {
37
- const msg = `Cannot set concurency on the functions ${tooSmallForConcurrency.join(",")} because they have fewer than 2GB memory`;
36
+ const msg = "The following functions are configured to allow concurrent " +
37
+ "execution and less than one full CPU. This is not supported: " +
38
+ tooSmallForConcurrency.join(",");
39
+ throw new error_1.FirebaseError(msg);
40
+ }
41
+ const gcfV1WithCPU = endpoints
42
+ .filter((endpoint) => endpoint.platform === "gcfv1" && typeof endpoint["cpu"] !== "undefined")
43
+ .map((endpoint) => endpoint.id);
44
+ if (gcfV1WithCPU.length) {
45
+ const msg = `Cannot set CPU on the functions ${gcfV1WithCPU.join(",")} because they are GCF gen 1`;
46
+ throw new error_1.FirebaseError(msg);
47
+ }
48
+ const invalidCPU = endpoints
49
+ .filter((endpoint) => {
50
+ if (typeof endpoint.cpu === "undefined") {
51
+ return false;
52
+ }
53
+ if (endpoint.cpu === "gcf_gen1") {
54
+ return false;
55
+ }
56
+ const cpu = endpoint.cpu;
57
+ if (cpu < 1) {
58
+ return false;
59
+ }
60
+ return ![1, 2, 4, 6, 8].includes(cpu);
61
+ })
62
+ .map((endpoint) => endpoint.id);
63
+ if (invalidCPU.length) {
64
+ const msg = `The following functions have invalid CPU settings ${invalidCPU.join(",")}. Valid CPU options are (0, 1], 2, 4, 6, 8, or "gcf_gen1"`;
38
65
  throw new error_1.FirebaseError(msg);
39
66
  }
40
67
  }
41
68
  exports.endpointsAreValid = endpointsAreValid;
69
+ function endpointsAreUnique(backends) {
70
+ const endpointToCodebases = {};
71
+ for (const [codebase, b] of Object.entries(backends)) {
72
+ for (const endpoint of backend.allEndpoints(b)) {
73
+ const key = backend.functionName(endpoint);
74
+ const cs = endpointToCodebases[key] || new Set();
75
+ cs.add(codebase);
76
+ endpointToCodebases[key] = cs;
77
+ }
78
+ }
79
+ const conflicts = {};
80
+ for (const [fn, codebases] of Object.entries(endpointToCodebases)) {
81
+ if (codebases.size > 1) {
82
+ conflicts[fn] = Array.from(codebases);
83
+ }
84
+ }
85
+ if (Object.keys(conflicts).length === 0) {
86
+ return;
87
+ }
88
+ const msgs = Object.entries(conflicts).map(([fn, codebases]) => `${fn}: ${codebases.join(",")}`);
89
+ throw new error_1.FirebaseError("More than one codebase claims following functions:\n\t" + `${msgs.join("\n\t")}`);
90
+ }
91
+ exports.endpointsAreUnique = endpointsAreUnique;
42
92
  function functionsDirectoryExists(sourceDir, projectDir) {
43
93
  if (!fsutils.dirExistsSync(sourceDir)) {
44
94
  const sourceDirName = path.relative(projectDir, sourceDir);
@@ -63,7 +113,7 @@ function functionIdsAreValid(functions) {
63
113
  return fn.platform === "gcfv2" && !v2FunctionName.test(fn.id);
64
114
  });
65
115
  if (invalidV2Ids.length !== 0) {
66
- const msg = `${invalidV2Ids.map((f) => f.id).join(", ")} v2 function name(s) can only contin lower ` +
116
+ const msg = `${invalidV2Ids.map((f) => f.id).join(", ")} v2 function name(s) can only contain lower ` +
67
117
  `case letters, numbers, hyphens, and not exceed 62 characters in length`;
68
118
  throw new error_1.FirebaseError(msg);
69
119
  }
@@ -77,13 +127,13 @@ async function secretsAreValid(projectId, wantBackend) {
77
127
  await validateSecretVersions(projectId, endpoints);
78
128
  }
79
129
  exports.secretsAreValid = secretsAreValid;
130
+ const secretsSupportedPlatforms = ["gcfv1", "gcfv2"];
80
131
  function validatePlatformTargets(endpoints) {
81
- const supportedPlatforms = ["gcfv1"];
82
- const unsupported = endpoints.filter((e) => !supportedPlatforms.includes(e.platform));
132
+ const unsupported = endpoints.filter((e) => !secretsSupportedPlatforms.includes(e.platform));
83
133
  if (unsupported.length > 0) {
84
134
  const errs = unsupported.map((e) => `${e.id}[platform=${e.platform}]`);
85
135
  throw new error_1.FirebaseError(`Tried to set secret environment variables on ${errs.join(", ")}. ` +
86
- `Only ${supportedPlatforms.join(", ")} support secret environments.`);
136
+ `Only ${secretsSupportedPlatforms.join(", ")} support secret environments.`);
87
137
  }
88
138
  }
89
139
  async function validateSecretVersions(projectId, endpoints) {
@@ -40,10 +40,12 @@ async function convertConfig(context, payload, config, finalize) {
40
40
  return out;
41
41
  }
42
42
  const endpointBeingDeployed = (serviceId, region = "us-central1") => {
43
- var _a, _b, _c;
44
- const endpoint = (_c = (_b = (_a = payload.functions) === null || _a === void 0 ? void 0 : _a.wantBackend) === null || _b === void 0 ? void 0 : _b.endpoints[region]) === null || _c === void 0 ? void 0 : _c[serviceId];
45
- if (endpoint && (0, backend_1.isHttpsTriggered)(endpoint) && endpoint.platform === "gcfv2")
46
- return endpoint;
43
+ var _a;
44
+ for (const { wantBackend } of Object.values(payload.functions || {})) {
45
+ const endpoint = (_a = wantBackend === null || wantBackend === void 0 ? void 0 : wantBackend.endpoints[region]) === null || _a === void 0 ? void 0 : _a[serviceId];
46
+ if (endpoint && (0, backend_1.isHttpsTriggered)(endpoint) && endpoint.platform === "gcfv2")
47
+ return endpoint;
48
+ }
47
49
  return undefined;
48
50
  };
49
51
  const matchingEndpoint = async (serviceId, region = "us-central1") => {
@@ -62,8 +62,12 @@ class AuthCloudFunction {
62
62
  phoneNumber: user.phoneNumber,
63
63
  disabled: user.disabled,
64
64
  metadata: {
65
- creationTime: user.createdAt,
66
- lastSignInTime: user.lastLoginAt,
65
+ creationTime: user.createdAt
66
+ ? new Date(parseInt(user.createdAt, 10)).toISOString()
67
+ : undefined,
68
+ lastSignInTime: user.lastLoginAt
69
+ ? new Date(parseInt(user.lastLoginAt, 10)).toISOString()
70
+ : undefined,
67
71
  },
68
72
  customClaims: JSON.parse(user.customAttributes || "{}"),
69
73
  providerData: user.providerUserInfo,
@@ -1210,7 +1210,6 @@ function grantToken(state, reqBody) {
1210
1210
  (0, errors_1.assert)(reqBody.grantType === "refresh_token", "INVALID_GRANT_TYPE");
1211
1211
  (0, errors_1.assert)(reqBody.refreshToken, "MISSING_REFRESH_TOKEN");
1212
1212
  const refreshTokenRecord = state.validateRefreshToken(reqBody.refreshToken);
1213
- (0, errors_1.assert)(refreshTokenRecord, "INVALID_REFRESH_TOKEN");
1214
1213
  (0, errors_1.assert)(!refreshTokenRecord.user.disabled, "USER_DISABLED");
1215
1214
  const tokens = issueTokens(state, refreshTokenRecord.user, refreshTokenRecord.provider, {
1216
1215
  extraClaims: refreshTokenRecord.extraClaims,
@@ -342,7 +342,7 @@ function toExegesisController(ops, getProjectStateById) {
342
342
  }
343
343
  function toExegesisOperation(operation) {
344
344
  return (ctx) => {
345
- var _a, _b, _c, _d, _e, _f, _g;
345
+ var _a, _b, _c, _d, _e, _f, _g, _h;
346
346
  let targetProjectId = ctx.params.path.targetProjectId || ((_a = ctx.requestBody) === null || _a === void 0 ? void 0 : _a.targetProjectId);
347
347
  if (targetProjectId) {
348
348
  if ((_b = ctx.api.operationObject.security) === null || _b === void 0 ? void 0 : _b.some((sec) => sec.Oauth2)) {
@@ -365,6 +365,13 @@ function toExegesisController(ops, getProjectStateById) {
365
365
  }
366
366
  targetTenantId = targetTenantId || (decoded === null || decoded === void 0 ? void 0 : decoded.payload.firebase.tenant);
367
367
  }
368
+ if ((_h = ctx.requestBody) === null || _h === void 0 ? void 0 : _h.refreshToken) {
369
+ const refreshTokenRecord = (0, state_1.decodeRefreshToken)(ctx.requestBody.refreshToken);
370
+ if (refreshTokenRecord.tenantId && targetTenantId) {
371
+ (0, errors_2.assert)(refreshTokenRecord.tenantId === targetTenantId, "TENANT_ID_MISMATCH: ((Refresh token tenant ID does not match target tenant ID.))");
372
+ }
373
+ targetTenantId = targetTenantId || refreshTokenRecord.tenantId;
374
+ }
368
375
  return operation(getProjectStateById(targetProjectId, targetTenantId), ctx.requestBody, ctx);
369
376
  };
370
377
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.BlockingFunctionEvents = exports.UsageMode = exports.TenantProjectState = exports.AgentProjectState = exports.ProjectState = exports.SIGNIN_METHOD_EMAIL_LINK = exports.PROVIDER_GAME_CENTER = exports.PROVIDER_CUSTOM = exports.PROVIDER_ANONYMOUS = exports.PROVIDER_PHONE = exports.PROVIDER_PASSWORD = void 0;
3
+ exports.decodeRefreshToken = exports.encodeRefreshToken = exports.BlockingFunctionEvents = exports.UsageMode = exports.TenantProjectState = exports.AgentProjectState = exports.ProjectState = exports.SIGNIN_METHOD_EMAIL_LINK = exports.PROVIDER_GAME_CENTER = exports.PROVIDER_CUSTOM = exports.PROVIDER_ANONYMOUS = exports.PROVIDER_PHONE = exports.PROVIDER_PASSWORD = void 0;
4
4
  const utils_1 = require("./utils");
5
5
  const cloudFunctions_1 = require("./cloudFunctions");
6
6
  const errors_1 = require("./errors");
@@ -19,8 +19,6 @@ class ProjectState {
19
19
  this.localIdForPhoneNumber = new Map();
20
20
  this.localIdsForProviderEmail = new Map();
21
21
  this.userIdForProviderRawId = new Map();
22
- this.refreshTokens = new Map();
23
- this.refreshTokensForLocalId = new Map();
24
22
  this.oobs = new Map();
25
23
  this.verificationCodes = new Map();
26
24
  this.temporaryProofs = new Map();
@@ -73,13 +71,6 @@ class ProjectState {
73
71
  deleteUser(user) {
74
72
  this.users.delete(user.localId);
75
73
  this.removeUserFromIndex(user);
76
- const refreshTokens = this.refreshTokensForLocalId.get(user.localId);
77
- if (refreshTokens) {
78
- this.refreshTokensForLocalId.delete(user.localId);
79
- for (const refreshToken of refreshTokens) {
80
- this.refreshTokens.delete(refreshToken);
81
- }
82
- }
83
74
  this.authCloudFunction.dispatch("delete", user);
84
75
  }
85
76
  updateUserByLocalId(localId, fields, options = {}) {
@@ -282,26 +273,23 @@ class ProjectState {
282
273
  }
283
274
  createRefreshTokenFor(userInfo, provider, { extraClaims = {}, secondFactor, } = {}) {
284
275
  const localId = userInfo.localId;
285
- const refreshToken = (0, utils_1.randomBase64UrlStr)(204);
286
- this.refreshTokens.set(refreshToken, {
276
+ const refreshTokenRecord = {
277
+ _AuthEmulatorRefreshToken: "DO NOT MODIFY",
287
278
  localId,
288
279
  provider,
289
280
  extraClaims,
281
+ projectId: this.projectId,
290
282
  secondFactor,
291
283
  tenantId: userInfo.tenantId,
292
- });
293
- let refreshTokens = this.refreshTokensForLocalId.get(localId);
294
- if (!refreshTokens) {
295
- refreshTokens = new Set();
296
- this.refreshTokensForLocalId.set(localId, refreshTokens);
297
- }
298
- refreshTokens.add(refreshToken);
284
+ };
285
+ const refreshToken = encodeRefreshToken(refreshTokenRecord);
299
286
  return refreshToken;
300
287
  }
301
288
  validateRefreshToken(refreshToken) {
302
- const record = this.refreshTokens.get(refreshToken);
303
- if (!record) {
304
- return undefined;
289
+ const record = decodeRefreshToken(refreshToken);
290
+ (0, errors_1.assert)(record.projectId === this.projectId, "INVALID_REFRESH_TOKEN");
291
+ if (this instanceof TenantProjectState) {
292
+ (0, errors_1.assert)(record.tenantId === this.tenantId, "TENANT_ID_MISMATCH");
305
293
  }
306
294
  return {
307
295
  user: this.getUserByLocalIdAssertingExists(record.localId),
@@ -356,8 +344,6 @@ class ProjectState {
356
344
  this.localIdForPhoneNumber.clear();
357
345
  this.localIdsForProviderEmail.clear();
358
346
  this.userIdForProviderRawId.clear();
359
- this.refreshTokens.clear();
360
- this.refreshTokensForLocalId.clear();
361
347
  }
362
348
  getUserCount() {
363
349
  return this.users.size;
@@ -617,6 +603,23 @@ var BlockingFunctionEvents;
617
603
  BlockingFunctionEvents["BEFORE_CREATE"] = "beforeCreate";
618
604
  BlockingFunctionEvents["BEFORE_SIGN_IN"] = "beforeSignIn";
619
605
  })(BlockingFunctionEvents = exports.BlockingFunctionEvents || (exports.BlockingFunctionEvents = {}));
606
+ function encodeRefreshToken(refreshTokenRecord) {
607
+ return Buffer.from(JSON.stringify(refreshTokenRecord), "utf8").toString("base64");
608
+ }
609
+ exports.encodeRefreshToken = encodeRefreshToken;
610
+ function decodeRefreshToken(refreshTokenString) {
611
+ let refreshTokenRecord;
612
+ try {
613
+ const json = Buffer.from(refreshTokenString, "base64").toString("utf8");
614
+ refreshTokenRecord = JSON.parse(json);
615
+ }
616
+ catch (_a) {
617
+ throw new errors_1.BadRequestError("INVALID_REFRESH_TOKEN");
618
+ }
619
+ (0, errors_1.assert)(refreshTokenRecord._AuthEmulatorRefreshToken, "INVALID_REFRESH_TOKEN");
620
+ return refreshTokenRecord;
621
+ }
622
+ exports.decodeRefreshToken = decodeRefreshToken;
620
623
  function getProviderEmailsForUser(user) {
621
624
  var _a;
622
625
  const emails = new Set();
@@ -268,16 +268,19 @@ async function startAll(options, showUI = true) {
268
268
  const emulatableBackends = [];
269
269
  const projectDir = (options.extDevDir || options.config.projectDir);
270
270
  if (shouldStart(options, types_1.Emulators.FUNCTIONS)) {
271
- const functionsCfg = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions)[0];
271
+ const functionsCfg = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions);
272
272
  utils.assertIsStringOrUndefined(options.extDevDir);
273
- const functionsDir = path.join(projectDir, functionsCfg.source);
274
- emulatableBackends.push({
275
- functionsDir,
276
- env: Object.assign({}, options.extDevEnv),
277
- secretEnv: [],
278
- predefinedTriggers: options.extDevTriggers,
279
- nodeMajorVersion: (0, functionsEmulatorUtils_1.parseRuntimeVersion)(options.extDevNodeVersion || functionsCfg.runtime),
280
- });
273
+ for (const cfg of functionsCfg) {
274
+ const functionsDir = path.join(projectDir, cfg.source);
275
+ emulatableBackends.push({
276
+ functionsDir,
277
+ codebase: cfg.codebase,
278
+ env: Object.assign({}, options.extDevEnv),
279
+ secretEnv: [],
280
+ predefinedTriggers: options.extDevTriggers,
281
+ nodeMajorVersion: (0, functionsEmulatorUtils_1.parseRuntimeVersion)(options.extDevNodeVersion || cfg.runtime),
282
+ });
283
+ }
281
284
  }
282
285
  if (shouldStart(options, types_1.Emulators.EXTENSIONS) && previews_1.previews.extensionsemulator) {
283
286
  const projectNumber = constants_1.Constants.isDemoProject(projectId)
@@ -56,7 +56,15 @@ class DatabaseEmulator {
56
56
  if (!c.instance) {
57
57
  continue;
58
58
  }
59
- await this.updateRules(c.instance, c.rules);
59
+ try {
60
+ await this.updateRules(c.instance, c.rules);
61
+ }
62
+ catch (e) {
63
+ const rulesError = this.prettyPrintRulesError(c.rules, e);
64
+ this.logger.logLabeled("WARN", "database", rulesError);
65
+ this.logger.logLabeled("WARN", "database", "Failed to update rules");
66
+ throw new error_1.FirebaseError(`Failed to load initial ${constants_1.Constants.description(this.getName())} rules:\n${rulesError}`);
67
+ }
60
68
  }
61
69
  }
62
70
  }
@@ -114,6 +122,7 @@ class DatabaseEmulator {
114
122
  });
115
123
  }
116
124
  async updateRules(instance, rulesPath) {
125
+ var _a;
117
126
  const rulesExt = path.extname(rulesPath);
118
127
  const content = rulesExt === ".bolt"
119
128
  ? parseBoltRules(rulesPath).toString()
@@ -131,12 +140,36 @@ class DatabaseEmulator {
131
140
  if (e.context && e.context.body) {
132
141
  throw e.context.body.error;
133
142
  }
134
- throw e.original;
143
+ throw (_a = e.original) !== null && _a !== void 0 ? _a : e;
135
144
  }
136
145
  }
137
146
  prettyPrintRulesError(filePath, error) {
147
+ let errStr;
148
+ switch (typeof error) {
149
+ case "string":
150
+ errStr = error;
151
+ break;
152
+ case "object":
153
+ if (error != null && "message" in error) {
154
+ const message = error.message;
155
+ errStr = `${message}`;
156
+ if (typeof message === "string") {
157
+ try {
158
+ const parsed = JSON.parse(message);
159
+ if (typeof parsed === "object" && parsed.error) {
160
+ errStr = `${parsed.error}`;
161
+ }
162
+ }
163
+ catch (_) {
164
+ }
165
+ }
166
+ break;
167
+ }
168
+ default:
169
+ errStr = `Unknown error: ${JSON.stringify(error)}`;
170
+ }
138
171
  const relativePath = path.relative(process.cwd(), filePath);
139
- return `${clc.cyan(relativePath)}:${error.trim()}`;
172
+ return `${clc.cyan(relativePath)}:${errStr.trim()}`;
140
173
  }
141
174
  }
142
175
  exports.DatabaseEmulator = DatabaseEmulator;
@@ -83,15 +83,15 @@ exports.DownloadDetails = {
83
83
  },
84
84
  }
85
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"),
86
+ version: "1.6.6",
87
+ downloadPath: path.join(CACHE_DIR, "ui-v1.6.6.zip"),
88
+ unzipDir: path.join(CACHE_DIR, "ui-v1.6.6"),
89
+ binaryPath: path.join(CACHE_DIR, "ui-v1.6.6", "server.bundle.js"),
90
90
  opts: {
91
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",
92
+ remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.6.6.zip",
93
+ expectedSize: 3817247,
94
+ expectedChecksum: "c80a3f0ae1e3f682ace0a18a9cdd2861",
95
95
  namePrefix: "ui",
96
96
  },
97
97
  },
@@ -152,6 +152,7 @@ class ExtensionsEmulator {
152
152
  return emulatableBackend;
153
153
  }
154
154
  autoPopulatedParams(instance) {
155
+ var _a;
155
156
  const projectId = this.args.projectId;
156
157
  return {
157
158
  PROJECT_ID: projectId !== null && projectId !== void 0 ? projectId : "",
@@ -159,6 +160,8 @@ class ExtensionsEmulator {
159
160
  DATABASE_INSTANCE: projectId !== null && projectId !== void 0 ? projectId : "",
160
161
  DATABASE_URL: `https://${projectId}.firebaseio.com`,
161
162
  STORAGE_BUCKET: `${projectId}.appspot.com`,
163
+ ALLOWED_EVENT_TYPES: instance.allowedEventTypes ? instance.allowedEventTypes.join(",") : "",
164
+ EVENTARC_CHANNEL: (_a = instance.eventarcChannel) !== null && _a !== void 0 ? _a : "",
162
165
  };
163
166
  }
164
167
  async checkAndWarnAPIs(instances) {