firebase-tools 11.8.1 → 11.10.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 (37) hide show
  1. package/lib/bin/firebase.js +5 -5
  2. package/lib/commands/ext-install.js +2 -2
  3. package/lib/crashlytics/buildToolsJarHelper.js +1 -1
  4. package/lib/deploy/functions/build.js +3 -4
  5. package/lib/deploy/functions/cache/applyHash.js +2 -9
  6. package/lib/deploy/functions/cel.js +86 -47
  7. package/lib/deploy/functions/deploy.js +21 -2
  8. package/lib/deploy/functions/params.js +35 -8
  9. package/lib/deploy/functions/prepare.js +13 -2
  10. package/lib/deploy/functions/prepareFunctionsUpload.js +2 -2
  11. package/lib/deploy/functions/release/planner.js +1 -0
  12. package/lib/deploy/functions/services/index.js +11 -0
  13. package/lib/deploy/functions/services/remoteConfig.js +14 -0
  14. package/lib/emulator/auth/server.js +6 -0
  15. package/lib/emulator/auth/state.js +3 -1
  16. package/lib/emulator/downloadableEmulators.js +10 -10
  17. package/lib/emulator/functionsEmulator.js +71 -171
  18. package/lib/emulator/functionsEmulatorRuntime.js +104 -142
  19. package/lib/emulator/functionsEmulatorShared.js +3 -0
  20. package/lib/emulator/functionsEmulatorShell.js +1 -1
  21. package/lib/emulator/functionsRuntimeWorker.js +55 -36
  22. package/lib/emulator/storage/files.js +5 -5
  23. package/lib/emulator/storage/rules/runtime.js +41 -6
  24. package/lib/emulator/storage/rules/types.js +7 -1
  25. package/lib/emulator/storage/rules/utils.js +3 -1
  26. package/lib/emulator/storage/server.js +6 -0
  27. package/lib/ensureApiEnabled.js +2 -0
  28. package/lib/extensions/askUserForConsent.js +1 -38
  29. package/lib/extensions/displayExtensionInfo.js +27 -2
  30. package/lib/extensions/updateHelper.js +3 -35
  31. package/lib/functions/events/v2.js +2 -1
  32. package/lib/gcp/cloudfunctionsv2.js +5 -2
  33. package/lib/previews.js +1 -1
  34. package/lib/rulesDeploy.js +1 -2
  35. package/lib/serve/hosting.js +1 -1
  36. package/npm-shrinkwrap.json +69 -9
  37. package/package.json +3 -2
@@ -10,15 +10,13 @@ const types_1 = require("./types");
10
10
  const constants_1 = require("./constants");
11
11
  const functionsEmulatorShared_1 = require("./functionsEmulatorShared");
12
12
  const functionsEmulatorUtils_1 = require("./functionsEmulatorUtils");
13
+ const types_2 = require("./events/types");
13
14
  let functionModule;
14
15
  let FUNCTION_TARGET_NAME;
15
16
  let FUNCTION_SIGNATURE;
16
17
  let FUNCTION_DEBUG_MODE;
17
18
  let developerPkgJSON;
18
19
  const dynamicImport = new Function("modulePath", "return import(modulePath)");
19
- function isFeatureEnabled(frb, feature) {
20
- return frb.disabled_features ? !frb.disabled_features[feature] : true;
21
- }
22
20
  function noOp() {
23
21
  return false;
24
22
  }
@@ -485,73 +483,14 @@ async function initializeFunctionsConfigHelper() {
485
483
  function rawBodySaver(req, res, buf) {
486
484
  req.rawBody = buf;
487
485
  }
488
- async function processHTTPS(trigger) {
489
- const ephemeralServer = express();
490
- await new Promise((resolveEphemeralServer, rejectEphemeralServer) => {
491
- ephemeralServer.enable("trust proxy");
492
- ephemeralServer.use(bodyParser.json({
493
- limit: "10mb",
494
- verify: rawBodySaver,
495
- }));
496
- ephemeralServer.use(bodyParser.text({
497
- limit: "10mb",
498
- verify: rawBodySaver,
499
- }));
500
- ephemeralServer.use(bodyParser.urlencoded({
501
- extended: true,
502
- limit: "10mb",
503
- verify: rawBodySaver,
504
- }));
505
- ephemeralServer.use(bodyParser.raw({
506
- type: "*/*",
507
- limit: "10mb",
508
- verify: rawBodySaver,
509
- }));
510
- let server;
511
- function closeServer() {
512
- if (server) {
513
- server.close((err) => {
514
- if (err) {
515
- rejectEphemeralServer(err);
516
- }
517
- else {
518
- resolveEphemeralServer();
519
- }
520
- });
521
- }
522
- }
523
- ephemeralServer.get("/__/health", (req, res) => {
524
- res.status(200).send();
525
- });
526
- ephemeralServer.all("/favicon.ico|/robots.txt", (req, res) => {
527
- res.on("finish", closeServer);
528
- res.status(404).send();
529
- });
530
- ephemeralServer.all(`/*`, async (req, res) => {
531
- try {
532
- logDebug(`Ephemeral server handling ${req.method} request`);
533
- res.on("finish", closeServer);
534
- await runHTTPS(trigger, [req, res]);
535
- }
536
- catch (err) {
537
- rejectEphemeralServer(err);
538
- }
539
- });
540
- server = ephemeralServer.listen(process.env.PORT);
541
- logDebug(`Listening to port: ${process.env.PORT}`);
542
- server.on("error", rejectEphemeralServer);
543
- });
544
- }
545
- async function processBackground(trigger, frb, signature) {
546
- const proto = frb.proto;
547
- logDebug("ProcessBackground", proto);
486
+ async function processBackground(trigger, reqBody, signature) {
548
487
  if (signature === "cloudevent") {
549
- return runCloudEvent(trigger, proto);
488
+ return runCloudEvent(trigger, reqBody);
550
489
  }
551
- const data = proto.data;
552
- delete proto.data;
553
- const context = proto.context ? proto.context : proto;
554
- if (!proto.eventType || !proto.eventType.startsWith("google.storage")) {
490
+ const data = reqBody.data;
491
+ delete reqBody.data;
492
+ const context = reqBody.context ? reqBody.context : reqBody;
493
+ if (!reqBody.eventType || !reqBody.eventType.startsWith("google.storage")) {
555
494
  if (context.resource && context.resource.name) {
556
495
  logDebug("ProcessBackground: lifting resource.name from resource", context.resource);
557
496
  context.resource = context.resource.name;
@@ -567,15 +506,14 @@ async function runFunction(func) {
567
506
  catch (err) {
568
507
  caughtErr = err;
569
508
  }
570
- logDebug(`Ephemeral server survived.`);
571
509
  if (caughtErr) {
572
510
  throw caughtErr;
573
511
  }
574
512
  }
575
- async function runBackground(trigger, proto) {
576
- logDebug("RunBackground", proto);
513
+ async function runBackground(trigger, reqBody) {
514
+ logDebug("RunBackground", reqBody);
577
515
  await runFunction(() => {
578
- return trigger(proto.data, proto.context);
516
+ return trigger(reqBody.data, reqBody.context);
579
517
  });
580
518
  }
581
519
  async function runCloudEvent(trigger, event) {
@@ -613,43 +551,6 @@ async function moduleResolutionDetective(error) {
613
551
  function logDebug(msg, data) {
614
552
  new types_1.EmulatorLog("DEBUG", "runtime-status", `[${process.pid}] ${msg}`, data).log();
615
553
  }
616
- async function invokeTrigger(trigger, frb) {
617
- new types_1.EmulatorLog("INFO", "runtime-status", `Beginning execution of "${FUNCTION_TARGET_NAME}"`, {
618
- frb,
619
- }).log();
620
- logDebug(`Running ${FUNCTION_TARGET_NAME} in signature ${FUNCTION_SIGNATURE}`);
621
- let seconds = 0;
622
- const timerId = setInterval(() => {
623
- seconds++;
624
- }, 1000);
625
- let timeoutId;
626
- if (isFeatureEnabled(frb, "timeout")) {
627
- let timeout = process.env.FUNCTIONS_EMULATOR_TIMEOUT_SECONDS || "60";
628
- if (timeout.endsWith("s")) {
629
- timeout = timeout.slice(0, -1);
630
- }
631
- const timeoutMs = parseInt(timeout, 10) * 1000;
632
- timeoutId = setTimeout(() => {
633
- new types_1.EmulatorLog("WARN", "runtime-status", `Your function timed out after ~${timeout}s. To configure this timeout, see
634
- https://firebase.google.com/docs/functions/manage-functions#set_timeout_and_memory_allocation.`).log();
635
- throw new Error("Function timed out.");
636
- }, timeoutMs);
637
- }
638
- switch (FUNCTION_SIGNATURE) {
639
- case "event":
640
- case "cloudevent":
641
- await processBackground(trigger, frb, FUNCTION_SIGNATURE);
642
- break;
643
- case "http":
644
- await processHTTPS(trigger);
645
- break;
646
- }
647
- if (timeoutId) {
648
- clearTimeout(timeoutId);
649
- }
650
- clearInterval(timerId);
651
- new types_1.EmulatorLog("INFO", "runtime-status", `Finished "${FUNCTION_TARGET_NAME}" in ~${Math.max(seconds, 1)}s`).log();
652
- }
653
554
  async function initializeRuntime() {
654
555
  FUNCTION_DEBUG_MODE = process.env.FUNCTION_DEBUG_MODE || "";
655
556
  if (!FUNCTION_DEBUG_MODE) {
@@ -695,50 +596,25 @@ async function flushAndExit(code) {
695
596
  await types_1.EmulatorLog.waitForFlush();
696
597
  process.exit(code);
697
598
  }
698
- async function goIdle() {
699
- new types_1.EmulatorLog("SYSTEM", "runtime-status", "Runtime is now idle", { state: "idle" }).log();
700
- await types_1.EmulatorLog.waitForFlush();
701
- }
702
599
  async function handleMessage(message) {
703
- let runtimeArgs;
600
+ let debug;
704
601
  try {
705
- runtimeArgs = JSON.parse(message);
602
+ debug = JSON.parse(message);
706
603
  }
707
604
  catch (e) {
708
605
  new types_1.EmulatorLog("FATAL", "runtime-error", `Got unexpected message body: ${message}`).log();
709
606
  await flushAndExit(1);
710
607
  return;
711
608
  }
712
- if (!functionModule) {
713
- try {
714
- functionModule = await loadTriggers();
609
+ if (FUNCTION_DEBUG_MODE) {
610
+ if (debug) {
611
+ FUNCTION_TARGET_NAME = debug.functionTarget;
612
+ FUNCTION_SIGNATURE = debug.functionSignature;
715
613
  }
716
- catch (e) {
717
- logDebug(e);
718
- new types_1.EmulatorLog("FATAL", "runtime-status", `Failed to initialize and load triggers. This shouldn't happen: ${e.message}`).log();
719
- await flushAndExit(1);
720
- return;
614
+ else {
615
+ new types_1.EmulatorLog("WARN", "runtime-warning", "Expected debug payload while in debug mode.");
721
616
  }
722
617
  }
723
- if (FUNCTION_DEBUG_MODE) {
724
- FUNCTION_TARGET_NAME = runtimeArgs.frb.debug.functionTarget;
725
- FUNCTION_SIGNATURE = runtimeArgs.frb.debug.functionSignature;
726
- }
727
- const trigger = FUNCTION_TARGET_NAME.split(".").reduce((mod, functionTargetPart) => {
728
- return mod === null || mod === void 0 ? void 0 : mod[functionTargetPart];
729
- }, functionModule);
730
- if (!trigger) {
731
- throw new Error(`Failed to find function ${FUNCTION_TARGET_NAME} in the loaded module`);
732
- }
733
- logDebug(`Beginning invocation function ${FUNCTION_TARGET_NAME}!`);
734
- try {
735
- await invokeTrigger(trigger, runtimeArgs.frb);
736
- await goIdle();
737
- }
738
- catch (err) {
739
- new types_1.EmulatorLog("FATAL", "runtime-error", err.stack ? err.stack : err).log();
740
- await flushAndExit(1);
741
- }
742
618
  }
743
619
  async function main() {
744
620
  let lastSignal = new Date().getTime();
@@ -755,6 +631,92 @@ async function main() {
755
631
  }
756
632
  });
757
633
  await initializeRuntime();
634
+ try {
635
+ functionModule = await loadTriggers();
636
+ }
637
+ catch (e) {
638
+ new types_1.EmulatorLog("FATAL", "runtime-status", `Failed to initialize and load triggers. This shouldn't happen: ${e.message}`).log();
639
+ await flushAndExit(1);
640
+ }
641
+ const app = express();
642
+ app.enable("trust proxy");
643
+ app.use(bodyParser.json({
644
+ limit: "10mb",
645
+ verify: rawBodySaver,
646
+ }));
647
+ app.use(bodyParser.text({
648
+ limit: "10mb",
649
+ verify: rawBodySaver,
650
+ }));
651
+ app.use(bodyParser.urlencoded({
652
+ extended: true,
653
+ limit: "10mb",
654
+ verify: rawBodySaver,
655
+ }));
656
+ app.use(bodyParser.raw({
657
+ type: "*/*",
658
+ limit: "10mb",
659
+ verify: rawBodySaver,
660
+ }));
661
+ app.get("/__/health", (req, res) => {
662
+ res.status(200).send();
663
+ });
664
+ app.all("/favicon.ico|/robots.txt", (req, res) => {
665
+ res.status(404).send();
666
+ });
667
+ app.all(`/*`, async (req, res) => {
668
+ var _a;
669
+ try {
670
+ new types_1.EmulatorLog("INFO", "runtime-status", `Beginning execution of "${FUNCTION_TARGET_NAME}"`).log();
671
+ const trigger = FUNCTION_TARGET_NAME.split(".").reduce((mod, functionTargetPart) => {
672
+ return mod === null || mod === void 0 ? void 0 : mod[functionTargetPart];
673
+ }, functionModule);
674
+ if (!trigger) {
675
+ throw new Error(`Failed to find function ${FUNCTION_TARGET_NAME} in the loaded module`);
676
+ }
677
+ const startHrTime = process.hrtime();
678
+ res.on("finish", () => {
679
+ const elapsedHrTime = process.hrtime(startHrTime);
680
+ new types_1.EmulatorLog("INFO", "runtime-status", `Finished "${FUNCTION_TARGET_NAME}" in ${elapsedHrTime[0] * 1000 + elapsedHrTime[1] / 1000000}ms`).log();
681
+ });
682
+ switch (FUNCTION_SIGNATURE) {
683
+ case "event":
684
+ case "cloudevent":
685
+ const rawBody = req.rawBody;
686
+ let reqBody = JSON.parse(rawBody.toString());
687
+ if ((_a = req.headers["content-type"]) === null || _a === void 0 ? void 0 : _a.includes("cloudevent")) {
688
+ if (types_2.EventUtils.isBinaryCloudEvent(req)) {
689
+ reqBody = types_2.EventUtils.extractBinaryCloudEventContext(req);
690
+ reqBody.data = req.body;
691
+ }
692
+ }
693
+ await processBackground(trigger, reqBody, FUNCTION_SIGNATURE);
694
+ res.send({ status: "acknowledged" });
695
+ break;
696
+ case "http":
697
+ await runHTTPS(trigger, [req, res]);
698
+ }
699
+ }
700
+ catch (err) {
701
+ new types_1.EmulatorLog("FATAL", "runtime-error", err.stack ? err.stack : err).log();
702
+ res.status(500).send(err.message);
703
+ }
704
+ });
705
+ const server = app.listen(process.env.PORT, () => {
706
+ logDebug(`Listening to port: ${process.env.PORT}`);
707
+ });
708
+ if (!FUNCTION_DEBUG_MODE) {
709
+ let timeout = process.env.FUNCTIONS_EMULATOR_TIMEOUT_SECONDS || "60";
710
+ if (timeout.endsWith("s")) {
711
+ timeout = timeout.slice(0, -1);
712
+ }
713
+ const timeoutMs = parseInt(timeout, 10) * 1000;
714
+ server.setTimeout(timeoutMs, () => {
715
+ new types_1.EmulatorLog("FATAL", "runtime-error", `Your function timed out after ~${timeout}s. To configure this timeout, see
716
+ https://firebase.google.com/docs/functions/manage-functions#set_timeout_and_memory_allocation.`).log();
717
+ return flushAndExit(1);
718
+ });
719
+ }
758
720
  let messageHandlePromise = Promise.resolve();
759
721
  process.on("message", (message) => {
760
722
  messageHandlePromise = messageHandlePromise
@@ -161,6 +161,9 @@ function getFunctionService(def) {
161
161
  if (def.blockingTrigger) {
162
162
  return def.blockingTrigger.eventType;
163
163
  }
164
+ if (def.httpsTrigger) {
165
+ return "https";
166
+ }
164
167
  return "unknown";
165
168
  }
166
169
  exports.getFunctionService = getFunctionService;
@@ -41,7 +41,7 @@ class FunctionsEmulatorShell {
41
41
  auth: opts.auth,
42
42
  data,
43
43
  };
44
- this.emu.invokeTrigger(trigger, proto);
44
+ this.emu.sendRequest(trigger, proto);
45
45
  }
46
46
  getTrigger(name) {
47
47
  const result = this.triggers.find((trigger) => {
@@ -22,19 +22,6 @@ class RuntimeWorker {
22
22
  this.id = uuid.v4();
23
23
  this.key = key;
24
24
  this.runtime = runtime;
25
- this.runtime.events.on("log", (log) => {
26
- if (log.type === "runtime-status") {
27
- if (log.data.state === "idle") {
28
- if (this.state === RuntimeWorkerState.BUSY) {
29
- this.state = RuntimeWorkerState.IDLE;
30
- }
31
- else if (this.state === RuntimeWorkerState.FINISHING) {
32
- this.log(`IDLE --> FINISHING`);
33
- this.runtime.process.kill();
34
- }
35
- }
36
- }
37
- });
38
25
  const childProc = this.runtime.process;
39
26
  let msgBuffer = "";
40
27
  childProc.on("message", (msg) => {
@@ -71,11 +58,55 @@ class RuntimeWorker {
71
58
  }
72
59
  return lines[lines.length - 1];
73
60
  }
74
- execute(frb, opts) {
75
- const execFrb = Object.assign({}, frb);
76
- const args = { frb: execFrb, opts };
61
+ sendDebugMsg(debug) {
62
+ return new Promise((resolve, reject) => {
63
+ this.runtime.process.send(JSON.stringify(debug), (err) => {
64
+ if (err) {
65
+ reject(err);
66
+ }
67
+ else {
68
+ resolve();
69
+ }
70
+ });
71
+ });
72
+ }
73
+ request(req, resp, body) {
77
74
  this.state = RuntimeWorkerState.BUSY;
78
- this.runtime.process.send(JSON.stringify(args));
75
+ const onFinish = () => {
76
+ if (this.state === RuntimeWorkerState.BUSY) {
77
+ this.state = RuntimeWorkerState.IDLE;
78
+ }
79
+ else if (this.state === RuntimeWorkerState.FINISHING) {
80
+ this.log(`IDLE --> FINISHING`);
81
+ this.runtime.process.kill();
82
+ }
83
+ };
84
+ return new Promise((resolve) => {
85
+ const proxy = http.request({
86
+ method: req.method,
87
+ path: req.path,
88
+ headers: req.headers,
89
+ socketPath: this.runtime.socketPath,
90
+ }, (_resp) => {
91
+ resp.writeHead(_resp.statusCode || 200, _resp.headers);
92
+ const piped = _resp.pipe(resp);
93
+ piped.on("finish", () => {
94
+ onFinish();
95
+ resolve();
96
+ });
97
+ });
98
+ proxy.on("error", (err) => {
99
+ resp.writeHead(500);
100
+ resp.write(JSON.stringify(err));
101
+ resp.end();
102
+ this.runtime.process.kill();
103
+ resolve();
104
+ });
105
+ if (body) {
106
+ proxy.write(body);
107
+ }
108
+ proxy.end();
109
+ });
79
110
  }
80
111
  get state() {
81
112
  return this._state;
@@ -100,20 +131,6 @@ class RuntimeWorker {
100
131
  }
101
132
  this.runtime.events.on("log", listener);
102
133
  }
103
- waitForDone() {
104
- if (this.state === RuntimeWorkerState.IDLE || this.state === RuntimeWorkerState.FINISHED) {
105
- return Promise.resolve();
106
- }
107
- return new Promise((res) => {
108
- const listener = () => {
109
- this.stateEvents.removeListener(RuntimeWorkerState.IDLE, listener);
110
- this.stateEvents.removeListener(RuntimeWorkerState.FINISHED, listener);
111
- res();
112
- };
113
- this.stateEvents.once(RuntimeWorkerState.IDLE, listener);
114
- this.stateEvents.once(RuntimeWorkerState.FINISHED, listener);
115
- });
116
- }
117
134
  isSocketReady() {
118
135
  return new Promise((resolve, reject) => {
119
136
  const req = http
@@ -198,14 +215,16 @@ class RuntimeWorkerPool {
198
215
  const idleWorker = this.getIdleWorker(triggerId);
199
216
  return !!idleWorker;
200
217
  }
201
- submitWork(triggerId, frb, opts) {
202
- this.log(`submitWork(triggerId=${triggerId})`);
218
+ async submitRequest(triggerId, req, resp, body, debug) {
219
+ this.log(`submitRequest(triggerId=${triggerId})`);
203
220
  const worker = this.getIdleWorker(triggerId);
204
221
  if (!worker) {
205
- throw new error_1.FirebaseError("Internal Error: can't call submitWork without checking for idle workers");
222
+ throw new error_1.FirebaseError("Internal Error: can't call submitRequest without checking for idle workers");
206
223
  }
207
- worker.execute(frb, opts);
208
- return worker;
224
+ if (debug) {
225
+ await worker.sendDebugMsg(debug);
226
+ }
227
+ return worker.request(req, resp, body);
209
228
  }
210
229
  getIdleWorker(triggerId) {
211
230
  this.cleanUpWorkers();
@@ -63,7 +63,7 @@ class StorageLayer {
63
63
  const hasValidDownloadToken = ((metadata === null || metadata === void 0 ? void 0 : metadata.downloadTokens) || []).includes((_a = request.downloadToken) !== null && _a !== void 0 ? _a : "");
64
64
  let authorized = hasValidDownloadToken;
65
65
  if (!authorized) {
66
- authorized = await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.GET, { before: metadata === null || metadata === void 0 ? void 0 : metadata.asRulesResource() }, request.authorization);
66
+ authorized = await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.GET, { before: metadata === null || metadata === void 0 ? void 0 : metadata.asRulesResource() }, this._projectId, request.authorization);
67
67
  }
68
68
  if (!authorized) {
69
69
  throw new errors_1.ForbiddenError("Failed auth");
@@ -92,7 +92,7 @@ class StorageLayer {
92
92
  }
93
93
  async deleteObject(request) {
94
94
  const storedMetadata = this.getMetadata(request.bucketId, request.decodedObjectId);
95
- const authorized = await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.DELETE, { before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource() }, request.authorization);
95
+ const authorized = await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.DELETE, { before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource() }, this._projectId, request.authorization);
96
96
  if (!authorized) {
97
97
  throw new errors_1.ForbiddenError();
98
98
  }
@@ -126,7 +126,7 @@ class StorageLayer {
126
126
  const authorized = await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.UPDATE, {
127
127
  before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource(),
128
128
  after: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource(request.metadata),
129
- }, request.authorization);
129
+ }, this._projectId, request.authorization);
130
130
  if (!authorized) {
131
131
  throw new errors_1.ForbiddenError();
132
132
  }
@@ -162,7 +162,7 @@ class StorageLayer {
162
162
  const authorized = await this._rulesValidator.validate(["b", upload.bucketId, "o", upload.objectId].join("/"), upload.bucketId, types_1.RulesetOperationMethod.CREATE, {
163
163
  before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource(),
164
164
  after: metadata.asRulesResource(),
165
- }, upload.authorization);
165
+ }, this._projectId, upload.authorization);
166
166
  if (!authorized) {
167
167
  this._persistence.deleteFile(upload.path);
168
168
  throw new errors_1.ForbiddenError();
@@ -220,7 +220,7 @@ class StorageLayer {
220
220
  async listObjects(request) {
221
221
  var _a;
222
222
  const { bucketId, prefix, delimiter, pageToken, authorization } = request;
223
- const authorized = await this._rulesValidator.validate(["b", bucketId, "o", prefix.replace(TRAILING_SLASHES_PATTERN, "")].join("/"), bucketId, types_1.RulesetOperationMethod.LIST, {}, authorization, delimiter);
223
+ const authorized = await this._rulesValidator.validate(["b", bucketId, "o", prefix.replace(TRAILING_SLASHES_PATTERN, "")].join("/"), bucketId, types_1.RulesetOperationMethod.LIST, {}, this._projectId, authorization, delimiter);
224
224
  if (!authorized) {
225
225
  throw new errors_1.ForbiddenError();
226
226
  }
@@ -14,6 +14,8 @@ const constants_1 = require("../../constants");
14
14
  const download_1 = require("../../download");
15
15
  const fs = require("fs-extra");
16
16
  const downloadableEmulators_1 = require("../../downloadableEmulators");
17
+ const registry_1 = require("../../registry");
18
+ const apiv2_1 = require("../../../apiv2");
17
19
  const lock = new AsyncLock();
18
20
  const synchonizationKey = "key";
19
21
  class StorageRulesetInstance {
@@ -118,6 +120,7 @@ class StorageRulesRuntime {
118
120
  }
119
121
  });
120
122
  (_b = this._childprocess.stdout) === null || _b === void 0 ? void 0 : _b.on("data", (buf) => {
123
+ var _a;
121
124
  const serializedRuntimeActionResponse = buf.toString("utf-8").trim();
122
125
  if (serializedRuntimeActionResponse !== "") {
123
126
  let rap;
@@ -128,8 +131,13 @@ class StorageRulesRuntime {
128
131
  emulatorLogger_1.EmulatorLogger.forEmulator(types_2.Emulators.STORAGE).log("INFO", serializedRuntimeActionResponse);
129
132
  return;
130
133
  }
131
- const request = this._requests[rap.id];
132
- if (rap.status !== "ok") {
134
+ const id = (_a = rap.id) !== null && _a !== void 0 ? _a : rap.server_request_id;
135
+ if (id === undefined) {
136
+ console.log(`Received no ID from server response ${serializedRuntimeActionResponse}`);
137
+ return;
138
+ }
139
+ const request = this._requests[id];
140
+ if (rap.status !== "ok" && !("action" in rap)) {
133
141
  console.warn(`[RULES] ${rap.status}: ${rap.message}`);
134
142
  rap.errors.forEach(console.warn.bind(console));
135
143
  return;
@@ -148,12 +156,15 @@ class StorageRulesRuntime {
148
156
  var _a;
149
157
  (_a = this._childprocess) === null || _a === void 0 ? void 0 : _a.kill("SIGINT");
150
158
  }
151
- async _sendRequest(rab) {
159
+ async _sendRequest(rab, overrideId) {
152
160
  if (!this._childprocess) {
153
161
  throw new error_1.FirebaseError("Attempted to send Cloud Storage rules request before child was ready");
154
162
  }
155
- const runtimeActionRequest = Object.assign(Object.assign({}, rab), { id: this._requestCount++ });
156
- if (this._requests[runtimeActionRequest.id]) {
163
+ const runtimeActionRequest = Object.assign(Object.assign({}, rab), { id: overrideId !== null && overrideId !== void 0 ? overrideId : this._requestCount++ });
164
+ if (overrideId !== undefined) {
165
+ delete this._requests[overrideId];
166
+ }
167
+ else if (this._requests[runtimeActionRequest.id]) {
157
168
  throw new error_1.FirebaseError("Attempted to send Cloud Storage rules request with stale id");
158
169
  }
159
170
  return new Promise((resolve) => {
@@ -211,7 +222,14 @@ class StorageRulesRuntime {
211
222
  variables: runtimeVariables,
212
223
  },
213
224
  };
214
- const response = (await this._sendRequest(runtimeActionRequest));
225
+ return this._completeVerifyWithRuleset(opts.projectId, runtimeActionRequest);
226
+ }
227
+ async _completeVerifyWithRuleset(projectId, runtimeActionRequest, overrideId) {
228
+ const response = (await this._sendRequest(runtimeActionRequest, overrideId));
229
+ if ("context" in response) {
230
+ const dataResponse = await fetchFirestoreDocument(projectId, response);
231
+ return this._completeVerifyWithRuleset(projectId, dataResponse, response.server_request_id);
232
+ }
215
233
  if (!response.errors)
216
234
  response.errors = [];
217
235
  if (!response.warnings)
@@ -282,6 +300,23 @@ function toExpressionValue(obj) {
282
300
  }
283
301
  throw new error_1.FirebaseError(`Cannot convert "${obj}" of type ${typeof obj} for Firebase Storage rules runtime`);
284
302
  }
303
+ async function fetchFirestoreDocument(projectId, request) {
304
+ const url = registry_1.EmulatorRegistry.url(types_2.Emulators.FIRESTORE);
305
+ const pathname = `projects/${projectId}${request.context.path}`;
306
+ const client = new apiv2_1.Client({
307
+ urlPrefix: url.toString(),
308
+ apiVersion: "v1",
309
+ });
310
+ try {
311
+ const doc = await client.get(pathname);
312
+ const { name, fields } = doc.body;
313
+ const result = { name, fields };
314
+ return { result, status: types_1.DataLoadStatus.OK, warnings: [], errors: [] };
315
+ }
316
+ catch (e) {
317
+ return { status: types_1.DataLoadStatus.NOT_FOUND, warnings: [], errors: [] };
318
+ }
319
+ }
285
320
  function createAuthExpressionValue(opts) {
286
321
  if (!opts.token) {
287
322
  return toExpressionValue(null);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RulesetOperationMethod = void 0;
3
+ exports.DataLoadStatus = exports.RulesetOperationMethod = void 0;
4
4
  var RulesetOperationMethod;
5
5
  (function (RulesetOperationMethod) {
6
6
  RulesetOperationMethod["READ"] = "read";
@@ -11,3 +11,9 @@ var RulesetOperationMethod;
11
11
  RulesetOperationMethod["UPDATE"] = "update";
12
12
  RulesetOperationMethod["DELETE"] = "delete";
13
13
  })(RulesetOperationMethod = exports.RulesetOperationMethod || (exports.RulesetOperationMethod = {}));
14
+ var DataLoadStatus;
15
+ (function (DataLoadStatus) {
16
+ DataLoadStatus["OK"] = "ok";
17
+ DataLoadStatus["NOT_FOUND"] = "not_found";
18
+ DataLoadStatus["INVALID_STATE"] = "invalid_state";
19
+ })(DataLoadStatus = exports.DataLoadStatus || (exports.DataLoadStatus = {}));
@@ -5,12 +5,13 @@ const emulatorLogger_1 = require("../../emulatorLogger");
5
5
  const types_1 = require("../../types");
6
6
  function getFirebaseRulesValidator(rulesetProvider) {
7
7
  return {
8
- validate: async (path, bucketId, method, variableOverrides, authorization, delimiter) => {
8
+ validate: async (path, bucketId, method, variableOverrides, projectId, authorization, delimiter) => {
9
9
  return await isPermitted({
10
10
  ruleset: rulesetProvider(bucketId),
11
11
  file: variableOverrides,
12
12
  path,
13
13
  method,
14
+ projectId,
14
15
  authorization,
15
16
  delimiter,
16
17
  });
@@ -42,6 +43,7 @@ async function isPermitted(opts) {
42
43
  method: opts.method,
43
44
  path: opts.path,
44
45
  file: opts.file,
46
+ projectId: opts.projectId,
45
47
  token: opts.authorization ? opts.authorization.split(" ")[1] : undefined,
46
48
  delimiter: opts.delimiter,
47
49
  });
@@ -13,6 +13,12 @@ function createApp(defaultProjectId, emulator) {
13
13
  const { storageLayer } = emulator;
14
14
  const app = express();
15
15
  emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE).log("DEBUG", `Temp file directory for storage emulator: ${storageLayer.dirPath}`);
16
+ app.use("/", (req, res, next) => {
17
+ if (req.headers["access-control-request-private-network"]) {
18
+ res.setHeader("access-control-allow-private-network", "true");
19
+ }
20
+ next();
21
+ });
16
22
  app.use(cors({
17
23
  origin: true,
18
24
  exposedHeaders: [
@@ -17,6 +17,7 @@ const apiClient = new apiv2_1.Client({
17
17
  });
18
18
  async function check(projectId, apiName, prefix, silent = false) {
19
19
  const res = await apiClient.get(`/projects/${projectId}/services/${apiName}`, {
20
+ headers: { "x-goog-quota-user": `projects/${projectId}` },
20
21
  skipLog: { resBody: true },
21
22
  });
22
23
  const isEnabled = res.body.state === "ENABLED";
@@ -29,6 +30,7 @@ exports.check = check;
29
30
  async function enable(projectId, apiName) {
30
31
  try {
31
32
  await apiClient.post(`/projects/${projectId}/services/${apiName}:enable`, undefined, {
33
+ headers: { "x-goog-quota-user": `projects/${projectId}` },
32
34
  skipLog: { resBody: true },
33
35
  });
34
36
  }