firebase-tools 9.16.6 → 9.20.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 (82) hide show
  1. package/CHANGELOG.md +1 -3
  2. package/lib/api.js +1 -0
  3. package/lib/apiv2.js +1 -1
  4. package/lib/appdistribution/client.js +84 -72
  5. package/lib/appdistribution/distribution.js +8 -26
  6. package/lib/appdistribution/options-parser-util.js +51 -0
  7. package/lib/command.js +8 -6
  8. package/lib/commands/appdistribution-distribute.js +74 -91
  9. package/lib/commands/appdistribution-testers-add.js +18 -0
  10. package/lib/commands/appdistribution-testers-remove.js +32 -0
  11. package/lib/commands/crashlytics-symbols-upload.js +146 -0
  12. package/lib/commands/ext-configure.js +9 -1
  13. package/lib/commands/ext-dev-extension-delete.js +2 -1
  14. package/lib/commands/ext-dev-init.js +18 -9
  15. package/lib/commands/ext-dev-publish.js +11 -4
  16. package/lib/commands/ext-dev-unpublish.js +2 -1
  17. package/lib/commands/ext-install.js +115 -48
  18. package/lib/commands/ext-uninstall.js +6 -0
  19. package/lib/commands/ext-update.js +67 -43
  20. package/lib/commands/functions-config-export.js +115 -0
  21. package/lib/commands/functions-delete.js +44 -35
  22. package/lib/commands/functions-list.js +54 -0
  23. package/lib/commands/functions-log.js +5 -22
  24. package/lib/commands/hosting-channel-deploy.js +6 -4
  25. package/lib/commands/index.js +12 -0
  26. package/lib/deploy/functions/backend.js +47 -12
  27. package/lib/deploy/functions/containerCleaner.js +5 -1
  28. package/lib/deploy/functions/deploy.js +7 -5
  29. package/lib/deploy/functions/prepare.js +9 -7
  30. package/lib/deploy/functions/prompts.js +3 -21
  31. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +2 -1
  32. package/lib/deploy/functions/runtimes/index.js +2 -1
  33. package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +4 -3
  34. package/lib/deploy/functions/runtimes/node/parseTriggers.js +14 -9
  35. package/lib/deploy/functions/triggerRegionHelper.js +32 -0
  36. package/lib/downloadUtils.js +37 -0
  37. package/lib/emulator/auth/apiSpec.js +1758 -404
  38. package/lib/emulator/auth/handlers.js +6 -5
  39. package/lib/emulator/auth/operations.js +429 -40
  40. package/lib/emulator/auth/server.js +18 -11
  41. package/lib/emulator/auth/state.js +186 -5
  42. package/lib/emulator/auth/widget_ui.js +2 -2
  43. package/lib/emulator/download.js +2 -31
  44. package/lib/emulator/downloadableEmulators.js +7 -7
  45. package/lib/emulator/emulatorLogger.js +0 -3
  46. package/lib/emulator/events/types.js +16 -0
  47. package/lib/emulator/functionsEmulator.js +102 -17
  48. package/lib/emulator/functionsEmulatorRuntime.js +46 -121
  49. package/lib/emulator/functionsEmulatorShared.js +51 -7
  50. package/lib/emulator/functionsEmulatorShell.js +1 -1
  51. package/lib/emulator/pubsubEmulator.js +61 -40
  52. package/lib/extensions/askUserForConsent.js +16 -13
  53. package/lib/extensions/askUserForParam.js +72 -3
  54. package/lib/extensions/billingMigrationHelper.js +1 -11
  55. package/lib/extensions/changelog.js +93 -0
  56. package/lib/extensions/displayExtensionInfo.js +38 -38
  57. package/lib/extensions/emulator/optionsHelper.js +3 -3
  58. package/lib/extensions/emulator/triggerHelper.js +2 -32
  59. package/lib/extensions/extensionsApi.js +69 -95
  60. package/lib/extensions/extensionsHelper.js +75 -50
  61. package/lib/extensions/paramHelper.js +79 -36
  62. package/lib/extensions/refs.js +59 -0
  63. package/lib/extensions/resolveSource.js +2 -20
  64. package/lib/extensions/secretsUtils.js +58 -0
  65. package/lib/extensions/updateHelper.js +39 -105
  66. package/lib/extensions/warnings.js +1 -7
  67. package/lib/functional.js +64 -0
  68. package/lib/functions/env.js +26 -13
  69. package/lib/functions/functionslog.js +40 -0
  70. package/lib/functions/listFunctions.js +10 -0
  71. package/lib/functions/runtimeConfigExport.js +137 -0
  72. package/lib/gcp/cloudfunctions.js +84 -9
  73. package/lib/gcp/cloudfunctionsv2.js +99 -7
  74. package/lib/gcp/cloudlogging.js +27 -21
  75. package/lib/gcp/secretManager.js +111 -0
  76. package/lib/gcp/storage.js +16 -0
  77. package/lib/previews.js +1 -1
  78. package/lib/requireInteractive.js +12 -0
  79. package/package.json +5 -4
  80. package/schema/firebase-config.json +2 -1
  81. package/templates/extensions/CHANGELOG.md +7 -0
  82. package/templates/init/hosting/index.html +10 -10
@@ -83,7 +83,7 @@ async function createApp(defaultProjectId, projectStateForId = new Map()) {
83
83
  res.json(specWithEmulatorServer(req.protocol, req.headers.host));
84
84
  });
85
85
  registerLegacyRoutes(app);
86
- handlers_1.registerHandlers(app, (apiKey) => getProjectStateById(getProjectIdByApiKey(apiKey)));
86
+ handlers_1.registerHandlers(app, (apiKey, tenantId) => getProjectStateById(getProjectIdByApiKey(apiKey), tenantId));
87
87
  const apiKeyAuthenticator = (ctx, info) => {
88
88
  if (info.in !== "query") {
89
89
  throw new Error('apiKey must be defined as in: "query" in API spec.');
@@ -169,6 +169,9 @@ async function createApp(defaultProjectId, projectStateForId = new Map()) {
169
169
  "google-fieldmask"() {
170
170
  return true;
171
171
  },
172
+ "google-duration"() {
173
+ return true;
174
+ },
172
175
  uint64() {
173
176
  return true;
174
177
  },
@@ -234,13 +237,16 @@ async function createApp(defaultProjectId, projectStateForId = new Map()) {
234
237
  apiKey;
235
238
  return defaultProjectId;
236
239
  }
237
- function getProjectStateById(projectId) {
238
- let state = projectStateForId.get(projectId);
239
- if (!state) {
240
- state = new state_1.ProjectState(projectId);
241
- projectStateForId.set(projectId, state);
240
+ function getProjectStateById(projectId, tenantId) {
241
+ let agentState = projectStateForId.get(projectId);
242
+ if (!agentState) {
243
+ agentState = new state_1.AgentProjectState(projectId);
244
+ projectStateForId.set(projectId, agentState);
245
+ }
246
+ if (!tenantId) {
247
+ return agentState;
242
248
  }
243
- return state;
249
+ return agentState.getTenantProject(tenantId);
244
250
  }
245
251
  }
246
252
  exports.createApp = createApp;
@@ -328,7 +334,7 @@ function toExegesisController(ops, getProjectStateById) {
328
334
  }
329
335
  function toExegesisOperation(operation) {
330
336
  return (ctx) => {
331
- var _a, _b, _c, _d;
337
+ var _a, _b, _c, _d, _e;
332
338
  let targetProjectId = ctx.params.path.targetProjectId || ((_a = ctx.requestBody) === null || _a === void 0 ? void 0 : _a.targetProjectId);
333
339
  if (targetProjectId) {
334
340
  if ((_b = ctx.api.operationObject.security) === null || _b === void 0 ? void 0 : _b.some((sec) => sec.Oauth2)) {
@@ -338,10 +344,11 @@ function toExegesisController(ops, getProjectStateById) {
338
344
  else {
339
345
  targetProjectId = ctx.user;
340
346
  }
341
- if (ctx.params.path.tenantId || ((_d = ctx.requestBody) === null || _d === void 0 ? void 0 : _d.tenantId)) {
342
- throw new errors_2.NotImplementedError("Multi-tenancy is unimplemented.");
347
+ if (ctx.params.path.tenantId && ((_d = ctx.requestBody) === null || _d === void 0 ? void 0 : _d.tenantId)) {
348
+ errors_2.assert(ctx.params.path.tenantId === ctx.requestBody.tenantId, "TENANT_ID_MISMATCH");
343
349
  }
344
- return operation(getProjectStateById(targetProjectId), ctx.requestBody, ctx);
350
+ const targetTenantId = ctx.params.path.tenantId || ((_e = ctx.requestBody) === null || _e === void 0 ? void 0 : _e.tenantId);
351
+ return operation(getProjectStateById(targetProjectId, targetTenantId), ctx.requestBody, ctx);
345
352
  };
346
353
  }
347
354
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ProjectState = exports.SIGNIN_METHOD_EMAIL_LINK = exports.PROVIDER_CUSTOM = exports.PROVIDER_ANONYMOUS = exports.PROVIDER_PHONE = exports.PROVIDER_PASSWORD = void 0;
3
+ 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");
@@ -8,6 +8,7 @@ exports.PROVIDER_PASSWORD = "password";
8
8
  exports.PROVIDER_PHONE = "phone";
9
9
  exports.PROVIDER_ANONYMOUS = "anonymous";
10
10
  exports.PROVIDER_CUSTOM = "custom";
11
+ exports.PROVIDER_GAME_CENTER = "gc.apple.com";
11
12
  exports.SIGNIN_METHOD_EMAIL_LINK = "emailLink";
12
13
  class ProjectState {
13
14
  constructor(projectId) {
@@ -23,8 +24,6 @@ class ProjectState {
23
24
  this.oobs = new Map();
24
25
  this.verificationCodes = new Map();
25
26
  this.temporaryProofs = new Map();
26
- this.oneAccountPerEmail = true;
27
- this.authCloudFunction = new cloudFunctions_1.AuthCloudFunction(projectId);
28
27
  }
29
28
  get projectNumber() {
30
29
  return "12345";
@@ -281,10 +280,16 @@ class ProjectState {
281
280
  getUserByLocalId(localId) {
282
281
  return this.users.get(localId);
283
282
  }
284
- createRefreshTokenFor(userInfo, provider, extraClaims = {}) {
283
+ createRefreshTokenFor(userInfo, provider, { extraClaims = {}, secondFactor, } = {}) {
285
284
  const localId = userInfo.localId;
286
285
  const refreshToken = utils_1.randomBase64UrlStr(204);
287
- this.refreshTokens.set(refreshToken, { localId, provider, extraClaims });
286
+ this.refreshTokens.set(refreshToken, {
287
+ localId,
288
+ provider,
289
+ extraClaims,
290
+ secondFactor,
291
+ tenantId: userInfo.tenantId,
292
+ });
288
293
  let refreshTokens = this.refreshTokensForLocalId.get(localId);
289
294
  if (!refreshTokens) {
290
295
  refreshTokens = new Set();
@@ -302,6 +307,7 @@ class ProjectState {
302
307
  user: this.getUserByLocalIdAssertingExists(record.localId),
303
308
  provider: record.provider,
304
309
  extraClaims: record.extraClaims,
310
+ secondFactor: record.secondFactor,
305
311
  };
306
312
  }
307
313
  createOob(email, requestType, generateLink) {
@@ -413,6 +419,176 @@ class ProjectState {
413
419
  }
414
420
  }
415
421
  exports.ProjectState = ProjectState;
422
+ class AgentProjectState extends ProjectState {
423
+ constructor(projectId) {
424
+ super(projectId);
425
+ this._oneAccountPerEmail = true;
426
+ this._usageMode = UsageMode.DEFAULT;
427
+ this.tenantProjectForTenantId = new Map();
428
+ this._authCloudFunction = new cloudFunctions_1.AuthCloudFunction(this.projectId);
429
+ }
430
+ get authCloudFunction() {
431
+ return this._authCloudFunction;
432
+ }
433
+ get oneAccountPerEmail() {
434
+ return this._oneAccountPerEmail;
435
+ }
436
+ set oneAccountPerEmail(oneAccountPerEmail) {
437
+ this._oneAccountPerEmail = oneAccountPerEmail;
438
+ }
439
+ get usageMode() {
440
+ return this._usageMode;
441
+ }
442
+ set usageMode(usageMode) {
443
+ this._usageMode = usageMode;
444
+ }
445
+ get allowPasswordSignup() {
446
+ return true;
447
+ }
448
+ get disableAuth() {
449
+ return false;
450
+ }
451
+ get mfaConfig() {
452
+ return { state: "ENABLED", enabledProviders: ["PHONE_SMS"] };
453
+ }
454
+ get enableAnonymousUser() {
455
+ return true;
456
+ }
457
+ get enableEmailLinkSignin() {
458
+ return true;
459
+ }
460
+ getTenantProject(tenantId) {
461
+ if (!this.tenantProjectForTenantId.has(tenantId)) {
462
+ this.createTenantWithTenantId(tenantId, { tenantId });
463
+ }
464
+ return this.tenantProjectForTenantId.get(tenantId);
465
+ }
466
+ listTenants(startToken) {
467
+ const tenantProjects = [];
468
+ for (const tenantProject of this.tenantProjectForTenantId.values()) {
469
+ if (!startToken || tenantProject.tenantId > startToken) {
470
+ tenantProjects.push(tenantProject);
471
+ }
472
+ }
473
+ tenantProjects.sort((a, b) => {
474
+ if (a.tenantId < b.tenantId) {
475
+ return -1;
476
+ }
477
+ else if (a.tenantId > b.tenantId) {
478
+ return 1;
479
+ }
480
+ return 0;
481
+ });
482
+ return tenantProjects.map((tenantProject) => tenantProject.tenantConfig);
483
+ }
484
+ createTenant(tenant) {
485
+ for (let i = 0; i < 10; i++) {
486
+ const tenantId = utils_1.randomId(28);
487
+ const createdTenant = this.createTenantWithTenantId(tenantId, tenant);
488
+ if (createdTenant) {
489
+ return createdTenant;
490
+ }
491
+ }
492
+ throw new Error("Could not generate a random unique tenantId after 10 tries");
493
+ }
494
+ createTenantWithTenantId(tenantId, tenant) {
495
+ if (this.tenantProjectForTenantId.has(tenantId)) {
496
+ return undefined;
497
+ }
498
+ tenant.name = `projects/${this.projectId}/tenants/${tenantId}`;
499
+ tenant.tenantId = tenantId;
500
+ this.tenantProjectForTenantId.set(tenantId, new TenantProjectState(this.projectId, tenantId, tenant, this));
501
+ return tenant;
502
+ }
503
+ deleteTenant(tenantId) {
504
+ this.tenantProjectForTenantId.delete(tenantId);
505
+ }
506
+ }
507
+ exports.AgentProjectState = AgentProjectState;
508
+ class TenantProjectState extends ProjectState {
509
+ constructor(projectId, tenantId, _tenantConfig, parentProject) {
510
+ super(projectId);
511
+ this.tenantId = tenantId;
512
+ this._tenantConfig = _tenantConfig;
513
+ this.parentProject = parentProject;
514
+ }
515
+ get oneAccountPerEmail() {
516
+ return this.parentProject.oneAccountPerEmail;
517
+ }
518
+ get authCloudFunction() {
519
+ return this.parentProject.authCloudFunction;
520
+ }
521
+ get usageMode() {
522
+ return this.parentProject.usageMode;
523
+ }
524
+ get tenantConfig() {
525
+ return this._tenantConfig;
526
+ }
527
+ get allowPasswordSignup() {
528
+ var _a;
529
+ return (_a = this._tenantConfig.allowPasswordSignup) !== null && _a !== void 0 ? _a : true;
530
+ }
531
+ get disableAuth() {
532
+ var _a;
533
+ return (_a = this._tenantConfig.disableAuth) !== null && _a !== void 0 ? _a : false;
534
+ }
535
+ get mfaConfig() {
536
+ var _a;
537
+ return ((_a = this._tenantConfig.mfaConfig) !== null && _a !== void 0 ? _a : {
538
+ state: "ENABLED",
539
+ enabledProviders: ["PHONE_SMS"],
540
+ });
541
+ }
542
+ get enableAnonymousUser() {
543
+ var _a;
544
+ return (_a = this._tenantConfig.enableAnonymousUser) !== null && _a !== void 0 ? _a : true;
545
+ }
546
+ get enableEmailLinkSignin() {
547
+ var _a;
548
+ return (_a = this._tenantConfig.enableEmailLinkSignin) !== null && _a !== void 0 ? _a : true;
549
+ }
550
+ delete() {
551
+ this.parentProject.deleteTenant(this.tenantId);
552
+ }
553
+ updateTenant(update, updateMask) {
554
+ if (!updateMask) {
555
+ this._tenantConfig = Object.assign(Object.assign({}, update), { tenantId: this.tenantId, name: this.tenantConfig.name });
556
+ return this.tenantConfig;
557
+ }
558
+ const paths = updateMask.split(",");
559
+ for (const path of paths) {
560
+ const fields = path.split(".");
561
+ let updateField = update;
562
+ let existingField = this._tenantConfig;
563
+ let field;
564
+ for (let i = 0; i < fields.length - 1; i++) {
565
+ field = fields[i];
566
+ if (updateField[field] == null) {
567
+ console.warn(`Unable to find field '${field}' in update '${updateField}`);
568
+ break;
569
+ }
570
+ if (Array.isArray(updateField[field]) ||
571
+ Object(updateField[field]) !== updateField[field]) {
572
+ console.warn(`Field '${field}' is singular and cannot have sub-fields`);
573
+ break;
574
+ }
575
+ if (!existingField[field]) {
576
+ existingField[field] = {};
577
+ }
578
+ updateField = updateField[field];
579
+ existingField = existingField[field];
580
+ }
581
+ field = fields[fields.length - 1];
582
+ if (updateField[field] == null) {
583
+ console.warn(`Unable to find field '${field}' in update '${JSON.stringify(updateField)}`);
584
+ continue;
585
+ }
586
+ existingField[field] = updateField[field];
587
+ }
588
+ return this.tenantConfig;
589
+ }
590
+ }
591
+ exports.TenantProjectState = TenantProjectState;
416
592
  function getProviderEmailsForUser(user) {
417
593
  var _a;
418
594
  const emails = new Set();
@@ -423,3 +599,8 @@ function getProviderEmailsForUser(user) {
423
599
  });
424
600
  return emails;
425
601
  }
602
+ var UsageMode;
603
+ (function (UsageMode) {
604
+ UsageMode["DEFAULT"] = "DEFAULT";
605
+ UsageMode["PASSTHROUGH"] = "PASSTHROUGH";
606
+ })(UsageMode = exports.UsageMode || (exports.UsageMode = {}));
@@ -488,7 +488,7 @@ exports.WIDGET_UI = `
488
488
  <meta charset="utf-8">
489
489
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
490
490
  <title>Auth Emulator IDP Login Widget</title>
491
- <link href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css" rel="stylesheet">
491
+ <link href="https://unpkg.com/material-components-web@10/dist/material-components-web.min.css" rel="stylesheet">
492
492
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
493
493
  <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
494
494
  <style>${STYLE}</style>
@@ -601,6 +601,6 @@ exports.WIDGET_UI = `
601
601
  </div>
602
602
  </div>
603
603
  </div>
604
- <script src="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js"></script>
604
+ <script src="https://unpkg.com/material-components-web@10/dist/material-components-web.min.js"></script>
605
605
  <script>${SCRIPT}</script>
606
606
  `;
@@ -1,23 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.downloadEmulator = void 0;
4
- const url_1 = require("url");
5
4
  const crypto = require("crypto");
6
5
  const fs = require("fs-extra");
7
6
  const path = require("path");
8
- const ProgressBar = require("progress");
9
7
  const tmp = require("tmp");
10
8
  const unzipper = require("unzipper");
11
- const apiv2_1 = require("../apiv2");
12
9
  const emulatorLogger_1 = require("./emulatorLogger");
13
10
  const error_1 = require("../error");
14
11
  const downloadableEmulators = require("./downloadableEmulators");
12
+ const downloadUtils = require("../downloadUtils");
15
13
  tmp.setGracefulCleanup();
16
14
  async function downloadEmulator(name) {
17
15
  const emulator = downloadableEmulators.getDownloadDetails(name);
18
16
  emulatorLogger_1.EmulatorLogger.forEmulator(name).logLabeled("BULLET", name, `downloading ${path.basename(emulator.downloadPath)}...`);
19
17
  fs.ensureDirSync(emulator.opts.cacheDir);
20
- const tmpfile = await downloadToTmp(emulator.opts.remoteUrl);
18
+ const tmpfile = await downloadUtils.downloadToTmp(emulator.opts.remoteUrl);
21
19
  if (!emulator.opts.skipChecksumAndSize) {
22
20
  await validateSize(tmpfile, emulator.opts.expectedSize);
23
21
  await validateChecksum(tmpfile, emulator.opts.expectedChecksum);
@@ -58,33 +56,6 @@ function removeOldFiles(name, emulator, removeAllVersions = false) {
58
56
  }
59
57
  }
60
58
  }
61
- async function downloadToTmp(remoteUrl) {
62
- const u = new url_1.URL(remoteUrl);
63
- const c = new apiv2_1.Client({ urlPrefix: u.origin, auth: false });
64
- const tmpfile = tmp.fileSync();
65
- const writeStream = fs.createWriteStream(tmpfile.name);
66
- const res = await c.request({
67
- method: "GET",
68
- path: u.pathname,
69
- queryParams: u.searchParams,
70
- responseType: "stream",
71
- resolveOnHTTPError: true,
72
- });
73
- if (res.status !== 200) {
74
- throw new error_1.FirebaseError(`download failed, status ${res.status}`, { exit: 1 });
75
- }
76
- const total = parseInt(res.response.headers.get("content-length") || "0", 10);
77
- const totalMb = Math.ceil(total / 1000000);
78
- const bar = new ProgressBar(`Progress: :bar (:percent of ${totalMb}MB)`, { total, head: ">" });
79
- res.body.on("data", (chunk) => {
80
- bar.tick(chunk.length);
81
- });
82
- await new Promise((resolve) => {
83
- writeStream.on("finish", resolve);
84
- res.body.pipe(writeStream);
85
- });
86
- return tmpfile.name;
87
- }
88
59
  function validateSize(filepath, expectedSize) {
89
60
  return new Promise((resolve, reject) => {
90
61
  const stat = fs.statSync(filepath);
@@ -50,15 +50,15 @@ exports.DownloadDetails = {
50
50
  },
51
51
  },
52
52
  ui: {
53
- version: "1.6.2",
54
- downloadPath: path.join(CACHE_DIR, "ui-v1.6.2.zip"),
55
- unzipDir: path.join(CACHE_DIR, "ui-v1.6.2"),
56
- binaryPath: path.join(CACHE_DIR, "ui-v1.6.2", "server.bundle.js"),
53
+ version: "1.6.3",
54
+ downloadPath: path.join(CACHE_DIR, "ui-v1.6.3.zip"),
55
+ unzipDir: path.join(CACHE_DIR, "ui-v1.6.3"),
56
+ binaryPath: path.join(CACHE_DIR, "ui-v1.6.3", "server.bundle.js"),
57
57
  opts: {
58
58
  cacheDir: CACHE_DIR,
59
- remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.6.2.zip",
60
- expectedSize: 3756236,
61
- expectedChecksum: "e32cc238fd9d952e2bde05f45043e3d4",
59
+ remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.6.3.zip",
60
+ expectedSize: 3757268,
61
+ expectedChecksum: "153090a46072545aadeb307397cc8f45",
62
62
  namePrefix: "ui",
63
63
  },
64
64
  },
@@ -168,9 +168,6 @@ You can probably fix this by running "npm install ${systemLog.data.name}@latest"
168
168
  case "function-runtimeconfig-json-invalid":
169
169
  this.log("WARN", "Found .runtimeconfig.json but the JSON format is invalid.");
170
170
  break;
171
- case "function-env-load-failed":
172
- this.log("WARN", "Failed to load environment variables: " + systemLog.text);
173
- break;
174
171
  default:
175
172
  }
176
173
  }
@@ -9,5 +9,21 @@ class EventUtils {
9
9
  static isLegacyEvent(proto) {
10
10
  return _.has(proto, "data") && _.has(proto, "resource");
11
11
  }
12
+ static isBinaryCloudEvent(req) {
13
+ return !!(req.header("ce-type") &&
14
+ req.header("ce-specversion") &&
15
+ req.header("ce-source") &&
16
+ req.header("ce-id"));
17
+ }
18
+ static extractBinaryCloudEventContext(req) {
19
+ const context = {};
20
+ for (const name of Object.keys(req.headers)) {
21
+ if (name.startsWith("ce-")) {
22
+ const attributeName = name.substr("ce-".length);
23
+ context[attributeName] = req.header(name);
24
+ }
25
+ }
26
+ return context;
27
+ }
12
28
  }
13
29
  exports.EventUtils = EventUtils;
@@ -8,6 +8,7 @@ const express = require("express");
8
8
  const clc = require("cli-color");
9
9
  const http = require("http");
10
10
  const jwt = require("jsonwebtoken");
11
+ const url_1 = require("url");
11
12
  const api = require("../api");
12
13
  const logger_1 = require("../logger");
13
14
  const track = require("../track");
@@ -26,6 +27,8 @@ const workQueue_1 = require("./workQueue");
26
27
  const utils_1 = require("../utils");
27
28
  const defaultCredentials_1 = require("../defaultCredentials");
28
29
  const adminSdkConfig_1 = require("./adminSdkConfig");
30
+ const functionsEnv = require("../functions/env");
31
+ const types_2 = require("./events/types");
29
32
  const EVENT_INVOKE = "functions:invoke";
30
33
  const DATABASE_PATH_PATTERN = new RegExp("^projects/[^/]+/instances/([^/]+)/refs(/.*)$");
31
34
  class FunctionsEmulator {
@@ -88,11 +91,18 @@ class FunctionsEmulator {
88
91
  const multicastFunctionRoute = `/functions/projects/:project_id/trigger_multicast`;
89
92
  const httpsFunctionRoutes = [httpsFunctionRoute, `${httpsFunctionRoute}/*`];
90
93
  const backgroundHandler = (req, res) => {
94
+ var _a;
91
95
  const region = req.params.region;
92
96
  const triggerId = req.params.trigger_name;
93
97
  const projectId = req.params.project_id;
94
98
  const reqBody = req.rawBody;
95
- const proto = JSON.parse(reqBody.toString());
99
+ let proto = JSON.parse(reqBody.toString());
100
+ if ((_a = req.headers["content-type"]) === null || _a === void 0 ? void 0 : _a.includes("cloudevent")) {
101
+ if (types_2.EventUtils.isBinaryCloudEvent(req)) {
102
+ proto = types_2.EventUtils.extractBinaryCloudEventContext(req);
103
+ proto.data = req.body;
104
+ }
105
+ }
96
106
  this.workQueue.submit(() => {
97
107
  this.logger.log("DEBUG", `Accepted request ${req.method} ${req.url} --> ${triggerId}`);
98
108
  return this.handleBackgroundTrigger(projectId, triggerId, proto)
@@ -134,7 +144,7 @@ class FunctionsEmulator {
134
144
  });
135
145
  return hub;
136
146
  }
137
- startFunctionRuntime(triggerId, targetName, triggerType, proto, runtimeOpts) {
147
+ startFunctionRuntime(triggerId, targetName, signatureType, proto, runtimeOpts) {
138
148
  const bundleTemplate = this.getBaseBundle();
139
149
  const runtimeBundle = Object.assign(Object.assign({}, bundleTemplate), { emulators: {
140
150
  firestore: this.getEmulatorInfo(types_1.Emulators.FIRESTORE),
@@ -144,14 +154,12 @@ class FunctionsEmulator {
144
154
  storage: this.getEmulatorInfo(types_1.Emulators.STORAGE),
145
155
  }, nodeMajorVersion: this.args.nodeMajorVersion, proto,
146
156
  triggerId,
147
- targetName,
148
- triggerType });
157
+ targetName });
149
158
  const opts = runtimeOpts || {
150
159
  nodeBinary: this.nodeBinary,
151
- env: this.args.env,
152
160
  extensionTriggers: this.args.predefinedTriggers,
153
161
  };
154
- const worker = this.invokeRuntime(runtimeBundle, opts);
162
+ const worker = this.invokeRuntime(runtimeBundle, opts, this.getRuntimeEnvs({ targetName, signatureType }));
155
163
  return worker;
156
164
  }
157
165
  async start() {
@@ -206,9 +214,8 @@ class FunctionsEmulator {
206
214
  this.workerPool.refresh();
207
215
  const worker = this.invokeRuntime(this.getBaseBundle(), {
208
216
  nodeBinary: this.nodeBinary,
209
- env: this.args.env,
210
217
  extensionTriggers: this.args.predefinedTriggers,
211
- });
218
+ }, Object.assign(Object.assign(Object.assign(Object.assign({}, this.getSystemEnvs()), this.getEmulatorEnvs()), { FIREBASE_CONFIG: this.getFirebaseConfig() }), this.args.env));
212
219
  const triggerParseEvent = await types_1.EmulatorLog.waitForLog(worker.runtime.events, "SYSTEM", "triggers-parsed");
213
220
  const parsedDefinitions = triggerParseEvent.data
214
221
  .triggerDefinitions;
@@ -238,6 +245,7 @@ class FunctionsEmulator {
238
245
  else if (definition.eventTrigger) {
239
246
  const service = functionsEmulatorShared_1.getFunctionService(definition);
240
247
  const key = this.getTriggerKey(definition);
248
+ const signature = functionsEmulatorShared_1.getSignatureType(definition);
241
249
  switch (service) {
242
250
  case constants_1.Constants.SERVICE_FIRESTORE:
243
251
  added = await this.addFirestoreTrigger(this.args.projectId, key, definition.eventTrigger);
@@ -246,7 +254,7 @@ class FunctionsEmulator {
246
254
  added = await this.addRealtimeDatabaseTrigger(this.args.projectId, key, definition.eventTrigger);
247
255
  break;
248
256
  case constants_1.Constants.SERVICE_PUBSUB:
249
- added = await this.addPubsubTrigger(definition.name, key, definition.eventTrigger, definition.schedule);
257
+ added = await this.addPubsubTrigger(definition.name, key, definition.eventTrigger, signature, definition.schedule);
250
258
  break;
251
259
  case constants_1.Constants.SERVICE_AUTH:
252
260
  added = this.addAuthTrigger(this.args.projectId, key, definition.eventTrigger);
@@ -342,7 +350,7 @@ class FunctionsEmulator {
342
350
  throw err;
343
351
  });
344
352
  }
345
- async addPubsubTrigger(triggerName, key, eventTrigger, schedule) {
353
+ async addPubsubTrigger(triggerName, key, eventTrigger, signatureType, schedule) {
346
354
  const pubsubPort = registry_1.EmulatorRegistry.getPort(types_1.Emulators.PUBSUB);
347
355
  if (!pubsubPort) {
348
356
  return false;
@@ -359,7 +367,7 @@ class FunctionsEmulator {
359
367
  topic = resourceParts[resourceParts.length - 1];
360
368
  }
361
369
  try {
362
- await pubsubEmulator.addTrigger(topic, key);
370
+ await pubsubEmulator.addTrigger(topic, key, signatureType);
363
371
  return true;
364
372
  }
365
373
  catch (e) {
@@ -424,7 +432,6 @@ class FunctionsEmulator {
424
432
  projectId: this.args.projectId,
425
433
  triggerId: "",
426
434
  targetName: "",
427
- triggerType: undefined,
428
435
  emulators: {
429
436
  firestore: registry_1.EmulatorRegistry.getInfo(types_1.Emulators.FIRESTORE),
430
437
  database: registry_1.EmulatorRegistry.getInfo(types_1.Emulators.DATABASE),
@@ -473,7 +480,85 @@ class FunctionsEmulator {
473
480
  this.logger.log("WARN", `Your requested "node" version "${requestedMajorVersion}" doesn't match your global version "${hostMajorVersion}"`);
474
481
  return process.execPath;
475
482
  }
476
- invokeRuntime(frb, opts) {
483
+ getUserEnvs() {
484
+ const projectInfo = {
485
+ functionsSource: this.args.functionsDir,
486
+ projectId: this.args.projectId,
487
+ isEmulator: true,
488
+ };
489
+ if (functionsEnv.hasUserEnvs(projectInfo)) {
490
+ try {
491
+ return functionsEnv.loadUserEnvs(projectInfo);
492
+ }
493
+ catch (e) {
494
+ logger_1.logger.debug("Failed to load local environment variables", e);
495
+ }
496
+ }
497
+ return {};
498
+ }
499
+ getSystemEnvs(triggerDef) {
500
+ const envs = {};
501
+ envs.GCLOUD_PROJECT = this.args.projectId;
502
+ envs.K_REVISION = "1";
503
+ envs.PORT = "80";
504
+ if (triggerDef) {
505
+ const service = triggerDef.targetName;
506
+ const target = service.replace(/-/g, ".");
507
+ envs.FUNCTION_TARGET = target;
508
+ envs.FUNCTION_SIGNATURE_TYPE = triggerDef.signatureType;
509
+ envs.K_SERVICE = service;
510
+ }
511
+ return envs;
512
+ }
513
+ getEmulatorEnvs() {
514
+ const envs = {};
515
+ envs.FUNCTIONS_EMULATOR = "true";
516
+ envs.TZ = "UTC";
517
+ const firestoreEmulator = this.getEmulatorInfo(types_1.Emulators.FIRESTORE);
518
+ if (firestoreEmulator != null) {
519
+ envs[constants_1.Constants.FIRESTORE_EMULATOR_HOST] = functionsEmulatorShared_1.formatHost(firestoreEmulator);
520
+ }
521
+ const databaseEmulator = this.getEmulatorInfo(types_1.Emulators.DATABASE);
522
+ if (databaseEmulator) {
523
+ envs[constants_1.Constants.FIREBASE_DATABASE_EMULATOR_HOST] = functionsEmulatorShared_1.formatHost(databaseEmulator);
524
+ }
525
+ const authEmulator = this.getEmulatorInfo(types_1.Emulators.AUTH);
526
+ if (authEmulator) {
527
+ envs[constants_1.Constants.FIREBASE_AUTH_EMULATOR_HOST] = functionsEmulatorShared_1.formatHost(authEmulator);
528
+ }
529
+ const storageEmulator = this.getEmulatorInfo(types_1.Emulators.STORAGE);
530
+ if (storageEmulator) {
531
+ envs[constants_1.Constants.FIREBASE_STORAGE_EMULATOR_HOST] = functionsEmulatorShared_1.formatHost(storageEmulator);
532
+ envs[constants_1.Constants.CLOUD_STORAGE_EMULATOR_HOST] = `http://${functionsEmulatorShared_1.formatHost(storageEmulator)}`;
533
+ }
534
+ const pubsubEmulator = this.getEmulatorInfo(types_1.Emulators.PUBSUB);
535
+ if (pubsubEmulator) {
536
+ const pubsubHost = functionsEmulatorShared_1.formatHost(pubsubEmulator);
537
+ process.env.PUBSUB_EMULATOR_HOST = pubsubHost;
538
+ }
539
+ return envs;
540
+ }
541
+ getFirebaseConfig() {
542
+ const databaseEmulator = this.getEmulatorInfo(types_1.Emulators.DATABASE);
543
+ let emulatedDatabaseURL = undefined;
544
+ if (databaseEmulator) {
545
+ let ns = this.args.projectId;
546
+ if (this.adminSdkConfig.databaseURL) {
547
+ const asUrl = new url_1.URL(this.adminSdkConfig.databaseURL);
548
+ ns = asUrl.hostname.split(".")[0];
549
+ }
550
+ emulatedDatabaseURL = `http://${functionsEmulatorShared_1.formatHost(databaseEmulator)}/?ns=${ns}`;
551
+ }
552
+ return JSON.stringify({
553
+ storageBucket: this.adminSdkConfig.storageBucket,
554
+ databaseURL: emulatedDatabaseURL || this.adminSdkConfig.databaseURL,
555
+ projectId: this.args.projectId,
556
+ });
557
+ }
558
+ getRuntimeEnvs(triggerDef) {
559
+ return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, this.getUserEnvs()), this.getSystemEnvs(triggerDef)), this.getEmulatorEnvs()), { FIREBASE_CONFIG: this.getFirebaseConfig() }), this.args.env);
560
+ }
561
+ invokeRuntime(frb, opts, runtimeEnv) {
477
562
  if (this.workerPool.readyForWork(frb.triggerId)) {
478
563
  return this.workerPool.submitWork(frb.triggerId, frb, opts);
479
564
  }
@@ -499,7 +584,7 @@ class FunctionsEmulator {
499
584
  "See https://yarnpkg.com/getting-started/migration#step-by-step for more information.");
500
585
  }
501
586
  const childProcess = spawn(opts.nodeBinary, args, {
502
- env: Object.assign(Object.assign({ node: opts.nodeBinary }, opts.env), process.env),
587
+ env: Object.assign(Object.assign({ node: opts.nodeBinary }, process.env), (runtimeEnv !== null && runtimeEnv !== void 0 ? runtimeEnv : {})),
503
588
  cwd: frb.cwd,
504
589
  stdio: ["pipe", "pipe", "pipe", "ipc"],
505
590
  });
@@ -559,7 +644,7 @@ class FunctionsEmulator {
559
644
  }
560
645
  const trigger = this.getTriggerDefinitionByKey(triggerKey);
561
646
  const service = functionsEmulatorShared_1.getFunctionService(trigger);
562
- const worker = this.startFunctionRuntime(trigger.id, trigger.name, functionsEmulatorShared_1.EmulatedTriggerType.BACKGROUND, proto);
647
+ const worker = this.startFunctionRuntime(trigger.id, trigger.name, functionsEmulatorShared_1.getSignatureType(trigger), proto);
563
648
  return new Promise((resolve, reject) => {
564
649
  if (projectId !== this.args.projectId) {
565
650
  if (service !== constants_1.Constants.SERVICE_REALTIME_DATABASE) {
@@ -645,7 +730,7 @@ class FunctionsEmulator {
645
730
  req.headers[functionsEmulatorShared_1.HttpConstants.CALLABLE_AUTH_HEADER] = encodeURIComponent(JSON.stringify(contextAuth));
646
731
  }
647
732
  }
648
- const worker = this.startFunctionRuntime(trigger.id, trigger.name, functionsEmulatorShared_1.EmulatedTriggerType.HTTPS, undefined);
733
+ const worker = this.startFunctionRuntime(trigger.id, trigger.name, "http", undefined);
649
734
  worker.onLogs((el) => {
650
735
  if (el.level === "FATAL") {
651
736
  res.status(500).send(el.text);
@@ -660,7 +745,7 @@ class FunctionsEmulator {
660
745
  if (!worker.lastArgs.frb.socketPath) {
661
746
  throw new error_1.FirebaseError(`Cannot execute on a worker without a socketPath: ${JSON.stringify(worker.lastArgs)}`);
662
747
  }
663
- const url = new URL(`${req.protocol}://${req.hostname}${req.url}`);
748
+ const url = new url_1.URL(`${req.protocol}://${req.hostname}${req.url}`);
664
749
  const path = `${url.pathname}${url.search}`.replace(new RegExp(`\/${this.args.projectId}\/[^\/]*\/${triggerName}\/?`), "/");
665
750
  this.logger.log("DEBUG", `[functions] Got req.url=${req.url}, mapping to path=${path}`);
666
751
  const runtimeReq = http.request({