firebase-tools 10.7.1 → 10.9.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 (63) 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-list.js +12 -20
  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 +47 -14
  10. package/lib/deploy/functions/build.js +28 -9
  11. package/lib/deploy/functions/checkIam.js +65 -53
  12. package/lib/deploy/functions/containerCleaner.js +8 -7
  13. package/lib/deploy/functions/functionsDeployHelper.js +1 -1
  14. package/lib/deploy/functions/prepare.js +42 -15
  15. package/lib/deploy/functions/pricing.js +2 -2
  16. package/lib/deploy/functions/release/executor.js +1 -1
  17. package/lib/deploy/functions/release/fabricator.js +66 -11
  18. package/lib/deploy/functions/release/index.js +0 -21
  19. package/lib/deploy/functions/runtimes/discovery/index.js +2 -1
  20. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +13 -1
  21. package/lib/deploy/functions/runtimes/golang/index.js +3 -0
  22. package/lib/deploy/functions/runtimes/node/index.js +23 -20
  23. package/lib/deploy/functions/runtimes/node/parseTriggers.js +108 -1
  24. package/lib/deploy/functions/services/storage.js +6 -12
  25. package/lib/deploy/functions/validate.js +80 -16
  26. package/lib/deploy/index.js +2 -1
  27. package/lib/emulator/auth/cloudFunctions.js +6 -2
  28. package/lib/emulator/auth/operations.js +0 -1
  29. package/lib/emulator/auth/server.js +8 -1
  30. package/lib/emulator/auth/state.js +27 -24
  31. package/lib/emulator/controller.js +10 -5
  32. package/lib/emulator/databaseEmulator.js +36 -3
  33. package/lib/emulator/downloadableEmulators.js +18 -34
  34. package/lib/emulator/extensionsEmulator.js +4 -1
  35. package/lib/emulator/functionsEmulator.js +6 -7
  36. package/lib/emulator/functionsEmulatorRuntime.js +1 -1
  37. package/lib/emulator/functionsEmulatorShared.js +3 -0
  38. package/lib/emulator/functionsEmulatorUtils.js +5 -1
  39. package/lib/emulator/storage/apis/firebase.js +26 -4
  40. package/lib/extensions/askUserForEventsConfig.js +97 -0
  41. package/lib/extensions/export.js +7 -0
  42. package/lib/extensions/extensionsApi.js +47 -7
  43. package/lib/extensions/manifest.js +1 -1
  44. package/lib/extensions/paramHelper.js +2 -0
  45. package/lib/extensions/updateHelper.js +7 -1
  46. package/lib/extensions/warnings.js +11 -4
  47. package/lib/frameworks/index.js +111 -0
  48. package/lib/functions/functionslog.js +4 -9
  49. package/lib/gcp/cloudfunctions.js +1 -1
  50. package/lib/gcp/cloudfunctionsv2.js +14 -9
  51. package/lib/gcp/serviceusage.js +24 -0
  52. package/lib/hosting/normalizedHostingConfigs.js +3 -0
  53. package/lib/previews.js +1 -1
  54. package/lib/serve/index.js +2 -1
  55. package/lib/throttler/throttler.js +2 -1
  56. package/npm-shrinkwrap.json +315 -534
  57. package/package.json +4 -4
  58. package/templates/extensions/javascript/package.lint.json +5 -5
  59. package/templates/extensions/javascript/package.nolint.json +3 -3
  60. package/templates/extensions/typescript/package.lint.json +8 -7
  61. package/templates/extensions/typescript/package.nolint.json +2 -1
  62. package/templates/init/functions/typescript/package.lint.json +1 -0
  63. package/templates/init/functions/typescript/package.nolint.json +5 -5
@@ -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.endpointsAreUnique = exports.endpointsAreValid = void 0;
3
+ exports.secretsAreValid = exports.functionIdsAreValid = exports.functionsDirectoryExists = exports.endpointsAreUnique = exports.cpuConfigIsValid = exports.endpointsAreValid = void 0;
4
4
  const path = require("path");
5
5
  const clc = require("cli-color");
6
6
  const error_1 = require("../../error");
@@ -11,34 +11,98 @@ const backend = require("./backend");
11
11
  const utils = require("../../utils");
12
12
  const secrets = require("../../functions/secrets");
13
13
  const services_1 = require("./services");
14
+ function matchingIds(endpoints, filter) {
15
+ return endpoints
16
+ .filter(filter)
17
+ .map((endpoint) => endpoint.id)
18
+ .join(",");
19
+ }
20
+ const mem = (endpoint) => endpoint.availableMemoryMb || backend.DEFAULT_MEMORY;
21
+ const cpu = (endpoint) => {
22
+ var _a;
23
+ return endpoint.cpu === "gcf_gen1"
24
+ ? backend.memoryToGen1Cpu(mem(endpoint))
25
+ : (_a = endpoint.cpu) !== null && _a !== void 0 ? _a : backend.memoryToGen2Cpu(mem(endpoint));
26
+ };
14
27
  function endpointsAreValid(wantBackend) {
15
28
  const endpoints = backend.allEndpoints(wantBackend);
16
29
  functionIdsAreValid(endpoints);
17
30
  for (const ep of endpoints) {
18
31
  (0, services_1.serviceForEndpoint)(ep).validateTrigger(ep, wantBackend);
19
32
  }
20
- const gcfV1WithConcurrency = endpoints
21
- .filter((endpoint) => (endpoint.concurrency || 1) !== 1 && endpoint.platform === "gcfv1")
22
- .map((endpoint) => endpoint.id);
33
+ const gcfV1WithConcurrency = matchingIds(endpoints, (endpoint) => (endpoint.concurrency || 1) !== 1 && endpoint.platform === "gcfv1");
23
34
  if (gcfV1WithConcurrency.length) {
24
- const msg = `Cannot set concurrency on the functions ${gcfV1WithConcurrency.join(",")} because they are GCF gen 1`;
35
+ const msg = `Cannot set concurrency on the functions ${gcfV1WithConcurrency} because they are GCF gen 1`;
25
36
  throw new error_1.FirebaseError(msg);
26
37
  }
27
- const tooSmallForConcurrency = endpoints
28
- .filter((endpoint) => {
38
+ const tooSmallForConcurrency = matchingIds(endpoints, (endpoint) => {
29
39
  if ((endpoint.concurrency || 1) === 1) {
30
40
  return false;
31
41
  }
32
- const mem = endpoint.availableMemoryMb || backend.DEFAULT_MEMORY;
33
- return mem < backend.MIN_MEMORY_FOR_CONCURRENCY;
34
- })
35
- .map((endpoint) => endpoint.id);
42
+ return cpu(endpoint) < backend.MIN_CPU_FOR_CONCURRENCY;
43
+ });
36
44
  if (tooSmallForConcurrency.length) {
37
- const msg = `Cannot set concurency on the functions ${tooSmallForConcurrency.join(",")} because they have fewer than 2GB memory`;
45
+ const msg = "The following functions are configured to allow concurrent " +
46
+ "execution and less than one full CPU. This is not supported: " +
47
+ tooSmallForConcurrency;
38
48
  throw new error_1.FirebaseError(msg);
39
49
  }
50
+ cpuConfigIsValid(endpoints);
40
51
  }
41
52
  exports.endpointsAreValid = endpointsAreValid;
53
+ function cpuConfigIsValid(endpoints) {
54
+ const gcfV1WithCPU = matchingIds(endpoints, (endpoint) => endpoint.platform === "gcfv1" && typeof endpoint["cpu"] !== "undefined");
55
+ if (gcfV1WithCPU.length) {
56
+ const msg = `Cannot set CPU on the functions ${gcfV1WithCPU} because they are GCF gen 1`;
57
+ throw new error_1.FirebaseError(msg);
58
+ }
59
+ const invalidCPU = matchingIds(endpoints, (endpoint) => {
60
+ const c = cpu(endpoint);
61
+ if (c < 0.08) {
62
+ return true;
63
+ }
64
+ if (c < 1) {
65
+ return false;
66
+ }
67
+ return ![1, 2, 4, 6, 8].includes(c);
68
+ });
69
+ if (invalidCPU.length) {
70
+ const msg = `The following functions have invalid CPU settings ${invalidCPU}. Valid CPU options are (0.08, 1], 2, 4, 6, 8, or "gcf_gen1"`;
71
+ throw new error_1.FirebaseError(msg);
72
+ }
73
+ const smallCPURegions = ["australia-southeast2", "asia-northeast3", "asia-south2"];
74
+ const tooBigCPUForRegion = matchingIds(endpoints, (endpoint) => smallCPURegions.includes(endpoint.region) && cpu(endpoint) > 4);
75
+ if (tooBigCPUForRegion) {
76
+ const msg = `The functions ${tooBigCPUForRegion} have > 4 CPU in a region that supports a maximum 4 CPU`;
77
+ throw new error_1.FirebaseError(msg);
78
+ }
79
+ const tooSmallCPUSmall = matchingIds(endpoints, (endpoint) => mem(endpoint) > 512 && cpu(endpoint) < 0.5);
80
+ if (tooSmallCPUSmall) {
81
+ const msg = `The functions ${tooSmallCPUSmall} have too little CPU for their memory allocation. A minimum of 0.5 CPU is needed to set a memory limit greater than 512MiB`;
82
+ throw new error_1.FirebaseError(msg);
83
+ }
84
+ const tooSmallCPUBig = matchingIds(endpoints, (endpoint) => mem(endpoint) > 1024 && cpu(endpoint) < 1);
85
+ if (tooSmallCPUBig) {
86
+ const msg = `The functions ${tooSmallCPUSmall} have too little CPU for their memory allocation. A minimum of 1 CPU is needed to set a memory limit greater than 1GiB`;
87
+ throw new error_1.FirebaseError(msg);
88
+ }
89
+ const tooSmallMemory4CPU = matchingIds(endpoints, (endpoint) => cpu(endpoint) === 4 && mem(endpoint) < 2 << 10);
90
+ if (tooSmallMemory4CPU) {
91
+ const msg = `The functions ${tooSmallMemory4CPU} have too little memory for their CPU. Functions with 4 CPU require at least 2GiB`;
92
+ throw new error_1.FirebaseError(msg);
93
+ }
94
+ const tooSmallMemory6CPU = matchingIds(endpoints, (endpoint) => cpu(endpoint) === 6 && mem(endpoint) < 3 << 10);
95
+ if (tooSmallMemory6CPU) {
96
+ const msg = `The functions ${tooSmallMemory6CPU} have too little memory for their CPU. Functions with 6 CPU require at least 3GiB`;
97
+ throw new error_1.FirebaseError(msg);
98
+ }
99
+ const tooSmallMemory8CPU = matchingIds(endpoints, (endpoint) => cpu(endpoint) === 8 && mem(endpoint) < 4 << 10);
100
+ if (tooSmallMemory8CPU) {
101
+ const msg = `The functions ${tooSmallMemory8CPU} have too little memory for their CPU. Functions with 8 CPU require at least 4GiB`;
102
+ throw new error_1.FirebaseError(msg);
103
+ }
104
+ }
105
+ exports.cpuConfigIsValid = cpuConfigIsValid;
42
106
  function endpointsAreUnique(backends) {
43
107
  const endpointToCodebases = {};
44
108
  for (const [codebase, b] of Object.entries(backends)) {
@@ -86,7 +150,7 @@ function functionIdsAreValid(functions) {
86
150
  return fn.platform === "gcfv2" && !v2FunctionName.test(fn.id);
87
151
  });
88
152
  if (invalidV2Ids.length !== 0) {
89
- const msg = `${invalidV2Ids.map((f) => f.id).join(", ")} v2 function name(s) can only contin lower ` +
153
+ const msg = `${invalidV2Ids.map((f) => f.id).join(", ")} v2 function name(s) can only contain lower ` +
90
154
  `case letters, numbers, hyphens, and not exceed 62 characters in length`;
91
155
  throw new error_1.FirebaseError(msg);
92
156
  }
@@ -100,13 +164,13 @@ async function secretsAreValid(projectId, wantBackend) {
100
164
  await validateSecretVersions(projectId, endpoints);
101
165
  }
102
166
  exports.secretsAreValid = secretsAreValid;
167
+ const secretsSupportedPlatforms = ["gcfv1", "gcfv2"];
103
168
  function validatePlatformTargets(endpoints) {
104
- const supportedPlatforms = ["gcfv1"];
105
- const unsupported = endpoints.filter((e) => !supportedPlatforms.includes(e.platform));
169
+ const unsupported = endpoints.filter((e) => !secretsSupportedPlatforms.includes(e.platform));
106
170
  if (unsupported.length > 0) {
107
171
  const errs = unsupported.map((e) => `${e.id}[platform=${e.platform}]`);
108
172
  throw new error_1.FirebaseError(`Tried to set secret environment variables on ${errs.join(", ")}. ` +
109
- `Only ${supportedPlatforms.join(", ")} support secret environments.`);
173
+ `Only ${secretsSupportedPlatforms.join(", ")} support secret environments.`);
110
174
  }
111
175
  }
112
176
  async function validateSecretVersions(projectId, endpoints) {
@@ -18,6 +18,7 @@ const FunctionsTarget = require("./functions");
18
18
  const StorageTarget = require("./storage");
19
19
  const RemoteConfigTarget = require("./remoteconfig");
20
20
  const ExtensionsTarget = require("./extensions");
21
+ const frameworks_1 = require("../frameworks");
21
22
  const TARGETS = {
22
23
  hosting: HostingTarget,
23
24
  database: DatabaseTarget,
@@ -45,7 +46,7 @@ const deploy = async function (targetNames, options, customContext = {}) {
45
46
  if (previews_1.previews.frameworkawareness && targetNames.includes("hosting")) {
46
47
  const config = options.config.get("hosting");
47
48
  if (Array.isArray(config) ? config.some((it) => it.source) : config.source) {
48
- await require("firebase-frameworks").prepare(targetNames, context, options);
49
+ await (0, frameworks_1.prepareFrameworks)(targetNames, context, options);
49
50
  }
50
51
  }
51
52
  for (const targetName of targetNames) {
@@ -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();
@@ -37,9 +37,10 @@ const config_1 = require("./storage/rules/config");
37
37
  const getDefaultDatabaseInstance_1 = require("../getDefaultDatabaseInstance");
38
38
  const auth_2 = require("../auth");
39
39
  const extensionsEmulator_1 = require("./extensionsEmulator");
40
- const previews_1 = require("../previews");
41
40
  const projectConfig_1 = require("../functions/projectConfig");
42
41
  const downloadableEmulators_1 = require("./downloadableEmulators");
42
+ const frameworks_1 = require("../frameworks");
43
+ const previews_1 = require("../previews");
43
44
  const START_LOGGING_EMULATOR = utils.envOverride("START_LOGGING_EMULATOR", "false", (val) => val === "true");
44
45
  async function getAndCheckAddress(emulator, options) {
45
46
  var _a, _b, _c, _d;
@@ -125,9 +126,7 @@ async function cleanShutdown() {
125
126
  exports.cleanShutdown = cleanShutdown;
126
127
  function filterEmulatorTargets(options) {
127
128
  let targets = [...types_1.ALL_SERVICE_EMULATORS];
128
- if (previews_1.previews.extensionsemulator) {
129
- targets.push(types_1.Emulators.EXTENSIONS);
130
- }
129
+ targets.push(types_1.Emulators.EXTENSIONS);
131
130
  targets = targets.filter((e) => {
132
131
  return options.config.has(e) || options.config.has(`emulators.${e}`);
133
132
  });
@@ -245,6 +244,12 @@ async function startAll(options, showUI = true) {
245
244
  }
246
245
  }
247
246
  }
247
+ if (previews_1.previews.frameworkawareness) {
248
+ const config = options.config.get("hosting");
249
+ if (Array.isArray(config) ? config.some((it) => it.source) : config.source) {
250
+ await (0, frameworks_1.prepareFrameworks)(targets, options, options);
251
+ }
252
+ }
248
253
  if (shouldStart(options, types_1.Emulators.HUB)) {
249
254
  const hubAddr = await getAndCheckAddress(types_1.Emulators.HUB, options);
250
255
  const hub = new hub_1.EmulatorHub(Object.assign({ projectId }, hubAddr));
@@ -282,7 +287,7 @@ async function startAll(options, showUI = true) {
282
287
  });
283
288
  }
284
289
  }
285
- if (shouldStart(options, types_1.Emulators.EXTENSIONS) && previews_1.previews.extensionsemulator) {
290
+ if (shouldStart(options, types_1.Emulators.EXTENSIONS)) {
286
291
  const projectNumber = constants_1.Constants.isDemoProject(projectId)
287
292
  ? constants_1.Constants.FAKE_PROJECT_NUMBER
288
293
  : await (0, projectUtils_1.needProjectNumber)(options);
@@ -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;
@@ -50,15 +50,15 @@ exports.DownloadDetails = {
50
50
  namePrefix: "cloud-storage-rules-emulator",
51
51
  },
52
52
  },
53
- ui: previews_1.previews.extensionsemulator
53
+ ui: previews_1.previews.emulatoruisnapshot
54
54
  ? {
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"),
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"),
59
59
  opts: {
60
60
  cacheDir: CACHE_DIR,
61
- remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-vEXTENSIONS.zip",
61
+ remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-vSNAPSHOT.zip",
62
62
  expectedSize: -1,
63
63
  expectedChecksum: "",
64
64
  skipCache: true,
@@ -66,35 +66,19 @@ exports.DownloadDetails = {
66
66
  namePrefix: "ui",
67
67
  },
68
68
  }
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
- },
69
+ : {
70
+ version: "1.7.0",
71
+ downloadPath: path.join(CACHE_DIR, "ui-v1.7.0.zip"),
72
+ unzipDir: path.join(CACHE_DIR, "ui-v1.7.0"),
73
+ binaryPath: path.join(CACHE_DIR, "ui-v1.7.0", "server.bundle.js"),
74
+ opts: {
75
+ cacheDir: CACHE_DIR,
76
+ remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.7.0.zip",
77
+ expectedSize: 4053708,
78
+ expectedChecksum: "aea9ae19091df5974a88a8847aaf127c",
79
+ namePrefix: "ui",
97
80
  },
81
+ },
98
82
  pubsub: {
99
83
  downloadPath: path.join(CACHE_DIR, "pubsub-emulator-0.1.0.zip"),
100
84
  version: "0.1.0",