firebase-tools 11.7.0 → 11.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/lib/auth.js +1 -1
  2. package/lib/deploy/functions/build.js +39 -25
  3. package/lib/deploy/functions/cache/applyHash.js +29 -0
  4. package/lib/deploy/functions/cache/hash.js +30 -0
  5. package/lib/deploy/functions/cel.js +249 -0
  6. package/lib/deploy/functions/functionsDeployHelper.js +12 -1
  7. package/lib/deploy/functions/params.js +259 -102
  8. package/lib/deploy/functions/prepare.js +34 -4
  9. package/lib/deploy/functions/prepareFunctionsUpload.js +13 -5
  10. package/lib/deploy/functions/release/fabricator.js +17 -1
  11. package/lib/deploy/functions/release/planner.js +17 -0
  12. package/lib/deploy/functions/runtimes/node/parseTriggers.js +9 -0
  13. package/lib/deploy/functions/services/index.js +11 -0
  14. package/lib/deploy/functions/services/remoteConfig.js +14 -0
  15. package/lib/emulator/extensionsEmulator.js +1 -0
  16. package/lib/emulator/functionsEmulator.js +18 -59
  17. package/lib/emulator/functionsRuntimeWorker.js +38 -7
  18. package/lib/emulator/storage/apis/firebase.js +139 -125
  19. package/lib/emulator/storage/apis/gcloud.js +102 -42
  20. package/lib/emulator/storage/files.js +25 -15
  21. package/lib/emulator/storage/metadata.js +86 -56
  22. package/lib/emulator/storage/rules/runtime.js +10 -2
  23. package/lib/emulator/storage/upload.js +45 -9
  24. package/lib/extensions/extensionsHelper.js +1 -1
  25. package/lib/functions/constants.js +14 -0
  26. package/lib/functions/events/v2.js +2 -1
  27. package/lib/functions/secrets.js +8 -1
  28. package/lib/gcp/cloudfunctions.js +15 -18
  29. package/lib/gcp/cloudfunctionsv2.js +15 -18
  30. package/lib/gcp/cloudscheduler.js +2 -1
  31. package/lib/gcp/secretManager.js +15 -1
  32. package/lib/gcp/storage.js +15 -1
  33. package/lib/previews.js +1 -1
  34. package/lib/track.js +1 -1
  35. package/npm-shrinkwrap.json +54 -30
  36. package/package.json +6 -5
  37. package/templates/init/storage/storage.rules +1 -1
@@ -199,6 +199,15 @@ function addResourcesToBuild(projectId, runtime, annotation, want) {
199
199
  return str;
200
200
  });
201
201
  proto.convertIfPresent(endpoint, annotation, "timeoutSeconds", "timeout", proto.secondsFromDuration);
202
+ if (annotation.secrets) {
203
+ endpoint.secretEnvironmentVariables = annotation.secrets.map((secret) => {
204
+ return {
205
+ secret,
206
+ projectId,
207
+ key: secret,
208
+ };
209
+ });
210
+ }
202
211
  want.endpoints[endpointId] = endpoint;
203
212
  }
204
213
  exports.addResourcesToBuild = addResourcesToBuild;
@@ -6,6 +6,7 @@ const auth_1 = require("./auth");
6
6
  const storage_1 = require("./storage");
7
7
  const firebaseAlerts_1 = require("./firebaseAlerts");
8
8
  const database_1 = require("./database");
9
+ const remoteConfig_1 = require("./remoteConfig");
9
10
  const noop = () => Promise.resolve();
10
11
  exports.noop = noop;
11
12
  const noopProjectBindings = () => Promise.resolve([]);
@@ -55,6 +56,15 @@ const databaseService = {
55
56
  registerTrigger: exports.noop,
56
57
  unregisterTrigger: exports.noop,
57
58
  };
59
+ const remoteConfigService = {
60
+ name: "remoteconfig",
61
+ api: "firebaseremoteconfig.googleapis.com",
62
+ requiredProjectBindings: exports.noopProjectBindings,
63
+ ensureTriggerRegion: remoteConfig_1.ensureRemoteConfigTriggerRegion,
64
+ validateTrigger: exports.noop,
65
+ registerTrigger: exports.noop,
66
+ unregisterTrigger: exports.noop,
67
+ };
58
68
  const EVENT_SERVICE_MAPPING = {
59
69
  "google.cloud.pubsub.topic.v1.messagePublished": pubSubService,
60
70
  "google.cloud.storage.object.v1.finalized": storageService,
@@ -68,6 +78,7 @@ const EVENT_SERVICE_MAPPING = {
68
78
  "google.firebase.database.ref.v1.created": databaseService,
69
79
  "google.firebase.database.ref.v1.updated": databaseService,
70
80
  "google.firebase.database.ref.v1.deleted": databaseService,
81
+ "google.firebase.remoteconfig.remoteConfig.v1.updated": remoteConfigService,
71
82
  };
72
83
  function serviceForEndpoint(endpoint) {
73
84
  if (backend.isEventTriggered(endpoint)) {
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ensureRemoteConfigTriggerRegion = void 0;
4
+ const error_1 = require("../../../error");
5
+ function ensureRemoteConfigTriggerRegion(endpoint) {
6
+ if (!endpoint.eventTrigger.region) {
7
+ endpoint.eventTrigger.region = "global";
8
+ }
9
+ if (endpoint.eventTrigger.region !== "global") {
10
+ throw new error_1.FirebaseError("A remote config trigger must specify 'global' trigger location");
11
+ }
12
+ return Promise.resolve();
13
+ }
14
+ exports.ensureRemoteConfigTriggerRegion = ensureRemoteConfigTriggerRegion;
@@ -142,6 +142,7 @@ class ExtensionsEmulator {
142
142
  const emulatableBackend = {
143
143
  functionsDir,
144
144
  env: nonSecretEnv,
145
+ codebase: "",
145
146
  secretEnv: secretEnvVariables,
146
147
  predefinedTriggers: extensionTriggers,
147
148
  nodeMajorVersion: nodeMajorVersion,
@@ -54,7 +54,11 @@ class FunctionsEmulator {
54
54
  const mode = this.args.debugPort
55
55
  ? types_1.FunctionsExecutionMode.SEQUENTIAL
56
56
  : types_1.FunctionsExecutionMode.AUTO;
57
- this.workerPool = new functionsRuntimeWorker_1.RuntimeWorkerPool(mode);
57
+ this.workerPools = {};
58
+ for (const backend of this.args.emulatableBackends) {
59
+ const pool = new functionsRuntimeWorker_1.RuntimeWorkerPool(mode);
60
+ this.workerPools[backend.codebase] = pool;
61
+ }
58
62
  this.workQueue = new workQueue_1.WorkQueue(mode);
59
63
  }
60
64
  static getHttpFunctionUrl(host, port, projectId, name, region) {
@@ -241,7 +245,9 @@ class FunctionsEmulator {
241
245
  this.logger.logLabeled("WARN", "functions", "Functions emulator work queue did not empty before stopping");
242
246
  }
243
247
  this.workQueue.stop();
244
- this.workerPool.exit();
248
+ for (const pool of Object.values(this.workerPools)) {
249
+ pool.exit();
250
+ }
245
251
  if (this.destroyServer) {
246
252
  await this.destroyServer();
247
253
  }
@@ -273,7 +279,8 @@ class FunctionsEmulator {
273
279
  projectAlias: this.args.projectAlias,
274
280
  };
275
281
  const discoveredBuild = await runtimeDelegate.discoverBuild(runtimeConfig, environment);
276
- const discoveredBackend = await (0, build_1.resolveBackend)(discoveredBuild, userEnvOpt, environment);
282
+ const resolution = await (0, build_1.resolveBackend)(discoveredBuild, userEnvOpt, environment);
283
+ const discoveredBackend = resolution.backend;
277
284
  const endpoints = backend.allEndpoints(discoveredBackend);
278
285
  (0, functionsEmulatorShared_1.prepareEndpoints)(endpoints);
279
286
  for (const e of endpoints) {
@@ -297,7 +304,7 @@ class FunctionsEmulator {
297
304
  this.logger.logLabeled("ERROR", "functions", `Failed to load function definition from source: ${e}`);
298
305
  return;
299
306
  }
300
- this.workerPool.refresh();
307
+ this.workerPools[emulatableBackend.codebase].refresh();
301
308
  this.blockingFunctionsConfig = {};
302
309
  const toSetup = triggerDefinitions.filter((definition) => {
303
310
  if (force) {
@@ -380,7 +387,7 @@ class FunctionsEmulator {
380
387
  }
381
388
  }
382
389
  if (this.args.debugPort) {
383
- this.startRuntime(emulatableBackend, { nodeBinary: emulatableBackend.nodeBinary });
390
+ await this.startRuntime(emulatableBackend, { nodeBinary: emulatableBackend.nodeBinary });
384
391
  }
385
392
  }
386
393
  addEventarcTrigger(projectId, key, eventTrigger) {
@@ -802,10 +809,11 @@ class FunctionsEmulator {
802
809
  return secretEnvs;
803
810
  }
804
811
  async invokeRuntime(backend, trigger, frb, opts) {
805
- if (!this.workerPool.readyForWork(trigger.id)) {
812
+ const pool = this.workerPools[backend.codebase];
813
+ if (!pool.readyForWork(trigger.id)) {
806
814
  await this.startRuntime(backend, opts, trigger);
807
815
  }
808
- return this.workerPool.submitWork(trigger.id, frb, opts);
816
+ return pool.submitWork(trigger.id, frb, opts);
809
817
  }
810
818
  async startRuntime(backend, opts, trigger) {
811
819
  var _a;
@@ -838,52 +846,18 @@ class FunctionsEmulator {
838
846
  env: Object.assign(Object.assign(Object.assign(Object.assign({ node: opts.nodeBinary }, process.env), runtimeEnv), secretEnvs), { PORT: socketPath }),
839
847
  stdio: ["pipe", "pipe", "pipe", "ipc"],
840
848
  });
841
- if (!childProcess.stderr) {
842
- throw new error_1.FirebaseError(`childProcess.stderr is undefined.`);
843
- }
844
- if (!childProcess.stdout) {
845
- throw new error_1.FirebaseError(`childProcess.stdout is undefined.`);
846
- }
847
- const buffers = {
848
- stderr: { pipe: childProcess.stderr, value: "" },
849
- stdout: { pipe: childProcess.stdout, value: "" },
850
- };
851
- const ipcBuffer = { value: "" };
852
- childProcess.on("message", (message) => {
853
- this.onData(childProcess, emitter, ipcBuffer, message);
854
- });
855
- for (const id in buffers) {
856
- if (buffers.hasOwnProperty(id)) {
857
- const buffer = buffers[id];
858
- buffer.pipe.on("data", (buf) => {
859
- this.onData(childProcess, emitter, buffer, buf);
860
- });
861
- }
862
- }
863
849
  const runtime = {
864
- pid: childProcess.pid,
865
- exit: new Promise((resolve) => {
866
- childProcess.on("exit", resolve);
867
- }),
850
+ process: childProcess,
868
851
  events: emitter,
869
852
  cwd: backend.functionsDir,
870
853
  socketPath,
871
- shutdown: () => {
872
- childProcess.kill();
873
- },
874
- kill: (signal) => {
875
- childProcess.kill(signal);
876
- emitter.emit("log", new types_1.EmulatorLog("SYSTEM", "runtime-status", "killed"));
877
- },
878
- send: (args) => {
879
- return childProcess.send(JSON.stringify(args));
880
- },
881
854
  };
882
855
  const extensionLogInfo = {
883
856
  instanceId: backend.extensionInstanceId,
884
857
  ref: (_a = backend.extensionVersion) === null || _a === void 0 ? void 0 : _a.ref,
885
858
  };
886
- this.workerPool.addWorker(trigger === null || trigger === void 0 ? void 0 : trigger.id, runtime, extensionLogInfo);
859
+ const pool = this.workerPools[backend.codebase];
860
+ pool.addWorker(trigger === null || trigger === void 0 ? void 0 : trigger.id, runtime, extensionLogInfo);
887
861
  return;
888
862
  }
889
863
  async disableBackgroundTriggers() {
@@ -1057,20 +1031,5 @@ class FunctionsEmulator {
1057
1031
  });
1058
1032
  await worker.waitForDone();
1059
1033
  }
1060
- onData(runtime, emitter, buffer, buf) {
1061
- buffer.value += buf.toString();
1062
- const lines = buffer.value.split("\n");
1063
- if (lines.length > 1) {
1064
- lines.slice(0, -1).forEach((line) => {
1065
- const log = types_1.EmulatorLog.fromJSON(line);
1066
- emitter.emit("log", log);
1067
- if (log.level === "FATAL") {
1068
- emitter.emit("log", new types_1.EmulatorLog("SYSTEM", "runtime-status", "killed"));
1069
- runtime.kill();
1070
- }
1071
- });
1072
- }
1073
- buffer.value = lines[lines.length - 1];
1074
- }
1075
1034
  }
1076
1035
  exports.FunctionsEmulator = FunctionsEmulator;
@@ -30,21 +30,52 @@ class RuntimeWorker {
30
30
  }
31
31
  else if (this.state === RuntimeWorkerState.FINISHING) {
32
32
  this.log(`IDLE --> FINISHING`);
33
- this.runtime.shutdown();
33
+ this.runtime.process.kill();
34
34
  }
35
35
  }
36
36
  }
37
37
  });
38
- this.runtime.exit.then(() => {
38
+ const childProc = this.runtime.process;
39
+ let msgBuffer = "";
40
+ childProc.on("message", (msg) => {
41
+ msgBuffer = this.processStream(msg, msgBuffer);
42
+ });
43
+ let stdBuffer = "";
44
+ if (childProc.stdout) {
45
+ childProc.stdout.on("data", (data) => {
46
+ stdBuffer = this.processStream(data, stdBuffer);
47
+ });
48
+ }
49
+ if (childProc.stderr) {
50
+ childProc.stderr.on("data", (data) => {
51
+ stdBuffer = this.processStream(data, stdBuffer);
52
+ });
53
+ }
54
+ childProc.on("exit", () => {
39
55
  this.log("exited");
40
56
  this.state = RuntimeWorkerState.FINISHED;
41
57
  });
42
58
  }
59
+ processStream(s, buf) {
60
+ buf += s.toString();
61
+ const lines = buf.split("\n");
62
+ if (lines.length > 1) {
63
+ lines.slice(0, -1).forEach((line) => {
64
+ const log = types_1.EmulatorLog.fromJSON(line);
65
+ this.runtime.events.emit("log", log);
66
+ if (log.level === "FATAL") {
67
+ this.runtime.events.emit("log", new types_1.EmulatorLog("SYSTEM", "runtime-status", "killed"));
68
+ this.runtime.process.kill();
69
+ }
70
+ });
71
+ }
72
+ return lines[lines.length - 1];
73
+ }
43
74
  execute(frb, opts) {
44
75
  const execFrb = Object.assign({}, frb);
45
76
  const args = { frb: execFrb, opts };
46
77
  this.state = RuntimeWorkerState.BUSY;
47
- this.runtime.send(args);
78
+ this.runtime.process.send(JSON.stringify(args));
48
79
  }
49
80
  get state() {
50
81
  return this._state;
@@ -102,7 +133,7 @@ class RuntimeWorker {
102
133
  const timeout = new Promise((resolve, reject) => {
103
134
  setTimeout(() => {
104
135
  reject(new error_1.FirebaseError("Failed to load function."));
105
- }, 7000);
136
+ }, 30000);
106
137
  });
107
138
  while (true) {
108
139
  try {
@@ -142,7 +173,7 @@ class RuntimeWorkerPool {
142
173
  if (w.state === RuntimeWorkerState.IDLE) {
143
174
  this.log(`Shutting down IDLE worker (${w.key})`);
144
175
  w.state = RuntimeWorkerState.FINISHING;
145
- w.runtime.shutdown();
176
+ w.runtime.process.kill();
146
177
  }
147
178
  else if (w.state === RuntimeWorkerState.BUSY) {
148
179
  this.log(`Marking BUSY worker to finish (${w.key})`);
@@ -155,10 +186,10 @@ class RuntimeWorkerPool {
155
186
  for (const arr of this.workers.values()) {
156
187
  arr.forEach((w) => {
157
188
  if (w.state === RuntimeWorkerState.IDLE) {
158
- w.runtime.shutdown();
189
+ w.runtime.process.kill();
159
190
  }
160
191
  else {
161
- w.runtime.kill();
192
+ w.runtime.process.kill();
162
193
  }
163
194
  });
164
195
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createFirebaseEndpoints = void 0;
4
4
  const emulatorLogger_1 = require("../../emulatorLogger");
5
5
  const types_1 = require("../../types");
6
+ const uuid = require("uuid");
6
7
  const zlib_1 = require("zlib");
7
8
  const metadata_1 = require("../metadata");
8
9
  const express_1 = require("express");
@@ -93,8 +94,8 @@ function createFirebaseEndpoints(emulator) {
93
94
  }
94
95
  throw err;
95
96
  }
96
- if (!metadata.downloadTokens.length) {
97
- metadata.addDownloadToken();
97
+ if (metadata.downloadTokens.length === 0) {
98
+ metadata.addDownloadToken(true);
98
99
  }
99
100
  if (req.query.alt === "media") {
100
101
  const isGZipped = metadata.contentEncoding === "gzip";
@@ -102,7 +103,8 @@ function createFirebaseEndpoints(emulator) {
102
103
  data = (0, zlib_1.gunzipSync)(data);
103
104
  }
104
105
  res.setHeader("Accept-Ranges", "bytes");
105
- res.setHeader("Content-Type", metadata.contentType);
106
+ res.setHeader("Content-Type", metadata.contentType || "application/octet-stream");
107
+ res.setHeader("Content-Disposition", metadata.contentDisposition || "inline");
106
108
  setObjectHeaders(res, metadata, { "Content-Encoding": isGZipped ? "identity" : undefined });
107
109
  const byteRange = req.range(data.byteLength, { combine: true });
108
110
  if (Array.isArray(byteRange) && byteRange.type === "bytes" && byteRange.length > 0) {
@@ -165,45 +167,26 @@ function createFirebaseEndpoints(emulator) {
165
167
  });
166
168
  });
167
169
  const handleUpload = async (req, res) => {
168
- var _a;
170
+ var _a, _b;
169
171
  const bucketId = req.params.bucketId;
170
172
  const objectId = req.params.objectId
171
173
  ? decodeURIComponent(req.params.objectId)
172
174
  : ((_a = req.query.name) === null || _a === void 0 ? void 0 : _a.toString()) || null;
173
- const uploadType = req.header("x-goog-upload-protocol");
174
- if (uploadType === "multipart") {
175
- if (!objectId) {
176
- res.sendStatus(400);
177
- return;
178
- }
179
- const contentTypeHeader = req.header("content-type");
180
- if (!contentTypeHeader) {
181
- return res.sendStatus(400);
182
- }
183
- let metadataRaw;
184
- let dataRaw;
185
- try {
186
- ({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await (0, request_1.reqBodyToBuffer)(req)));
175
+ const uploadType = (_b = req.header("x-goog-upload-protocol")) === null || _b === void 0 ? void 0 : _b.toString();
176
+ async function finalizeOneShotUpload(upload) {
177
+ var _a, _b, _c;
178
+ if (!((_b = (_a = upload.metadata) === null || _a === void 0 ? void 0 : _a.metadata) === null || _b === void 0 ? void 0 : _b.firebaseStorageDownloadTokens)) {
179
+ const customMetadata = Object.assign(Object.assign({}, (((_c = upload.metadata) === null || _c === void 0 ? void 0 : _c.metadata) || {})), { firebaseStorageDownloadTokens: uuid.v4() });
180
+ upload.metadata = Object.assign(Object.assign({}, (upload.metadata || {})), { metadata: customMetadata });
187
181
  }
188
- catch (err) {
189
- if (err instanceof Error) {
190
- return res.status(400).send(err.message);
191
- }
192
- throw err;
193
- }
194
- const upload = uploadService.multipartUpload({
195
- bucketId,
196
- objectId,
197
- metadataRaw,
198
- dataRaw: dataRaw,
199
- authorization: req.header("authorization"),
200
- });
201
182
  let metadata;
202
183
  try {
203
184
  metadata = await storageLayer.uploadObject(upload);
204
185
  }
205
186
  catch (err) {
206
187
  if (err instanceof errors_1.ForbiddenError) {
188
+ res.header("x-goog-upload-status", "final");
189
+ uploadService.setResponseCode(upload.id, 403);
207
190
  return res.status(403).json({
208
191
  error: {
209
192
  code: 403,
@@ -213,124 +196,153 @@ function createFirebaseEndpoints(emulator) {
213
196
  }
214
197
  throw err;
215
198
  }
216
- metadata.addDownloadToken(false);
199
+ if (!metadata.contentDisposition) {
200
+ metadata.contentDisposition = "inline";
201
+ }
217
202
  return res.status(200).json(new metadata_1.OutgoingFirebaseMetadata(metadata));
218
203
  }
219
- const uploadCommand = req.header("x-goog-upload-command");
220
- if (!uploadCommand) {
221
- res.sendStatus(400);
222
- return;
223
- }
224
- if (uploadCommand === "start") {
225
- if (!objectId) {
204
+ if (uploadType === "resumable") {
205
+ const uploadCommand = req.header("x-goog-upload-command");
206
+ if (!uploadCommand) {
226
207
  res.sendStatus(400);
227
208
  return;
228
209
  }
229
- const upload = uploadService.startResumableUpload({
230
- bucketId,
231
- objectId,
232
- metadataRaw: JSON.stringify(req.body),
233
- authorization: req.header("authorization"),
234
- });
235
- res.header("x-goog-upload-chunk-granularity", "10000");
236
- res.header("x-goog-upload-control-url", "");
237
- res.header("x-goog-upload-status", "active");
238
- res.header("x-gupload-uploadid", upload.id);
239
- const uploadUrl = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE, req);
240
- uploadUrl.pathname = `/v0/b/${bucketId}/o`;
241
- uploadUrl.searchParams.set("name", objectId);
242
- uploadUrl.searchParams.set("upload_id", upload.id);
243
- uploadUrl.searchParams.set("upload_protocol", "resumable");
244
- res.header("x-goog-upload-url", uploadUrl.toString());
245
- return res.sendStatus(200);
246
- }
247
- if (!req.query.upload_id) {
248
- return res.sendStatus(400);
249
- }
250
- const uploadId = req.query.upload_id.toString();
251
- if (uploadCommand === "query") {
252
- let upload;
253
- try {
254
- upload = uploadService.getResumableUpload(uploadId);
255
- }
256
- catch (err) {
257
- if (err instanceof errors_1.NotFoundError) {
258
- return res.sendStatus(404);
210
+ if (uploadCommand === "start") {
211
+ if (!objectId) {
212
+ res.sendStatus(400);
213
+ return;
259
214
  }
260
- throw err;
215
+ const upload = uploadService.startResumableUpload({
216
+ bucketId,
217
+ objectId,
218
+ metadataRaw: JSON.stringify(req.body),
219
+ authorization: req.header("authorization"),
220
+ });
221
+ res.header("x-goog-upload-chunk-granularity", "10000");
222
+ res.header("x-goog-upload-control-url", "");
223
+ res.header("x-goog-upload-status", "active");
224
+ res.header("x-gupload-uploadid", upload.id);
225
+ const uploadUrl = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE, req);
226
+ uploadUrl.pathname = `/v0/b/${bucketId}/o`;
227
+ uploadUrl.searchParams.set("name", objectId);
228
+ uploadUrl.searchParams.set("upload_id", upload.id);
229
+ uploadUrl.searchParams.set("upload_protocol", "resumable");
230
+ res.header("x-goog-upload-url", uploadUrl.toString());
231
+ return res.sendStatus(200);
261
232
  }
262
- res.header("X-Goog-Upload-Size-Received", upload.size.toString());
263
- return res.sendStatus(200);
264
- }
265
- if (uploadCommand === "cancel") {
266
- try {
267
- uploadService.cancelResumableUpload(uploadId);
233
+ if (!req.query.upload_id) {
234
+ return res.sendStatus(400);
268
235
  }
269
- catch (err) {
270
- if (err instanceof errors_1.NotFoundError) {
271
- return res.sendStatus(404);
236
+ const uploadId = req.query.upload_id.toString();
237
+ if (uploadCommand === "query") {
238
+ let upload;
239
+ try {
240
+ upload = uploadService.getResumableUpload(uploadId);
272
241
  }
273
- else if (err instanceof upload_1.NotCancellableError) {
274
- return res.sendStatus(400);
242
+ catch (err) {
243
+ if (err instanceof errors_1.NotFoundError) {
244
+ return res.sendStatus(404);
245
+ }
246
+ throw err;
275
247
  }
276
- throw err;
277
- }
278
- return res.sendStatus(200);
279
- }
280
- if (uploadCommand.includes("upload")) {
281
- let upload;
282
- try {
283
- upload = uploadService.continueResumableUpload(uploadId, await (0, request_1.reqBodyToBuffer)(req));
248
+ res.header("X-Goog-Upload-Size-Received", upload.size.toString());
249
+ return res.sendStatus(200);
284
250
  }
285
- catch (err) {
286
- if (err instanceof errors_1.NotFoundError) {
287
- return res.sendStatus(404);
251
+ if (uploadCommand === "cancel") {
252
+ try {
253
+ uploadService.cancelResumableUpload(uploadId);
288
254
  }
289
- else if (err instanceof upload_1.UploadNotActiveError) {
290
- return res.sendStatus(400);
255
+ catch (err) {
256
+ if (err instanceof errors_1.NotFoundError) {
257
+ return res.sendStatus(404);
258
+ }
259
+ else if (err instanceof upload_1.NotCancellableError) {
260
+ return res.sendStatus(400);
261
+ }
262
+ throw err;
291
263
  }
292
- throw err;
293
- }
294
- if (!uploadCommand.includes("finalize")) {
295
- res.header("x-goog-upload-status", "active");
296
- res.header("x-gupload-uploadid", upload.id);
297
264
  return res.sendStatus(200);
298
265
  }
299
- }
300
- if (uploadCommand.includes("finalize")) {
301
- let upload;
302
- try {
303
- upload = uploadService.finalizeResumableUpload(uploadId);
266
+ if (uploadCommand.includes("upload")) {
267
+ let upload;
268
+ try {
269
+ upload = uploadService.continueResumableUpload(uploadId, await (0, request_1.reqBodyToBuffer)(req));
270
+ }
271
+ catch (err) {
272
+ if (err instanceof errors_1.NotFoundError) {
273
+ return res.sendStatus(404);
274
+ }
275
+ else if (err instanceof upload_1.UploadNotActiveError) {
276
+ return res.sendStatus(400);
277
+ }
278
+ throw err;
279
+ }
280
+ if (!uploadCommand.includes("finalize")) {
281
+ res.header("x-goog-upload-status", "active");
282
+ res.header("x-gupload-uploadid", upload.id);
283
+ return res.sendStatus(200);
284
+ }
304
285
  }
305
- catch (err) {
306
- if (err instanceof errors_1.NotFoundError) {
307
- return res.sendStatus(404);
286
+ if (uploadCommand.includes("finalize")) {
287
+ let upload;
288
+ try {
289
+ upload = uploadService.finalizeResumableUpload(uploadId);
308
290
  }
309
- else if (err instanceof upload_1.UploadNotActiveError) {
310
- return res.sendStatus(400);
291
+ catch (err) {
292
+ if (err instanceof errors_1.NotFoundError) {
293
+ uploadService.setResponseCode(uploadId, 404);
294
+ return res.sendStatus(404);
295
+ }
296
+ else if (err instanceof upload_1.UploadNotActiveError) {
297
+ uploadService.setResponseCode(uploadId, 400);
298
+ return res.sendStatus(400);
299
+ }
300
+ else if (err instanceof upload_1.UploadPreviouslyFinalizedError) {
301
+ res.header("x-goog-upload-status", "final");
302
+ return res.sendStatus(uploadService.getPreviousResponseCode(uploadId));
303
+ }
304
+ throw err;
311
305
  }
312
- throw err;
306
+ res.header("x-goog-upload-status", "final");
307
+ return await finalizeOneShotUpload(upload);
308
+ }
309
+ }
310
+ if (!objectId) {
311
+ res.sendStatus(400);
312
+ return;
313
+ }
314
+ if (uploadType === "multipart") {
315
+ const contentTypeHeader = req.header("content-type");
316
+ if (!contentTypeHeader) {
317
+ return res.sendStatus(400);
313
318
  }
314
- let storedMetadata;
319
+ let metadataRaw;
320
+ let dataRaw;
315
321
  try {
316
- storedMetadata = await storageLayer.uploadObject(upload);
322
+ ({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await (0, request_1.reqBodyToBuffer)(req)));
317
323
  }
318
324
  catch (err) {
319
- if (err instanceof errors_1.ForbiddenError) {
320
- return res.status(403).json({
321
- error: {
322
- code: 403,
323
- message: `Permission denied. No WRITE permission.`,
324
- },
325
- });
325
+ if (err instanceof Error) {
326
+ return res.status(400).send(err.message);
326
327
  }
327
328
  throw err;
328
329
  }
329
- res.header("x-goog-upload-status", "final");
330
- storedMetadata.addDownloadToken(false);
331
- return res.status(200).json(new metadata_1.OutgoingFirebaseMetadata(storedMetadata));
330
+ const upload = uploadService.multipartUpload({
331
+ bucketId,
332
+ objectId,
333
+ metadataRaw,
334
+ dataRaw: dataRaw,
335
+ authorization: req.header("authorization"),
336
+ });
337
+ return await finalizeOneShotUpload(upload);
332
338
  }
333
- return res.sendStatus(400);
339
+ const upload = uploadService.mediaUpload({
340
+ bucketId: req.params.bucketId,
341
+ objectId: objectId,
342
+ dataRaw: await (0, request_1.reqBodyToBuffer)(req),
343
+ authorization: req.header("authorization"),
344
+ });
345
+ return await finalizeOneShotUpload(upload);
334
346
  };
335
347
  const handleTokenRequest = (req, res) => {
336
348
  var _a, _b;
@@ -469,11 +481,13 @@ function createFirebaseEndpoints(emulator) {
469
481
  }
470
482
  exports.createFirebaseEndpoints = createFirebaseEndpoints;
471
483
  function setObjectHeaders(res, metadata, headerOverride = { "Content-Encoding": undefined }) {
472
- res.setHeader("Content-Disposition", metadata.contentDisposition);
484
+ if (metadata.contentDisposition) {
485
+ res.setHeader("Content-Disposition", metadata.contentDisposition);
486
+ }
473
487
  if (headerOverride["Content-Encoding"]) {
474
488
  res.setHeader("Content-Encoding", headerOverride["Content-Encoding"]);
475
489
  }
476
- else {
490
+ else if (metadata.contentEncoding) {
477
491
  res.setHeader("Content-Encoding", metadata.contentEncoding);
478
492
  }
479
493
  if (metadata.cacheControl) {