firebase-tools 10.2.0 → 10.3.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 (139) hide show
  1. package/lib/apiv2.js +3 -0
  2. package/lib/appdistribution/options-parser-util.js +1 -1
  3. package/lib/auth.js +3 -3
  4. package/lib/command.js +1 -1
  5. package/lib/commands/apps-android-sha-create.js +2 -2
  6. package/lib/commands/apps-sdkconfig.js +1 -1
  7. package/lib/commands/auth-import.js +1 -1
  8. package/lib/commands/database-rules-list.js +2 -2
  9. package/lib/commands/emulators-start.js +1 -1
  10. package/lib/commands/ext-configure.js +58 -4
  11. package/lib/commands/ext-dev-init.js +49 -49
  12. package/lib/commands/ext-export.js +7 -2
  13. package/lib/commands/ext-install.js +163 -104
  14. package/lib/commands/ext-uninstall.js +17 -8
  15. package/lib/commands/ext-update.js +64 -11
  16. package/lib/commands/functions-config-clone.js +1 -1
  17. package/lib/commands/functions-config-export.js +1 -1
  18. package/lib/commands/hosting-clone.js +3 -3
  19. package/lib/commands/remoteconfig-get.js +1 -1
  20. package/lib/config.js +6 -3
  21. package/lib/deploy/extensions/deploymentSummary.js +3 -3
  22. package/lib/deploy/extensions/planner.js +7 -6
  23. package/lib/deploy/extensions/tasks.js +1 -1
  24. package/lib/deploy/functions/backend.js +21 -5
  25. package/lib/deploy/functions/checkIam.js +5 -5
  26. package/lib/deploy/functions/containerCleaner.js +3 -3
  27. package/lib/deploy/functions/ensure.js +3 -3
  28. package/lib/deploy/functions/functionsDeployHelper.js +2 -2
  29. package/lib/deploy/functions/prepare.js +5 -3
  30. package/lib/deploy/functions/pricing.js +1 -1
  31. package/lib/deploy/functions/prompts.js +2 -2
  32. package/lib/deploy/functions/release/fabricator.js +7 -7
  33. package/lib/deploy/functions/release/index.js +1 -1
  34. package/lib/deploy/functions/release/planner.js +43 -26
  35. package/lib/deploy/functions/release/reporter.js +3 -0
  36. package/lib/deploy/functions/runtimes/discovery/index.js +6 -6
  37. package/lib/deploy/functions/runtimes/discovery/parsing.js +1 -1
  38. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +22 -12
  39. package/lib/deploy/functions/runtimes/golang/index.js +2 -2
  40. package/lib/deploy/functions/runtimes/node/index.js +53 -0
  41. package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +2 -2
  42. package/lib/deploy/functions/runtimes/node/parseTriggers.js +52 -15
  43. package/lib/deploy/functions/runtimes/node/versioning.js +2 -2
  44. package/lib/deploy/functions/services/firebaseAlerts.js +30 -0
  45. package/lib/deploy/functions/services/index.js +9 -1
  46. package/lib/deploy/functions/services/storage.js +10 -4
  47. package/lib/deploy/functions/triggerRegionHelper.js +1 -1
  48. package/lib/deploy/functions/validate.js +3 -3
  49. package/lib/deploy/hosting/client.js +9 -0
  50. package/lib/deploy/hosting/convertConfig.js +6 -0
  51. package/lib/deploy/hosting/deploy.js +2 -2
  52. package/lib/deploy/hosting/hashcache.js +21 -19
  53. package/lib/deploy/hosting/index.js +5 -5
  54. package/lib/deploy/hosting/prepare.js +25 -25
  55. package/lib/deploy/hosting/release.js +21 -24
  56. package/lib/deploy/hosting/uploader.js +5 -5
  57. package/lib/deploy/remoteconfig/functions.js +2 -2
  58. package/lib/emulator/auth/cloudFunctions.js +1 -1
  59. package/lib/emulator/auth/operations.js +1 -1
  60. package/lib/emulator/commandUtils.js +5 -1
  61. package/lib/emulator/constants.js +4 -0
  62. package/lib/emulator/controller.js +54 -24
  63. package/lib/emulator/download.js +18 -1
  64. package/lib/emulator/downloadableEmulators.js +30 -13
  65. package/lib/emulator/emulatorLogger.js +12 -1
  66. package/lib/emulator/extensions/validation.js +70 -0
  67. package/lib/emulator/extensionsEmulator.js +175 -0
  68. package/lib/emulator/functionsEmulator.js +106 -44
  69. package/lib/emulator/functionsEmulatorRuntime.js +44 -36
  70. package/lib/emulator/functionsEmulatorShared.js +17 -10
  71. package/lib/emulator/functionsEmulatorShell.js +1 -1
  72. package/lib/emulator/functionsEmulatorUtils.js +4 -4
  73. package/lib/emulator/functionsRuntimeWorker.js +2 -2
  74. package/lib/emulator/hub.js +4 -3
  75. package/lib/emulator/loggingEmulator.js +1 -1
  76. package/lib/emulator/pubsubEmulator.js +1 -1
  77. package/lib/emulator/registry.js +10 -2
  78. package/lib/emulator/storage/apis/firebase.js +314 -332
  79. package/lib/emulator/storage/apis/gcloud.js +241 -121
  80. package/lib/emulator/storage/crc.js +5 -1
  81. package/lib/emulator/storage/errors.js +9 -0
  82. package/lib/emulator/storage/files.js +159 -300
  83. package/lib/emulator/storage/index.js +27 -73
  84. package/lib/emulator/storage/metadata.js +65 -51
  85. package/lib/emulator/storage/multipart.js +62 -0
  86. package/lib/emulator/storage/persistence.js +78 -0
  87. package/lib/emulator/storage/rules/config.js +33 -0
  88. package/lib/emulator/storage/rules/manager.js +81 -0
  89. package/lib/emulator/storage/rules/runtime.js +8 -7
  90. package/lib/emulator/storage/rules/utils.js +48 -0
  91. package/lib/emulator/storage/server.js +2 -2
  92. package/lib/emulator/storage/upload.js +106 -0
  93. package/lib/emulator/types.js +3 -0
  94. package/lib/ensureApiEnabled.js +5 -1
  95. package/lib/error.js +1 -1
  96. package/lib/extensions/askUserForParam.js +1 -1
  97. package/lib/extensions/changelog.js +3 -1
  98. package/lib/extensions/checkProjectBilling.js +1 -1
  99. package/lib/extensions/displayExtensionInfo.js +1 -1
  100. package/lib/extensions/emulator/optionsHelper.js +56 -8
  101. package/lib/extensions/emulator/specHelper.js +10 -23
  102. package/lib/extensions/export.js +1 -51
  103. package/lib/extensions/extensionsApi.js +1 -1
  104. package/lib/extensions/extensionsHelper.js +32 -19
  105. package/lib/extensions/listExtensions.js +2 -0
  106. package/lib/extensions/manifest.js +144 -0
  107. package/lib/extensions/metricsUtils.js +4 -4
  108. package/lib/extensions/paramHelper.js +9 -8
  109. package/lib/extensions/refs.js +1 -1
  110. package/lib/extensions/secretsUtils.js +3 -3
  111. package/lib/functional.js +1 -1
  112. package/lib/functions/env.js +6 -7
  113. package/lib/functions/events/v2.js +11 -0
  114. package/lib/gcp/cloudfunctions.js +42 -11
  115. package/lib/gcp/cloudfunctionsv2.js +48 -17
  116. package/lib/gcp/cloudtasks.js +1 -1
  117. package/lib/gcp/docker.js +2 -2
  118. package/lib/gcp/resourceManager.js +4 -4
  119. package/lib/gcp/run.js +2 -2
  120. package/lib/gcp/storage.js +1 -0
  121. package/lib/hosting/api.js +1 -1
  122. package/lib/hosting/functionsProxy.js +15 -5
  123. package/lib/hosting/proxy.js +2 -2
  124. package/lib/init/features/account.js +1 -1
  125. package/lib/management/database.js +1 -1
  126. package/lib/previews.js +1 -1
  127. package/lib/responseToError.js +16 -7
  128. package/lib/serve/functions.js +2 -1
  129. package/lib/serve/hosting.js +1 -1
  130. package/lib/utils.js +15 -2
  131. package/npm-shrinkwrap.json +904 -412
  132. package/package.json +3 -3
  133. package/schema/firebase-config.json +32 -0
  134. package/templates/init/functions/javascript/package.lint.json +3 -3
  135. package/templates/init/functions/javascript/package.nolint.json +2 -2
  136. package/templates/init/functions/typescript/package.lint.json +7 -7
  137. package/templates/init/functions/typescript/package.nolint.json +3 -3
  138. package/lib/deploy/extensions/params.js +0 -39
  139. package/lib/deploy/functions/eventTypes.js +0 -10
@@ -5,34 +5,14 @@ const emulatorLogger_1 = require("../../emulatorLogger");
5
5
  const types_1 = require("../../types");
6
6
  const zlib_1 = require("zlib");
7
7
  const metadata_1 = require("../metadata");
8
- const mime = require("mime");
9
8
  const express_1 = require("express");
10
9
  const registry_1 = require("../../registry");
11
- const types_2 = require("../rules/types");
12
- async function isPermitted(opts) {
13
- if (!opts.ruleset) {
14
- emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE).log("WARN", `Can not process SDK request with no loaded ruleset`);
15
- return false;
16
- }
17
- if (["Bearer owner", "Firebase owner"].includes(opts.authorization || "")) {
18
- return true;
19
- }
20
- const { permitted, issues } = await opts.ruleset.verify({
21
- method: opts.method,
22
- path: opts.path,
23
- file: opts.file,
24
- token: opts.authorization ? opts.authorization.split(" ")[1] : undefined,
25
- });
26
- if (issues.exist()) {
27
- issues.all.forEach((warningOrError) => {
28
- emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE).log("WARN", warningOrError);
29
- });
30
- }
31
- return !!permitted;
32
- }
10
+ const multipart_1 = require("../multipart");
11
+ const errors_1 = require("../errors");
12
+ const upload_1 = require("../upload");
33
13
  function createFirebaseEndpoints(emulator) {
34
14
  const firebaseStorageAPI = (0, express_1.Router)();
35
- const { storageLayer } = emulator;
15
+ const { storageLayer, uploadService } = emulator;
36
16
  if (process.env.STORAGE_EMULATOR_DEBUG) {
37
17
  firebaseStorageAPI.use((req, res, next) => {
38
18
  console.log("--------------INCOMING REQUEST--------------");
@@ -89,328 +69,244 @@ function createFirebaseEndpoints(emulator) {
89
69
  next();
90
70
  });
91
71
  firebaseStorageAPI.get("/b/:bucketId/o/:objectId", async (req, res) => {
92
- const decodedObjectId = decodeURIComponent(req.params.objectId);
93
- const operationPath = ["b", req.params.bucketId, "o", decodedObjectId].join("/");
94
- const md = storageLayer.getMetadata(req.params.bucketId, decodedObjectId);
95
- const rulesFiles = {};
96
- if (md) {
97
- rulesFiles.before = md.asRulesResource();
98
- }
99
- const isPermittedViaHeader = await isPermitted({
100
- ruleset: emulator.rules,
101
- method: types_2.RulesetOperationMethod.GET,
102
- path: operationPath,
103
- file: rulesFiles,
104
- authorization: req.header("authorization"),
105
- });
106
- const isPermittedViaToken = req.query.token && md && md.downloadTokens.includes(req.query.token.toString());
107
- const isRequestPermitted = isPermittedViaHeader || !!isPermittedViaToken;
108
- if (!isRequestPermitted) {
109
- res.sendStatus(403);
110
- return;
72
+ var _a;
73
+ let metadata;
74
+ let data;
75
+ try {
76
+ ({ metadata, data } = await storageLayer.handleGetObject({
77
+ bucketId: req.params.bucketId,
78
+ decodedObjectId: decodeURIComponent(req.params.objectId),
79
+ authorization: req.header("authorization"),
80
+ downloadToken: (_a = req.query.token) === null || _a === void 0 ? void 0 : _a.toString(),
81
+ }));
111
82
  }
112
- if (!md) {
113
- res.sendStatus(404);
114
- return;
83
+ catch (err) {
84
+ if (err instanceof errors_1.NotFoundError) {
85
+ return res.sendStatus(404);
86
+ }
87
+ else if (err instanceof errors_1.ForbiddenError) {
88
+ return res.status(403).json({
89
+ error: {
90
+ code: 403,
91
+ message: `Permission denied. No READ permission.`,
92
+ },
93
+ });
94
+ }
95
+ throw err;
115
96
  }
116
- let isGZipped = false;
117
- if (md.contentEncoding == "gzip") {
118
- isGZipped = true;
97
+ if (!metadata.downloadTokens.length) {
98
+ metadata.addDownloadToken();
119
99
  }
120
- if (req.query.alt == "media") {
121
- let data = storageLayer.getBytes(req.params.bucketId, req.params.objectId);
122
- if (!data) {
123
- res.sendStatus(404);
124
- return;
125
- }
100
+ if (req.query.alt === "media") {
101
+ const isGZipped = metadata.contentEncoding === "gzip";
126
102
  if (isGZipped) {
127
103
  data = (0, zlib_1.gunzipSync)(data);
128
104
  }
129
105
  res.setHeader("Accept-Ranges", "bytes");
130
- res.setHeader("Content-Type", md.contentType);
131
- setObjectHeaders(res, md, { "Content-Encoding": isGZipped ? "identity" : undefined });
132
- const byteRange = [...(req.header("range") || "").split("bytes="), "", ""];
133
- const [rangeStart, rangeEnd] = byteRange[1].split("-");
134
- if (rangeStart) {
135
- const range = {
136
- start: parseInt(rangeStart),
137
- end: rangeEnd ? parseInt(rangeEnd) : data.byteLength,
138
- };
139
- res.setHeader("Content-Range", `bytes ${range.start}-${range.end - 1}/${data.byteLength}`);
140
- res.status(206).end(data.slice(range.start, range.end));
106
+ res.setHeader("Content-Type", metadata.contentType);
107
+ setObjectHeaders(res, metadata, { "Content-Encoding": isGZipped ? "identity" : undefined });
108
+ const byteRange = req.range(data.byteLength, { combine: true });
109
+ if (Array.isArray(byteRange) && byteRange.type === "bytes" && byteRange.length > 0) {
110
+ const range = byteRange[0];
111
+ res.setHeader("Content-Range", `${byteRange.type} ${range.start}-${range.end}/${data.byteLength}`);
112
+ res.status(206).end(data.slice(range.start, range.end + 1));
141
113
  }
142
114
  else {
143
115
  res.end(data);
144
116
  }
145
117
  return;
146
118
  }
147
- if (!md.downloadTokens.length) {
148
- md.addDownloadToken();
149
- }
150
- res.json(new metadata_1.OutgoingFirebaseMetadata(md));
119
+ return res.json(new metadata_1.OutgoingFirebaseMetadata(metadata));
151
120
  });
152
- const handleMetadataUpdate = async (req, res) => {
153
- const md = storageLayer.getMetadata(req.params.bucketId, req.params.objectId);
154
- if (!md) {
155
- res.sendStatus(404);
156
- return;
157
- }
158
- const decodedObjectId = decodeURIComponent(req.params.objectId);
159
- const operationPath = ["b", req.params.bucketId, "o", decodedObjectId].join("/");
160
- if (!(await isPermitted({
161
- ruleset: emulator.rules,
162
- method: types_2.RulesetOperationMethod.UPDATE,
163
- path: operationPath,
164
- authorization: req.header("authorization"),
165
- file: {
166
- before: md.asRulesResource(),
167
- after: md.asRulesResource(req.body),
168
- },
169
- }))) {
170
- return res.status(403).json({
171
- error: {
172
- code: 403,
173
- message: `Permission denied. No WRITE permission.`,
174
- },
175
- });
176
- }
177
- md.update(req.body);
178
- setObjectHeaders(res, md);
179
- const outgoingMetadata = new metadata_1.OutgoingFirebaseMetadata(md);
180
- res.json(outgoingMetadata);
181
- return;
182
- };
183
121
  firebaseStorageAPI.get("/b/:bucketId/o", async (req, res) => {
184
- let maxRes = undefined;
185
- if (req.query.maxResults) {
186
- maxRes = +req.query.maxResults.toString();
187
- }
188
- const delimiter = req.query.delimiter ? req.query.delimiter.toString() : "/";
189
- const pageToken = req.query.pageToken ? req.query.pageToken.toString() : undefined;
190
- const prefix = req.query.prefix ? req.query.prefix.toString() : "";
191
- const operationPath = ["b", req.params.bucketId, "o", prefix].join("/");
192
- if (!(await isPermitted({
193
- ruleset: emulator.rules,
194
- method: types_2.RulesetOperationMethod.LIST,
195
- path: operationPath,
196
- file: {},
197
- authorization: req.header("authorization"),
198
- }))) {
199
- return res.status(403).json({
200
- error: {
201
- code: 403,
202
- message: `Permission denied. No LIST permission.`,
203
- },
122
+ var _a, _b;
123
+ const maxResults = (_a = req.query.maxResults) === null || _a === void 0 ? void 0 : _a.toString();
124
+ let response;
125
+ try {
126
+ response = await storageLayer.handleListObjects({
127
+ bucketId: req.params.bucketId,
128
+ prefix: req.query.prefix ? req.query.prefix.toString() : "",
129
+ delimiter: req.query.delimiter ? req.query.delimiter.toString() : "",
130
+ pageToken: (_b = req.query.pageToken) === null || _b === void 0 ? void 0 : _b.toString(),
131
+ maxResults: maxResults ? +maxResults : undefined,
132
+ authorization: req.header("authorization"),
204
133
  });
205
134
  }
206
- res.json(storageLayer.listItemsAndPrefixes(req.params.bucketId, prefix, delimiter, pageToken, maxRes));
207
- });
208
- const handleUpload = async (req, res) => {
209
- var _a;
210
- if (req.query.create_token || req.query.delete_token) {
211
- const decodedObjectId = decodeURIComponent(req.params.objectId);
212
- const operationPath = ["b", req.params.bucketId, "o", decodedObjectId].join("/");
213
- const mdBefore = storageLayer.getMetadata(req.params.bucketId, req.params.objectId);
214
- if (!(await isPermitted({
215
- ruleset: emulator.rules,
216
- method: types_2.RulesetOperationMethod.UPDATE,
217
- path: operationPath,
218
- authorization: req.header("authorization"),
219
- file: {
220
- before: mdBefore === null || mdBefore === void 0 ? void 0 : mdBefore.asRulesResource(),
221
- },
222
- }))) {
135
+ catch (err) {
136
+ if (err instanceof errors_1.ForbiddenError) {
223
137
  return res.status(403).json({
224
138
  error: {
225
139
  code: 403,
226
- message: `Permission denied. No WRITE permission.`,
140
+ message: `Permission denied. No LIST permission.`,
227
141
  },
228
142
  });
229
143
  }
230
- if (!mdBefore) {
231
- return res.status(404).json({
232
- error: {
233
- code: 404,
234
- message: `Request object can not be found`,
235
- },
236
- });
144
+ throw err;
145
+ }
146
+ return res.json(response);
147
+ });
148
+ const reqBodyToBuffer = async (req) => {
149
+ if (req.body instanceof Buffer) {
150
+ return Buffer.from(req.body);
151
+ }
152
+ const bufs = [];
153
+ req.on("data", (data) => {
154
+ bufs.push(data);
155
+ });
156
+ await new Promise((resolve) => {
157
+ req.on("end", () => {
158
+ resolve();
159
+ });
160
+ });
161
+ return Buffer.concat(bufs);
162
+ };
163
+ const handleUpload = async (req, res) => {
164
+ if (!req.query.name) {
165
+ res.sendStatus(400);
166
+ return;
167
+ }
168
+ const bucketId = req.params.bucketId;
169
+ const objectId = req.query.name.toString();
170
+ const uploadType = req.header("x-goog-upload-protocol");
171
+ if (uploadType === "multipart") {
172
+ const contentTypeHeader = req.header("content-type");
173
+ if (!contentTypeHeader) {
174
+ return res.sendStatus(400);
175
+ }
176
+ let metadataRaw;
177
+ let dataRaw;
178
+ try {
179
+ ({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await reqBodyToBuffer(req)));
237
180
  }
238
- const createTokenParam = req.query["create_token"];
239
- const deleteTokenParam = req.query["delete_token"];
240
- let md;
241
- if (createTokenParam) {
242
- if (createTokenParam != "true") {
243
- res.sendStatus(400);
244
- return;
181
+ catch (err) {
182
+ if (err instanceof Error) {
183
+ return res.status(400).json({
184
+ error: {
185
+ code: 400,
186
+ message: err.toString(),
187
+ },
188
+ });
245
189
  }
246
- md = storageLayer.addDownloadToken(req.params.bucketId, req.params.objectId);
190
+ throw err;
247
191
  }
248
- else if (deleteTokenParam) {
249
- md = storageLayer.deleteDownloadToken(req.params.bucketId, req.params.objectId, deleteTokenParam.toString());
192
+ const upload = uploadService.multipartUpload({
193
+ bucketId,
194
+ objectId,
195
+ metadataRaw,
196
+ dataRaw: dataRaw,
197
+ authorization: req.header("authorization"),
198
+ });
199
+ let metadata;
200
+ try {
201
+ metadata = await storageLayer.handleUploadObject(upload);
250
202
  }
251
- if (!md) {
252
- res.sendStatus(404);
253
- return;
203
+ catch (err) {
204
+ if (err instanceof errors_1.ForbiddenError) {
205
+ return res.status(403).json({
206
+ error: {
207
+ code: 403,
208
+ message: "Permission denied. No WRITE permission.",
209
+ },
210
+ });
211
+ }
212
+ throw err;
254
213
  }
255
- setObjectHeaders(res, md);
256
- return res.json(new metadata_1.OutgoingFirebaseMetadata(md));
214
+ metadata.addDownloadToken();
215
+ return res.status(200).json(new metadata_1.OutgoingFirebaseMetadata(metadata));
257
216
  }
258
- if (!req.query.name) {
217
+ const uploadCommand = req.header("x-goog-upload-command");
218
+ if (!uploadCommand) {
259
219
  res.sendStatus(400);
260
220
  return;
261
221
  }
262
- const name = req.query.name.toString();
263
- const uploadType = req.header("x-goog-upload-protocol");
264
- if (uploadType == "multipart") {
265
- const contentType = req.header("content-type");
266
- if (!contentType || !contentType.startsWith("multipart/related")) {
267
- res.sendStatus(400);
268
- return;
269
- }
270
- const boundary = `--${contentType.split("boundary=")[1]}`;
271
- const bodyString = req.body.toString();
272
- const bodyStringParts = bodyString.split(boundary).filter((v) => v);
273
- const metadataString = bodyStringParts[0].split("\r\n")[3];
274
- const blobParts = bodyStringParts[1].split("\r\n");
275
- const blobContentTypeString = blobParts[1];
276
- if (!blobContentTypeString || !blobContentTypeString.startsWith("Content-Type: ")) {
277
- res.sendStatus(400);
278
- return;
279
- }
280
- const blobContentType = blobContentTypeString.slice("Content-Type: ".length);
281
- const bodyBuffer = req.body;
282
- const metadataSegment = `${boundary}${bodyString.split(boundary)[1]}`;
283
- const dataSegment = `${boundary}${bodyString.split(boundary).slice(2)[0]}`;
284
- const dataSegmentHeader = (dataSegment.match(/.+Content-Type:.+?\r\n\r\n/s) || [])[0];
285
- if (!dataSegmentHeader) {
286
- res.sendStatus(400);
287
- return;
288
- }
289
- const bufferOffset = metadataSegment.length + dataSegmentHeader.length;
290
- const blobBytes = Buffer.from(bodyBuffer.slice(bufferOffset, -`\r\n${boundary}--`.length));
291
- const md = storageLayer.oneShotUpload(req.params.bucketId, name, blobContentType, JSON.parse(metadataString), Buffer.from(blobBytes));
292
- if (!md) {
293
- res.sendStatus(400);
294
- return;
295
- }
296
- const operationPath = ["b", req.params.bucketId, "o", name].join("/");
297
- if (!(await isPermitted({
298
- ruleset: emulator.rules,
299
- method: types_2.RulesetOperationMethod.CREATE,
300
- path: operationPath,
222
+ if (uploadCommand === "start") {
223
+ const upload = uploadService.startResumableUpload({
224
+ bucketId,
225
+ objectId,
226
+ metadataRaw: JSON.stringify(req.body),
301
227
  authorization: req.header("authorization"),
302
- file: {
303
- after: md === null || md === void 0 ? void 0 : md.asRulesResource(),
304
- },
305
- }))) {
306
- storageLayer.deleteFile(md === null || md === void 0 ? void 0 : md.bucket, md === null || md === void 0 ? void 0 : md.name);
307
- return res.status(403).json({
308
- error: {
309
- code: 403,
310
- message: `Permission denied. No WRITE permission.`,
311
- },
312
- });
228
+ });
229
+ res.header("x-goog-upload-chunk-granularity", "10000");
230
+ res.header("x-goog-upload-control-url", "");
231
+ res.header("x-goog-upload-status", "active");
232
+ const emulatorInfo = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE);
233
+ res.header("x-goog-upload-url", `http://${req.hostname}:${emulatorInfo === null || emulatorInfo === void 0 ? void 0 : emulatorInfo.port}/v0/b/${bucketId}/o?name=${objectId}&upload_id=${upload.id}&upload_protocol=resumable`);
234
+ res.header("x-gupload-uploadid", upload.id);
235
+ return res.sendStatus(200);
236
+ }
237
+ if (!req.query.upload_id) {
238
+ return res.sendStatus(400);
239
+ }
240
+ const uploadId = req.query.upload_id.toString();
241
+ if (uploadCommand === "query") {
242
+ let upload;
243
+ try {
244
+ upload = uploadService.getResumableUpload(uploadId);
313
245
  }
314
- if (md.downloadTokens.length == 0) {
315
- md.addDownloadToken();
246
+ catch (err) {
247
+ if (err instanceof errors_1.NotFoundError) {
248
+ return res.sendStatus(404);
249
+ }
250
+ throw err;
316
251
  }
317
- res.json(new metadata_1.OutgoingFirebaseMetadata(md));
318
- return;
252
+ res.header("X-Goog-Upload-Size-Received", upload.size.toString());
253
+ return res.sendStatus(200);
319
254
  }
320
- else {
321
- const operationPath = ["b", req.params.bucketId, "o", name].join("/");
322
- const uploadCommand = req.header("x-goog-upload-command");
323
- if (!uploadCommand) {
324
- res.sendStatus(400);
325
- return;
326
- }
327
- if (uploadCommand == "start") {
328
- let objectContentType = req.header("x-goog-upload-header-content-type") ||
329
- req.header("x-goog-upload-content-type");
330
- if (!objectContentType) {
331
- const mimeTypeFromName = mime.getType(name);
332
- if (!mimeTypeFromName) {
333
- objectContentType = "application/octet-stream";
334
- }
335
- else {
336
- objectContentType = mimeTypeFromName;
337
- }
338
- }
339
- const upload = storageLayer.startUpload(req.params.bucketId, name, objectContentType, req.body);
340
- storageLayer.uploadBytes(upload.uploadId, Buffer.alloc(0));
341
- const emulatorInfo = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE);
342
- res.header("x-goog-upload-chunk-granularity", "10000");
343
- res.header("x-goog-upload-control-url", "");
344
- res.header("x-goog-upload-status", "active");
345
- res.header("x-goog-upload-url", `http://${req.hostname}:${emulatorInfo === null || emulatorInfo === void 0 ? void 0 : emulatorInfo.port}/v0/b/${req.params.bucketId}/o?name=${req.query.name}&upload_id=${upload.uploadId}&upload_protocol=resumable`);
346
- res.header("x-gupload-uploadid", upload.uploadId);
347
- res.status(200).send();
348
- return;
349
- }
350
- if (!req.query.upload_id) {
351
- res.sendStatus(400);
352
- return;
353
- }
354
- const uploadId = req.query.upload_id.toString();
355
- if (uploadCommand == "query") {
356
- const upload = storageLayer.queryUpload(uploadId);
357
- if (!upload) {
358
- res.sendStatus(400);
359
- return;
255
+ if (uploadCommand === "cancel") {
256
+ try {
257
+ uploadService.cancelResumableUpload(uploadId);
258
+ }
259
+ catch (err) {
260
+ if (err instanceof errors_1.NotFoundError) {
261
+ return res.sendStatus(404);
360
262
  }
361
- res.header("X-Goog-Upload-Size-Received", upload.currentBytesUploaded.toString());
362
- res.sendStatus(200);
363
- return;
364
- }
365
- if (uploadCommand == "cancel") {
366
- const upload = storageLayer.cancelUpload(uploadId);
367
- if (!upload) {
368
- res.sendStatus(400);
369
- return;
263
+ else if (err instanceof upload_1.NotCancellableError) {
264
+ return res.sendStatus(400);
370
265
  }
371
- res.sendStatus(200);
372
- return;
266
+ throw err;
373
267
  }
268
+ return res.sendStatus(200);
269
+ }
270
+ if (uploadCommand.includes("upload")) {
374
271
  let upload;
375
- if (uploadCommand.includes("upload")) {
376
- if (!(req.body instanceof Buffer)) {
377
- const bufs = [];
378
- req.on("data", (data) => {
379
- bufs.push(data);
380
- });
381
- await new Promise((resolve) => {
382
- req.on("end", () => {
383
- req.body = Buffer.concat(bufs);
384
- resolve();
385
- });
386
- });
272
+ try {
273
+ upload = uploadService.continueResumableUpload(uploadId, await reqBodyToBuffer(req));
274
+ }
275
+ catch (err) {
276
+ if (err instanceof errors_1.NotFoundError) {
277
+ return res.sendStatus(404);
387
278
  }
388
- upload = storageLayer.uploadBytes(uploadId, req.body);
389
- if (!upload) {
390
- res.sendStatus(400);
391
- return;
279
+ else if (err instanceof upload_1.UploadNotActiveError) {
280
+ return res.sendStatus(400);
392
281
  }
282
+ throw err;
283
+ }
284
+ if (!uploadCommand.includes("finalize")) {
393
285
  res.header("x-goog-upload-status", "active");
394
- res.header("x-gupload-uploadid", upload.uploadId);
286
+ res.header("x-gupload-uploadid", upload.id);
287
+ return res.sendStatus(200);
395
288
  }
396
- if (uploadCommand.includes("finalize")) {
397
- const finalizedUpload = storageLayer.finalizeUpload(uploadId);
398
- if (!finalizedUpload) {
399
- res.sendStatus(400);
400
- return;
289
+ }
290
+ if (uploadCommand.includes("finalize")) {
291
+ let upload;
292
+ try {
293
+ upload = uploadService.finalizeResumableUpload(uploadId);
294
+ }
295
+ catch (err) {
296
+ if (err instanceof errors_1.NotFoundError) {
297
+ return res.sendStatus(404);
401
298
  }
402
- upload = finalizedUpload.upload;
403
- res.header("x-goog-upload-status", "final");
404
- if (!(await isPermitted({
405
- ruleset: emulator.rules,
406
- method: types_2.RulesetOperationMethod.CREATE,
407
- path: operationPath,
408
- authorization: req.header("authorization"),
409
- file: {
410
- after: (_a = storageLayer.getMetadata(req.params.bucketId, name)) === null || _a === void 0 ? void 0 : _a.asRulesResource(),
411
- },
412
- }))) {
413
- storageLayer.deleteFile(upload.bucketId, name);
299
+ else if (err instanceof upload_1.UploadNotActiveError) {
300
+ return res.sendStatus(400);
301
+ }
302
+ throw err;
303
+ }
304
+ let metadata;
305
+ try {
306
+ metadata = await storageLayer.handleUploadObject(upload);
307
+ }
308
+ catch (err) {
309
+ if (err instanceof errors_1.ForbiddenError) {
414
310
  return res.status(403).json({
415
311
  error: {
416
312
  code: 403,
@@ -418,20 +314,107 @@ function createFirebaseEndpoints(emulator) {
418
314
  },
419
315
  });
420
316
  }
421
- const md = finalizedUpload.file.metadata;
422
- if (md.downloadTokens.length == 0) {
423
- md.addDownloadToken();
317
+ throw err;
318
+ }
319
+ metadata.addDownloadToken();
320
+ return res.status(200).json(new metadata_1.OutgoingFirebaseMetadata(metadata));
321
+ }
322
+ return res.sendStatus(400);
323
+ };
324
+ const handleTokenRequest = (req, res) => {
325
+ var _a, _b;
326
+ if (!req.query.create_token && !req.query.delete_token) {
327
+ return res.sendStatus(400);
328
+ }
329
+ const bucketId = req.params.bucketId;
330
+ const decodedObjectId = decodeURIComponent(req.params.objectId);
331
+ const authorization = req.header("authorization");
332
+ let metadata;
333
+ if (req.query.create_token) {
334
+ if (req.query.create_token !== "true") {
335
+ return res.sendStatus(400);
336
+ }
337
+ try {
338
+ metadata = storageLayer.handleCreateDownloadToken({
339
+ bucketId,
340
+ decodedObjectId,
341
+ authorization,
342
+ });
343
+ }
344
+ catch (err) {
345
+ if (err instanceof errors_1.ForbiddenError) {
346
+ return res.status(403).json({
347
+ error: {
348
+ code: 403,
349
+ message: `Missing admin credentials.`,
350
+ },
351
+ });
352
+ }
353
+ if (err instanceof errors_1.NotFoundError) {
354
+ return res.sendStatus(404);
424
355
  }
425
- res.json(new metadata_1.OutgoingFirebaseMetadata(finalizedUpload.file.metadata));
356
+ throw err;
426
357
  }
427
- else if (!upload) {
428
- res.sendStatus(400);
429
- return;
358
+ }
359
+ else {
360
+ try {
361
+ metadata = storageLayer.handleDeleteDownloadToken({
362
+ bucketId,
363
+ decodedObjectId,
364
+ token: (_b = (_a = req.query["delete_token"]) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : "",
365
+ authorization,
366
+ });
430
367
  }
431
- else {
432
- res.sendStatus(200);
368
+ catch (err) {
369
+ if (err instanceof errors_1.ForbiddenError) {
370
+ return res.status(403).json({
371
+ error: {
372
+ code: 403,
373
+ message: `Missing admin credentials.`,
374
+ },
375
+ });
376
+ }
377
+ if (err instanceof errors_1.NotFoundError) {
378
+ return res.sendStatus(404);
379
+ }
380
+ throw err;
381
+ }
382
+ }
383
+ setObjectHeaders(res, metadata);
384
+ return res.json(new metadata_1.OutgoingFirebaseMetadata(metadata));
385
+ };
386
+ const handleObjectPostRequest = async (req, res) => {
387
+ if (req.query.create_token || req.query.delete_token) {
388
+ return handleTokenRequest(req, res);
389
+ }
390
+ return handleUpload(req, res);
391
+ };
392
+ const handleMetadataUpdate = async (req, res) => {
393
+ let metadata;
394
+ try {
395
+ metadata = await storageLayer.handleUpdateObjectMetadata({
396
+ bucketId: req.params.bucketId,
397
+ decodedObjectId: decodeURIComponent(req.params.objectId),
398
+ metadata: req.body,
399
+ authorization: req.header("authorization"),
400
+ });
401
+ }
402
+ catch (err) {
403
+ if (err instanceof errors_1.ForbiddenError) {
404
+ return res.status(403).json({
405
+ error: {
406
+ code: 403,
407
+ message: `Permission denied. No WRITE permission.`,
408
+ },
409
+ });
410
+ }
411
+ if (err instanceof errors_1.NotFoundError) {
412
+ return res.sendStatus(404);
433
413
  }
414
+ throw err;
434
415
  }
416
+ setObjectHeaders(res, metadata);
417
+ return res.json(new metadata_1.OutgoingFirebaseMetadata(metadata));
435
418
  };
436
419
  firebaseStorageAPI.patch("/b/:bucketId/o/:objectId", handleMetadataUpdate);
437
420
  firebaseStorageAPI.put("/b/:bucketId/o/:objectId?", async (req, res) => {
@@ -440,34 +423,33 @@ function createFirebaseEndpoints(emulator) {
440
423
  case "patch":
441
424
  return handleMetadataUpdate(req, res);
442
425
  default:
443
- return handleUpload(req, res);
426
+ return handleObjectPostRequest(req, res);
444
427
  }
445
428
  });
446
- firebaseStorageAPI.post("/b/:bucketId/o/:objectId?", handleUpload);
429
+ firebaseStorageAPI.post("/b/:bucketId/o/:objectId?", handleObjectPostRequest);
447
430
  firebaseStorageAPI.delete("/b/:bucketId/o/:objectId", async (req, res) => {
448
- const decodedObjectId = decodeURIComponent(req.params.objectId);
449
- const operationPath = ["b", req.params.bucketId, "o", decodedObjectId].join("/");
450
- if (!(await isPermitted({
451
- ruleset: emulator.rules,
452
- method: types_2.RulesetOperationMethod.DELETE,
453
- path: operationPath,
454
- authorization: req.header("authorization"),
455
- file: {},
456
- }))) {
457
- return res.status(403).json({
458
- error: {
459
- code: 403,
460
- message: `Permission denied. No WRITE permission.`,
461
- },
431
+ try {
432
+ await storageLayer.handleDeleteObject({
433
+ bucketId: req.params.bucketId,
434
+ decodedObjectId: decodeURIComponent(req.params.objectId),
435
+ authorization: req.header("authorization"),
462
436
  });
463
437
  }
464
- const md = storageLayer.getMetadata(req.params.bucketId, decodedObjectId);
465
- if (!md) {
466
- res.sendStatus(404);
467
- return;
438
+ catch (err) {
439
+ if (err instanceof errors_1.ForbiddenError) {
440
+ return res.status(403).json({
441
+ error: {
442
+ code: 403,
443
+ message: `Permission denied. No WRITE permission.`,
444
+ },
445
+ });
446
+ }
447
+ if (err instanceof errors_1.NotFoundError) {
448
+ return res.sendStatus(404);
449
+ }
450
+ throw err;
468
451
  }
469
- storageLayer.deleteFile(req.params.bucketId, req.params.objectId);
470
- res.sendStatus(200);
452
+ res.sendStatus(204);
471
453
  });
472
454
  firebaseStorageAPI.get("/", (req, res) => {
473
455
  res.json({ emulator: "storage" });