firebase-tools 11.5.0 → 11.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/lib/command.js +33 -7
  2. package/lib/commands/crashlytics-mappingfile-generateid.js +26 -0
  3. package/lib/commands/crashlytics-mappingfile-upload.js +46 -0
  4. package/lib/commands/crashlytics-symbols-upload.js +18 -87
  5. package/lib/commands/emulators-exec.js +4 -1
  6. package/lib/commands/emulators-export.js +5 -2
  7. package/lib/commands/emulators-start.js +23 -17
  8. package/lib/commands/ext-dev-publish.js +3 -0
  9. package/lib/commands/functions-delete.js +2 -0
  10. package/lib/commands/functions-secrets-get.js +2 -0
  11. package/lib/commands/index.js +3 -0
  12. package/lib/commands/login.js +2 -2
  13. package/lib/crashlytics/buildToolsJarHelper.js +51 -0
  14. package/lib/deploy/functions/backend.js +4 -4
  15. package/lib/deploy/functions/build.js +76 -9
  16. package/lib/deploy/functions/checkIam.js +6 -5
  17. package/lib/deploy/functions/params.js +22 -16
  18. package/lib/deploy/functions/prepare.js +1 -1
  19. package/lib/deploy/functions/release/fabricator.js +22 -5
  20. package/lib/deploy/functions/release/index.js +2 -0
  21. package/lib/deploy/functions/runtimes/discovery/index.js +1 -16
  22. package/lib/deploy/functions/runtimes/discovery/parsing.js +16 -0
  23. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +59 -131
  24. package/lib/deploy/functions/runtimes/node/parseTriggers.js +1 -1
  25. package/lib/emulator/auth/index.js +7 -2
  26. package/lib/emulator/auth/operations.js +10 -10
  27. package/lib/emulator/commandUtils.js +32 -15
  28. package/lib/emulator/constants.js +14 -6
  29. package/lib/emulator/controller.js +49 -17
  30. package/lib/emulator/downloadableEmulators.js +7 -7
  31. package/lib/emulator/eventarcEmulator.js +148 -0
  32. package/lib/emulator/extensionsEmulator.js +3 -1
  33. package/lib/emulator/functionsEmulator.js +44 -4
  34. package/lib/emulator/functionsEmulatorRuntime.js +12 -23
  35. package/lib/emulator/functionsEmulatorShared.js +6 -1
  36. package/lib/emulator/hub.js +7 -3
  37. package/lib/emulator/hubClient.js +2 -2
  38. package/lib/emulator/hubExport.js +22 -2
  39. package/lib/emulator/registry.js +1 -0
  40. package/lib/emulator/storage/apis/firebase.js +145 -129
  41. package/lib/emulator/storage/apis/gcloud.js +102 -42
  42. package/lib/emulator/storage/files.js +39 -17
  43. package/lib/emulator/storage/metadata.js +76 -55
  44. package/lib/emulator/storage/multipart.js +2 -2
  45. package/lib/emulator/storage/rules/runtime.js +12 -4
  46. package/lib/emulator/storage/server.js +2 -1
  47. package/lib/emulator/storage/upload.js +46 -9
  48. package/lib/emulator/types.js +3 -0
  49. package/lib/emulator/ui.js +7 -2
  50. package/lib/extensions/extensionsApi.js +2 -1
  51. package/lib/extensions/extensionsHelper.js +29 -1
  52. package/lib/functions/constants.js +14 -0
  53. package/lib/functions/env.js +9 -9
  54. package/lib/gcp/cloudfunctions.js +15 -18
  55. package/lib/gcp/cloudfunctionsv2.js +15 -18
  56. package/lib/gcp/cloudscheduler.js +32 -14
  57. package/lib/serve/index.js +15 -0
  58. package/lib/track.js +122 -3
  59. package/lib/utils.js +14 -1
  60. package/npm-shrinkwrap.json +542 -9
  61. package/package.json +5 -4
  62. package/schema/firebase-config.json +12 -0
  63. package/templates/extensions/CHANGELOG.md +1 -7
@@ -39,6 +39,7 @@ class EmulatorRegistry {
39
39
  pubsub: 3.2,
40
40
  auth: 3.3,
41
41
  storage: 3.5,
42
+ eventarc: 3.6,
42
43
  hub: 4,
43
44
  logging: 5,
44
45
  };
@@ -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,47 +167,26 @@ function createFirebaseEndpoints(emulator) {
165
167
  });
166
168
  });
167
169
  const handleUpload = async (req, res) => {
168
- if (!req.query.name) {
169
- res.sendStatus(400);
170
- return;
171
- }
170
+ var _a, _b;
172
171
  const bucketId = req.params.bucketId;
173
- const objectId = req.query.name.toString();
174
- const uploadType = req.header("x-goog-upload-protocol");
175
- if (uploadType === "multipart") {
176
- const contentTypeHeader = req.header("content-type");
177
- if (!contentTypeHeader) {
178
- return res.sendStatus(400);
172
+ const objectId = req.params.objectId
173
+ ? decodeURIComponent(req.params.objectId)
174
+ : ((_a = req.query.name) === null || _a === void 0 ? void 0 : _a.toString()) || null;
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 });
179
181
  }
180
- let metadataRaw;
181
- let dataRaw;
182
- try {
183
- ({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await (0, request_1.reqBodyToBuffer)(req)));
184
- }
185
- catch (err) {
186
- if (err instanceof Error) {
187
- return res.status(400).json({
188
- error: {
189
- code: 400,
190
- message: err.message,
191
- },
192
- });
193
- }
194
- throw err;
195
- }
196
- const upload = uploadService.multipartUpload({
197
- bucketId,
198
- objectId,
199
- metadataRaw,
200
- dataRaw: dataRaw,
201
- authorization: req.header("authorization"),
202
- });
203
182
  let metadata;
204
183
  try {
205
184
  metadata = await storageLayer.uploadObject(upload);
206
185
  }
207
186
  catch (err) {
208
187
  if (err instanceof errors_1.ForbiddenError) {
188
+ res.header("x-goog-upload-status", "final");
189
+ uploadService.setResponseCode(upload.id, 403);
209
190
  return res.status(403).json({
210
191
  error: {
211
192
  code: 403,
@@ -215,120 +196,153 @@ function createFirebaseEndpoints(emulator) {
215
196
  }
216
197
  throw err;
217
198
  }
218
- metadata.addDownloadToken(false);
199
+ if (!metadata.contentDisposition) {
200
+ metadata.contentDisposition = "inline";
201
+ }
219
202
  return res.status(200).json(new metadata_1.OutgoingFirebaseMetadata(metadata));
220
203
  }
221
- const uploadCommand = req.header("x-goog-upload-command");
222
- if (!uploadCommand) {
223
- res.sendStatus(400);
224
- return;
225
- }
226
- if (uploadCommand === "start") {
227
- const upload = uploadService.startResumableUpload({
228
- bucketId,
229
- objectId,
230
- metadataRaw: JSON.stringify(req.body),
231
- authorization: req.header("authorization"),
232
- });
233
- res.header("x-goog-upload-chunk-granularity", "10000");
234
- res.header("x-goog-upload-control-url", "");
235
- res.header("x-goog-upload-status", "active");
236
- res.header("x-gupload-uploadid", upload.id);
237
- const uploadUrl = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE, req);
238
- uploadUrl.pathname = `/v0/b/${bucketId}/o`;
239
- uploadUrl.searchParams.set("name", objectId);
240
- uploadUrl.searchParams.set("upload_id", upload.id);
241
- uploadUrl.searchParams.set("upload_protocol", "resumable");
242
- res.header("x-goog-upload-url", uploadUrl.toString());
243
- return res.sendStatus(200);
244
- }
245
- if (!req.query.upload_id) {
246
- return res.sendStatus(400);
247
- }
248
- const uploadId = req.query.upload_id.toString();
249
- if (uploadCommand === "query") {
250
- let upload;
251
- try {
252
- upload = uploadService.getResumableUpload(uploadId);
253
- }
254
- catch (err) {
255
- if (err instanceof errors_1.NotFoundError) {
256
- return res.sendStatus(404);
204
+ if (uploadType === "resumable") {
205
+ const uploadCommand = req.header("x-goog-upload-command");
206
+ if (!uploadCommand) {
207
+ res.sendStatus(400);
208
+ return;
209
+ }
210
+ if (uploadCommand === "start") {
211
+ if (!objectId) {
212
+ res.sendStatus(400);
213
+ return;
257
214
  }
258
- 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);
259
232
  }
260
- res.header("X-Goog-Upload-Size-Received", upload.size.toString());
261
- return res.sendStatus(200);
262
- }
263
- if (uploadCommand === "cancel") {
264
- try {
265
- uploadService.cancelResumableUpload(uploadId);
233
+ if (!req.query.upload_id) {
234
+ return res.sendStatus(400);
266
235
  }
267
- catch (err) {
268
- if (err instanceof errors_1.NotFoundError) {
269
- 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);
270
241
  }
271
- else if (err instanceof upload_1.NotCancellableError) {
272
- return res.sendStatus(400);
242
+ catch (err) {
243
+ if (err instanceof errors_1.NotFoundError) {
244
+ return res.sendStatus(404);
245
+ }
246
+ throw err;
273
247
  }
274
- throw err;
275
- }
276
- return res.sendStatus(200);
277
- }
278
- if (uploadCommand.includes("upload")) {
279
- let upload;
280
- try {
281
- 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);
282
250
  }
283
- catch (err) {
284
- if (err instanceof errors_1.NotFoundError) {
285
- return res.sendStatus(404);
251
+ if (uploadCommand === "cancel") {
252
+ try {
253
+ uploadService.cancelResumableUpload(uploadId);
286
254
  }
287
- else if (err instanceof upload_1.UploadNotActiveError) {
288
- 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;
289
263
  }
290
- throw err;
291
- }
292
- if (!uploadCommand.includes("finalize")) {
293
- res.header("x-goog-upload-status", "active");
294
- res.header("x-gupload-uploadid", upload.id);
295
264
  return res.sendStatus(200);
296
265
  }
297
- }
298
- if (uploadCommand.includes("finalize")) {
299
- let upload;
300
- try {
301
- 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
+ }
302
285
  }
303
- catch (err) {
304
- if (err instanceof errors_1.NotFoundError) {
305
- return res.sendStatus(404);
286
+ if (uploadCommand.includes("finalize")) {
287
+ let upload;
288
+ try {
289
+ upload = uploadService.finalizeResumableUpload(uploadId);
306
290
  }
307
- else if (err instanceof upload_1.UploadNotActiveError) {
308
- 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;
309
305
  }
310
- 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);
311
318
  }
312
- let storedMetadata;
319
+ let metadataRaw;
320
+ let dataRaw;
313
321
  try {
314
- storedMetadata = await storageLayer.uploadObject(upload);
322
+ ({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await (0, request_1.reqBodyToBuffer)(req)));
315
323
  }
316
324
  catch (err) {
317
- if (err instanceof errors_1.ForbiddenError) {
318
- return res.status(403).json({
319
- error: {
320
- code: 403,
321
- message: `Permission denied. No WRITE permission.`,
322
- },
323
- });
325
+ if (err instanceof Error) {
326
+ return res.status(400).send(err.message);
324
327
  }
325
328
  throw err;
326
329
  }
327
- res.header("x-goog-upload-status", "final");
328
- storedMetadata.addDownloadToken(false);
329
- 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);
330
338
  }
331
- 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);
332
346
  };
333
347
  const handleTokenRequest = (req, res) => {
334
348
  var _a, _b;
@@ -467,11 +481,13 @@ function createFirebaseEndpoints(emulator) {
467
481
  }
468
482
  exports.createFirebaseEndpoints = createFirebaseEndpoints;
469
483
  function setObjectHeaders(res, metadata, headerOverride = { "Content-Encoding": undefined }) {
470
- res.setHeader("Content-Disposition", metadata.contentDisposition);
484
+ if (metadata.contentDisposition) {
485
+ res.setHeader("Content-Disposition", metadata.contentDisposition);
486
+ }
471
487
  if (headerOverride["Content-Encoding"]) {
472
488
  res.setHeader("Content-Encoding", headerOverride["Content-Encoding"]);
473
489
  }
474
- else {
490
+ else if (metadata.contentEncoding) {
475
491
  res.setHeader("Content-Encoding", metadata.contentEncoding);
476
492
  }
477
493
  if (metadata.cacheControl) {
@@ -15,6 +15,45 @@ const request_1 = require("../../shared/request");
15
15
  function createCloudEndpoints(emulator) {
16
16
  const gcloudStorageAPI = (0, express_1.Router)();
17
17
  const { adminStorageLayer, uploadService } = emulator;
18
+ if (process.env.STORAGE_EMULATOR_DEBUG) {
19
+ gcloudStorageAPI.use((req, res, next) => {
20
+ console.log("--------------INCOMING REQUEST--------------");
21
+ console.log(`${req.method.toUpperCase()} ${req.path}`);
22
+ console.log("-- query:");
23
+ console.log(JSON.stringify(req.query, undefined, 2));
24
+ console.log("-- headers:");
25
+ console.log(JSON.stringify(req.headers, undefined, 2));
26
+ console.log("-- body:");
27
+ if (req.body instanceof Buffer) {
28
+ console.log(`Buffer of ${req.body.length}`);
29
+ }
30
+ else if (req.body) {
31
+ console.log(req.body);
32
+ }
33
+ else {
34
+ console.log("Empty body (could be stream)");
35
+ }
36
+ const resJson = res.json.bind(res);
37
+ res.json = (...args) => {
38
+ console.log("-- response:");
39
+ args.forEach((data) => console.log(JSON.stringify(data, undefined, 2)));
40
+ return resJson.call(res, ...args);
41
+ };
42
+ const resSendStatus = res.sendStatus.bind(res);
43
+ res.sendStatus = (status) => {
44
+ console.log("-- response status:");
45
+ console.log(status);
46
+ return resSendStatus.call(res, status);
47
+ };
48
+ const resStatus = res.status.bind(res);
49
+ res.status = (status) => {
50
+ console.log("-- response status:");
51
+ console.log(status);
52
+ return resStatus.call(res, status);
53
+ };
54
+ next();
55
+ });
56
+ }
18
57
  gcloudStorageAPI.use(/.*\/b\/(.+?)\/.*/, (req, res, next) => {
19
58
  adminStorageLayer.createBucket(req.params[0]);
20
59
  next();
@@ -25,7 +64,11 @@ function createCloudEndpoints(emulator) {
25
64
  items: await adminStorageLayer.listBuckets(),
26
65
  });
27
66
  });
28
- gcloudStorageAPI.get(["/b/:bucketId/o/:objectId", "/download/storage/v1/b/:bucketId/o/:objectId"], async (req, res) => {
67
+ gcloudStorageAPI.get([
68
+ "/b/:bucketId/o/:objectId",
69
+ "/download/storage/v1/b/:bucketId/o/:objectId",
70
+ "/storage/v1/b/:bucketId/o/:objectId",
71
+ ], async (req, res) => {
29
72
  let getObjectResponse;
30
73
  try {
31
74
  getObjectResponse = await adminStorageLayer.getObject({
@@ -87,7 +130,7 @@ function createCloudEndpoints(emulator) {
87
130
  throw err;
88
131
  }
89
132
  return res.status(200).json({
90
- kind: "#storage/objects",
133
+ kind: "storage#objects",
91
134
  nextPageToken: listResponse.nextPageToken,
92
135
  prefixes: listResponse.prefixes,
93
136
  items: (_a = listResponse.items) === null || _a === void 0 ? void 0 : _a.map((item) => new metadata_1.CloudStorageObjectMetadata(item)),
@@ -177,11 +220,8 @@ function createCloudEndpoints(emulator) {
177
220
  });
178
221
  });
179
222
  gcloudStorageAPI.post("/upload/storage/v1/b/:bucketId/o", async (req, res) => {
180
- const contentTypeHeader = req.header("content-type") || req.header("x-upload-content-type");
181
- if (!contentTypeHeader) {
182
- return res.sendStatus(400);
183
- }
184
- if (req.query.uploadType === "resumable") {
223
+ const uploadType = req.query.uploadType || req.header("X-Goog-Upload-Protocol");
224
+ if (uploadType === "resumable") {
185
225
  const emulatorInfo = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE);
186
226
  if (emulatorInfo === undefined) {
187
227
  return res.sendStatus(500);
@@ -204,45 +244,65 @@ function createCloudEndpoints(emulator) {
204
244
  uploadUrl.searchParams.set("upload_id", upload.id);
205
245
  return res.header("location", uploadUrl.toString()).sendStatus(200);
206
246
  }
207
- let metadataRaw;
208
- let dataRaw;
209
- try {
210
- ({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await (0, request_1.reqBodyToBuffer)(req)));
247
+ async function finalizeOneShotUpload(upload) {
248
+ let metadata;
249
+ try {
250
+ metadata = await adminStorageLayer.uploadObject(upload);
251
+ }
252
+ catch (err) {
253
+ if (err instanceof errors_1.ForbiddenError) {
254
+ return res.sendStatus(403);
255
+ }
256
+ throw err;
257
+ }
258
+ return res.status(200).json(new metadata_1.CloudStorageObjectMetadata(metadata));
211
259
  }
212
- catch (err) {
213
- if (err instanceof Error) {
214
- return res.status(400).json({
215
- error: {
216
- code: 400,
217
- message: err.message,
218
- },
219
- });
260
+ if (uploadType === "multipart") {
261
+ const contentTypeHeader = req.header("content-type") || req.header("x-upload-content-type");
262
+ if (!contentTypeHeader) {
263
+ return res.sendStatus(400);
220
264
  }
221
- throw err;
265
+ let metadataRaw;
266
+ let dataRaw;
267
+ try {
268
+ ({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await (0, request_1.reqBodyToBuffer)(req)));
269
+ }
270
+ catch (err) {
271
+ if (err instanceof Error) {
272
+ return res.status(400).json({
273
+ error: {
274
+ code: 400,
275
+ message: err.message,
276
+ },
277
+ });
278
+ }
279
+ throw err;
280
+ }
281
+ const name = getIncomingFileNameFromRequest(req.query, JSON.parse(metadataRaw));
282
+ if (name === undefined) {
283
+ res.sendStatus(400);
284
+ return;
285
+ }
286
+ const upload = uploadService.multipartUpload({
287
+ bucketId: req.params.bucketId,
288
+ objectId: name,
289
+ metadataRaw: metadataRaw,
290
+ dataRaw: dataRaw,
291
+ authorization: req.header("authorization"),
292
+ });
293
+ return await finalizeOneShotUpload(upload);
222
294
  }
223
- const name = getIncomingFileNameFromRequest(req.query, JSON.parse(metadataRaw));
224
- if (name === undefined) {
295
+ const name = req.query.name;
296
+ if (!name) {
225
297
  res.sendStatus(400);
226
- return;
227
298
  }
228
- const upload = uploadService.multipartUpload({
299
+ const upload = uploadService.mediaUpload({
229
300
  bucketId: req.params.bucketId,
230
- objectId: name,
231
- metadataRaw: metadataRaw,
232
- dataRaw: dataRaw,
301
+ objectId: name.toString(),
302
+ dataRaw: await (0, request_1.reqBodyToBuffer)(req),
233
303
  authorization: req.header("authorization"),
234
304
  });
235
- let metadata;
236
- try {
237
- metadata = await adminStorageLayer.uploadObject(upload);
238
- }
239
- catch (err) {
240
- if (err instanceof errors_1.ForbiddenError) {
241
- return res.sendStatus(403);
242
- }
243
- throw err;
244
- }
245
- return res.status(200).json(new metadata_1.CloudStorageObjectMetadata(metadata));
305
+ return await finalizeOneShotUpload(upload);
246
306
  });
247
307
  gcloudStorageAPI.get("/:bucketId/:objectId(**)", async (req, res) => {
248
308
  let getObjectResponse;
@@ -324,11 +384,11 @@ function sendFileBytes(md, data, req, res) {
324
384
  data = (0, zlib_1.gunzipSync)(data);
325
385
  }
326
386
  res.setHeader("Accept-Ranges", "bytes");
327
- res.setHeader("Content-Type", md.contentType);
328
- res.setHeader("Content-Disposition", md.contentDisposition);
329
- res.setHeader("Content-Encoding", isGZipped ? "identity" : md.contentEncoding);
387
+ res.setHeader("Content-Type", md.contentType || "application/octet-stream");
388
+ res.setHeader("Content-Disposition", md.contentDisposition || "attachment");
389
+ res.setHeader("Content-Encoding", isGZipped ? "identity" : md.contentEncoding || "");
330
390
  res.setHeader("ETag", md.etag);
331
- res.setHeader("Cache-Control", md.cacheControl);
391
+ res.setHeader("Cache-Control", md.cacheControl || "");
332
392
  res.setHeader("x-goog-generation", `${md.generation}`);
333
393
  res.setHeader("x-goog-metadatageneration", `${md.metageneration}`);
334
394
  res.setHeader("x-goog-storage-class", md.storageClass);
@@ -17,6 +17,8 @@ const logger_1 = require("../../logger");
17
17
  const adminSdkConfig_1 = require("../adminSdkConfig");
18
18
  const types_1 = require("./rules/types");
19
19
  const upload_1 = require("./upload");
20
+ const track_1 = require("../../track");
21
+ const types_2 = require("../types");
20
22
  class StoredFile {
21
23
  constructor(metadata) {
22
24
  this.metadata = metadata;
@@ -140,20 +142,26 @@ class StorageLayer {
140
142
  }
141
143
  const storedMetadata = this.getMetadata(upload.bucketId, upload.objectId);
142
144
  const filePath = this.path(upload.bucketId, upload.objectId);
145
+ function getIncomingMetadata(field) {
146
+ if (!upload.metadata) {
147
+ return undefined;
148
+ }
149
+ const value = upload.metadata[field];
150
+ return value === null ? undefined : value;
151
+ }
143
152
  const metadata = new metadata_1.StoredFileMetadata({
144
153
  name: upload.objectId,
145
154
  bucket: upload.bucketId,
146
- contentType: upload.metadata.contentType || "application/octet-stream",
147
- contentDisposition: upload.metadata.contentDisposition,
148
- contentEncoding: upload.metadata.contentEncoding,
149
- contentLanguage: upload.metadata.contentLanguage,
150
- cacheControl: upload.metadata.cacheControl,
151
- customMetadata: upload.metadata.metadata,
155
+ contentType: getIncomingMetadata("contentType"),
156
+ contentDisposition: getIncomingMetadata("contentDisposition"),
157
+ contentEncoding: getIncomingMetadata("contentEncoding"),
158
+ contentLanguage: getIncomingMetadata("contentLanguage"),
159
+ cacheControl: getIncomingMetadata("cacheControl"),
160
+ customMetadata: getIncomingMetadata("metadata"),
152
161
  }, this._cloudFunctions, this._persistence.readBytes(upload.path, upload.size));
153
- metadata.update(upload.metadata, false);
154
162
  const authorized = await this._rulesValidator.validate(["b", upload.bucketId, "o", upload.objectId].join("/"), upload.bucketId, types_1.RulesetOperationMethod.CREATE, {
155
163
  before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource(),
156
- after: metadata === null || metadata === void 0 ? void 0 : metadata.asRulesResource(),
164
+ after: metadata.asRulesResource(),
157
165
  }, upload.authorization);
158
166
  if (!authorized) {
159
167
  this._persistence.deleteFile(upload.path);
@@ -190,16 +198,20 @@ class StorageLayer {
190
198
  newMetadata.metadata[k] = "";
191
199
  }
192
200
  }
201
+ function getMetadata(field) {
202
+ const value = newMetadata[field];
203
+ return value === null ? undefined : value;
204
+ }
193
205
  const copiedFileMetadata = new metadata_1.StoredFileMetadata({
194
206
  name: destinationObject,
195
207
  bucket: destinationBucket,
196
- contentType: newMetadata.contentType || "application/octet-stream",
197
- contentDisposition: newMetadata.contentDisposition,
198
- contentEncoding: newMetadata.contentEncoding,
199
- contentLanguage: newMetadata.contentLanguage,
200
- cacheControl: newMetadata.cacheControl,
201
- customMetadata: newMetadata.metadata,
202
- }, this._cloudFunctions, sourceBytes, incomingMetadata);
208
+ contentType: getMetadata("contentType"),
209
+ contentDisposition: getMetadata("contentDisposition"),
210
+ contentEncoding: getMetadata("contentEncoding"),
211
+ contentLanguage: getMetadata("contentLanguage"),
212
+ cacheControl: getMetadata("cacheControl"),
213
+ customMetadata: getMetadata("metadata"),
214
+ }, this._cloudFunctions, sourceBytes);
203
215
  const file = new StoredFile(copiedFileMetadata);
204
216
  this._files.set(destinationFilePath, file);
205
217
  this._cloudFunctions.dispatch("finalize", new metadata_1.CloudStorageObjectMetadata(file.metadata));
@@ -292,7 +304,7 @@ class StorageLayer {
292
304
  get dirPath() {
293
305
  return this._persistence.dirPath;
294
306
  }
295
- async export(storageExportPath) {
307
+ async export(storageExportPath, options) {
296
308
  var e_1, _a;
297
309
  const bucketsList = {
298
310
  buckets: [],
@@ -300,6 +312,11 @@ class StorageLayer {
300
312
  for (const b of await this.listBuckets()) {
301
313
  bucketsList.buckets.push({ id: b.id });
302
314
  }
315
+ void (0, track_1.trackEmulator)("emulator_export", {
316
+ initiated_by: options.initiatedBy,
317
+ emulator_name: types_2.Emulators.STORAGE,
318
+ count: bucketsList.buckets.length,
319
+ });
303
320
  const bucketsFilePath = path.join(storageExportPath, "buckets.json");
304
321
  await fse.writeFile(bucketsFilePath, JSON.stringify(bucketsList, undefined, 2));
305
322
  const blobsDirPath = path.join(storageExportPath, "blobs");
@@ -323,9 +340,14 @@ class StorageLayer {
323
340
  finally { if (e_1) throw e_1.error; }
324
341
  }
325
342
  }
326
- import(storageExportPath) {
343
+ import(storageExportPath, options) {
327
344
  const bucketsFile = path.join(storageExportPath, "buckets.json");
328
345
  const bucketsList = JSON.parse((0, fs_1.readFileSync)(bucketsFile, "utf-8"));
346
+ void (0, track_1.trackEmulator)("emulator_import", {
347
+ initiated_by: options.initiatedBy,
348
+ emulator_name: types_2.Emulators.STORAGE,
349
+ count: bucketsList.buckets.length,
350
+ });
329
351
  for (const b of bucketsList.buckets) {
330
352
  const bucketMetadata = new metadata_1.CloudStorageBucketMetadata(b.id);
331
353
  this._buckets.set(b.id, bucketMetadata);