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.
- package/CHANGELOG.md +1 -3
- package/lib/api.js +1 -0
- package/lib/apiv2.js +1 -1
- package/lib/appdistribution/client.js +84 -72
- package/lib/appdistribution/distribution.js +8 -26
- package/lib/appdistribution/options-parser-util.js +51 -0
- package/lib/command.js +8 -6
- package/lib/commands/appdistribution-distribute.js +74 -91
- package/lib/commands/appdistribution-testers-add.js +18 -0
- package/lib/commands/appdistribution-testers-remove.js +32 -0
- package/lib/commands/crashlytics-symbols-upload.js +146 -0
- package/lib/commands/ext-configure.js +9 -1
- package/lib/commands/ext-dev-extension-delete.js +2 -1
- package/lib/commands/ext-dev-init.js +18 -9
- package/lib/commands/ext-dev-publish.js +11 -4
- package/lib/commands/ext-dev-unpublish.js +2 -1
- package/lib/commands/ext-install.js +115 -48
- package/lib/commands/ext-uninstall.js +6 -0
- package/lib/commands/ext-update.js +67 -43
- package/lib/commands/functions-config-export.js +115 -0
- package/lib/commands/functions-delete.js +44 -35
- package/lib/commands/functions-list.js +54 -0
- package/lib/commands/functions-log.js +5 -22
- package/lib/commands/hosting-channel-deploy.js +6 -4
- package/lib/commands/index.js +12 -0
- package/lib/deploy/functions/backend.js +47 -12
- package/lib/deploy/functions/containerCleaner.js +5 -1
- package/lib/deploy/functions/deploy.js +7 -5
- package/lib/deploy/functions/prepare.js +9 -7
- package/lib/deploy/functions/prompts.js +3 -21
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +2 -1
- package/lib/deploy/functions/runtimes/index.js +2 -1
- package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +4 -3
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +14 -9
- package/lib/deploy/functions/triggerRegionHelper.js +32 -0
- package/lib/downloadUtils.js +37 -0
- package/lib/emulator/auth/apiSpec.js +1758 -404
- package/lib/emulator/auth/handlers.js +6 -5
- package/lib/emulator/auth/operations.js +429 -40
- package/lib/emulator/auth/server.js +18 -11
- package/lib/emulator/auth/state.js +186 -5
- package/lib/emulator/auth/widget_ui.js +2 -2
- package/lib/emulator/download.js +2 -31
- package/lib/emulator/downloadableEmulators.js +7 -7
- package/lib/emulator/emulatorLogger.js +0 -3
- package/lib/emulator/events/types.js +16 -0
- package/lib/emulator/functionsEmulator.js +102 -17
- package/lib/emulator/functionsEmulatorRuntime.js +46 -121
- package/lib/emulator/functionsEmulatorShared.js +51 -7
- package/lib/emulator/functionsEmulatorShell.js +1 -1
- package/lib/emulator/pubsubEmulator.js +61 -40
- package/lib/extensions/askUserForConsent.js +16 -13
- package/lib/extensions/askUserForParam.js +72 -3
- package/lib/extensions/billingMigrationHelper.js +1 -11
- package/lib/extensions/changelog.js +93 -0
- package/lib/extensions/displayExtensionInfo.js +38 -38
- package/lib/extensions/emulator/optionsHelper.js +3 -3
- package/lib/extensions/emulator/triggerHelper.js +2 -32
- package/lib/extensions/extensionsApi.js +69 -95
- package/lib/extensions/extensionsHelper.js +75 -50
- package/lib/extensions/paramHelper.js +79 -36
- package/lib/extensions/refs.js +59 -0
- package/lib/extensions/resolveSource.js +2 -20
- package/lib/extensions/secretsUtils.js +58 -0
- package/lib/extensions/updateHelper.js +39 -105
- package/lib/extensions/warnings.js +1 -7
- package/lib/functional.js +64 -0
- package/lib/functions/env.js +26 -13
- package/lib/functions/functionslog.js +40 -0
- package/lib/functions/listFunctions.js +10 -0
- package/lib/functions/runtimeConfigExport.js +137 -0
- package/lib/gcp/cloudfunctions.js +84 -9
- package/lib/gcp/cloudfunctionsv2.js +99 -7
- package/lib/gcp/cloudlogging.js +27 -21
- package/lib/gcp/secretManager.js +111 -0
- package/lib/gcp/storage.js +16 -0
- package/lib/previews.js +1 -1
- package/lib/requireInteractive.js +12 -0
- package/package.json +5 -4
- package/schema/firebase-config.json +2 -1
- package/templates/extensions/CHANGELOG.md +7 -0
- 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
|
|
239
|
-
if (!
|
|
240
|
-
|
|
241
|
-
projectStateForId.set(projectId,
|
|
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
|
|
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
|
|
342
|
-
|
|
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
|
-
|
|
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, {
|
|
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@
|
|
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@
|
|
604
|
+
<script src="https://unpkg.com/material-components-web@10/dist/material-components-web.min.js"></script>
|
|
605
605
|
<script>${SCRIPT}</script>
|
|
606
606
|
`;
|
package/lib/emulator/download.js
CHANGED
|
@@ -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.
|
|
54
|
-
downloadPath: path.join(CACHE_DIR, "ui-v1.6.
|
|
55
|
-
unzipDir: path.join(CACHE_DIR, "ui-v1.6.
|
|
56
|
-
binaryPath: path.join(CACHE_DIR, "ui-v1.6.
|
|
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.
|
|
60
|
-
expectedSize:
|
|
61
|
-
expectedChecksum: "
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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 },
|
|
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.
|
|
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,
|
|
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({
|