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
@@ -7,9 +7,13 @@ const types_1 = require("../../types");
7
7
  const metadata_1 = require("../metadata");
8
8
  const registry_1 = require("../../registry");
9
9
  const emulatorLogger_1 = require("../../emulatorLogger");
10
+ const crc_1 = require("../crc");
11
+ const multipart_1 = require("../multipart");
12
+ const upload_1 = require("../upload");
13
+ const errors_1 = require("../errors");
10
14
  function createCloudEndpoints(emulator) {
11
15
  const gcloudStorageAPI = (0, express_1.Router)();
12
- const { storageLayer } = emulator;
16
+ const { storageLayer, uploadService } = emulator;
13
17
  gcloudStorageAPI.use(/.*\/b\/(.+?)\/.*/, (req, res, next) => {
14
18
  storageLayer.createBucket(req.params[0]);
15
19
  next();
@@ -20,103 +24,158 @@ function createCloudEndpoints(emulator) {
20
24
  items: await storageLayer.listBuckets(),
21
25
  });
22
26
  });
23
- gcloudStorageAPI.get(["/b/:bucketId/o/:objectId", "/download/storage/v1/b/:bucketId/o/:objectId"], (req, res) => {
24
- const md = storageLayer.getMetadata(req.params.bucketId, req.params.objectId);
25
- if (!md) {
26
- res.sendStatus(404);
27
- return;
27
+ gcloudStorageAPI.get(["/b/:bucketId/o/:objectId", "/download/storage/v1/b/:bucketId/o/:objectId"], async (req, res) => {
28
+ let getObjectResponse;
29
+ try {
30
+ getObjectResponse = await storageLayer.handleGetObject({
31
+ bucketId: req.params.bucketId,
32
+ decodedObjectId: req.params.objectId,
33
+ }, true);
34
+ }
35
+ catch (err) {
36
+ if (err instanceof errors_1.NotFoundError) {
37
+ return sendObjectNotFound(req, res);
38
+ }
39
+ if (err instanceof errors_1.ForbiddenError) {
40
+ throw new Error("Request failed unexpectedly due to Firebase Rules.");
41
+ }
42
+ throw err;
28
43
  }
29
- if (req.query.alt == "media") {
30
- return sendFileBytes(md, storageLayer, req, res);
44
+ if (req.query.alt === "media") {
45
+ return sendFileBytes(getObjectResponse.metadata, getObjectResponse.data, req, res);
31
46
  }
32
- const outgoingMd = new metadata_1.CloudStorageObjectMetadata(md);
33
- res.json(outgoingMd).status(200).send();
34
- return;
47
+ return res.json(new metadata_1.CloudStorageObjectMetadata(getObjectResponse.metadata));
35
48
  });
36
- gcloudStorageAPI.patch("/b/:bucketId/o/:objectId", (req, res) => {
37
- const md = storageLayer.getMetadata(req.params.bucketId, req.params.objectId);
38
- if (!md) {
39
- res.sendStatus(404);
40
- return;
49
+ gcloudStorageAPI.patch("/b/:bucketId/o/:objectId", async (req, res) => {
50
+ let updatedMetadata;
51
+ try {
52
+ updatedMetadata = await storageLayer.handleUpdateObjectMetadata({
53
+ bucketId: req.params.bucketId,
54
+ decodedObjectId: req.params.objectId,
55
+ metadata: req.body,
56
+ }, true);
57
+ }
58
+ catch (err) {
59
+ if (err instanceof errors_1.NotFoundError) {
60
+ return sendObjectNotFound(req, res);
61
+ }
62
+ if (err instanceof errors_1.ForbiddenError) {
63
+ throw new Error("Request failed unexpectedly due to Firebase Rules.");
64
+ }
65
+ throw err;
41
66
  }
42
- md.update(req.body);
43
- const outgoingMetadata = new metadata_1.CloudStorageObjectMetadata(md);
44
- res.json(outgoingMetadata).status(200).send();
45
- return;
67
+ return res.json(new metadata_1.CloudStorageObjectMetadata(updatedMetadata));
46
68
  });
47
69
  gcloudStorageAPI.get("/b/:bucketId/o", (req, res) => {
48
70
  let maxRes = undefined;
49
71
  if (req.query.maxResults) {
50
72
  maxRes = +req.query.maxResults.toString();
51
73
  }
52
- const delimiter = req.query.delimiter ? req.query.delimiter.toString() : "/";
74
+ const delimiter = req.query.delimiter ? req.query.delimiter.toString() : "";
53
75
  const pageToken = req.query.pageToken ? req.query.pageToken.toString() : undefined;
54
76
  const prefix = req.query.prefix ? req.query.prefix.toString() : "";
55
77
  const listResult = storageLayer.listItems(req.params.bucketId, prefix, delimiter, pageToken, maxRes);
56
- res.json(listResult);
78
+ res.json(Object.assign(Object.assign({}, listResult), { kind: "#storage/objects" }));
57
79
  });
58
- gcloudStorageAPI.delete("/b/:bucketId/o/:objectId", (req, res) => {
59
- const md = storageLayer.getMetadata(req.params.bucketId, req.params.objectId);
60
- if (!md) {
61
- res.sendStatus(404);
62
- return;
80
+ gcloudStorageAPI.delete("/b/:bucketId/o/:objectId", async (req, res) => {
81
+ try {
82
+ await storageLayer.handleDeleteObject({
83
+ bucketId: req.params.bucketId,
84
+ decodedObjectId: req.params.objectId,
85
+ }, true);
63
86
  }
64
- storageLayer.deleteFile(req.params.bucketId, req.params.objectId);
65
- res.status(200).send();
87
+ catch (err) {
88
+ if (err instanceof errors_1.NotFoundError) {
89
+ return sendObjectNotFound(req, res);
90
+ }
91
+ if (err instanceof errors_1.ForbiddenError) {
92
+ throw new Error("Request failed unexpectedly due to Firebase Rules.");
93
+ }
94
+ throw err;
95
+ }
96
+ return res.sendStatus(204);
66
97
  });
67
- gcloudStorageAPI.put("/upload/storage/v1/b/:bucketId/o", async (req, res) => {
68
- if (!req.query.upload_id) {
69
- res.sendStatus(400);
70
- return;
98
+ const reqBodyToBuffer = async (req) => {
99
+ if (req.body instanceof Buffer) {
100
+ return Buffer.from(req.body);
71
101
  }
72
- const uploadId = req.query.upload_id.toString();
73
102
  const bufs = [];
74
103
  req.on("data", (data) => {
75
104
  bufs.push(data);
76
105
  });
77
106
  await new Promise((resolve) => {
78
107
  req.on("end", () => {
79
- req.body = Buffer.concat(bufs);
80
108
  resolve();
81
109
  });
82
110
  });
83
- let upload = storageLayer.uploadBytes(uploadId, req.body);
84
- if (!upload) {
111
+ return Buffer.concat(bufs);
112
+ };
113
+ gcloudStorageAPI.put("/upload/storage/v1/b/:bucketId/o", async (req, res) => {
114
+ if (!req.query.upload_id) {
85
115
  res.sendStatus(400);
86
116
  return;
87
117
  }
88
- const finalizedUpload = storageLayer.finalizeUpload(uploadId);
89
- if (!finalizedUpload) {
90
- res.sendStatus(400);
91
- return;
118
+ const uploadId = req.query.upload_id.toString();
119
+ let upload;
120
+ try {
121
+ uploadService.continueResumableUpload(uploadId, await reqBodyToBuffer(req));
122
+ upload = uploadService.finalizeResumableUpload(uploadId);
92
123
  }
93
- upload = finalizedUpload.upload;
94
- res.status(200).json(new metadata_1.CloudStorageObjectMetadata(finalizedUpload.file.metadata)).send();
124
+ catch (err) {
125
+ if (err instanceof errors_1.NotFoundError) {
126
+ return res.sendStatus(404);
127
+ }
128
+ else if (err instanceof upload_1.UploadNotActiveError) {
129
+ return res.sendStatus(400);
130
+ }
131
+ throw err;
132
+ }
133
+ let metadata;
134
+ try {
135
+ metadata = await storageLayer.handleUploadObject(upload, true);
136
+ }
137
+ catch (err) {
138
+ if (err instanceof errors_1.ForbiddenError) {
139
+ throw new Error("Request failed unexpectedly due to Firebase Rules.");
140
+ }
141
+ throw err;
142
+ }
143
+ return res.json(new metadata_1.CloudStorageObjectMetadata(metadata));
95
144
  });
96
- gcloudStorageAPI.post("/b/:bucketId/o/:objectId/acl", (req, res) => {
145
+ gcloudStorageAPI.post("/b/:bucketId/o/:objectId/acl", async (req, res) => {
97
146
  var _a, _b;
98
147
  emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE).log("WARN_ONCE", "Cloud Storage ACLs are not supported in the Storage Emulator. All related methods will succeed, but have no effect.");
99
- const md = storageLayer.getMetadata(req.params.bucketId, req.params.objectId);
100
- if (!md) {
101
- res.sendStatus(404);
102
- return;
148
+ let getObjectResponse;
149
+ try {
150
+ getObjectResponse = await storageLayer.handleGetObject({
151
+ bucketId: req.params.bucketId,
152
+ decodedObjectId: req.params.objectId,
153
+ }, true);
154
+ }
155
+ catch (err) {
156
+ if (err instanceof errors_1.NotFoundError) {
157
+ return sendObjectNotFound(req, res);
158
+ }
159
+ if (err instanceof errors_1.ForbiddenError) {
160
+ throw new Error("Request failed unexpectedly due to Firebase Rules.");
161
+ }
162
+ throw err;
103
163
  }
104
- md.update({});
105
- res
106
- .json({
164
+ const { metadata } = getObjectResponse;
165
+ metadata.update({});
166
+ return res.json({
107
167
  kind: "storage#objectAccessControl",
108
- object: md.name,
109
- id: `${req.params.bucketId}/${md.name}/${md.generation}/allUsers`,
110
- selfLink: `http://${(_a = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE)) === null || _a === void 0 ? void 0 : _a.host}:${(_b = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE)) === null || _b === void 0 ? void 0 : _b.port}/storage/v1/b/${md.bucket}/o/${encodeURIComponent(md.name)}/acl/allUsers`,
111
- bucket: md.bucket,
168
+ object: metadata.name,
169
+ id: `${req.params.bucketId}/${metadata.name}/${metadata.generation}/allUsers`,
170
+ selfLink: `http://${(_a = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE)) === null || _a === void 0 ? void 0 : _a.host}:${(_b = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE)) === null || _b === void 0 ? void 0 : _b.port}/storage/v1/b/${metadata.bucket}/o/${encodeURIComponent(metadata.name)}/acl/allUsers`,
171
+ bucket: metadata.bucket,
112
172
  entity: req.body.entity,
113
173
  role: req.body.role,
114
174
  etag: "someEtag",
115
- generation: md.generation.toString(),
116
- })
117
- .status(200);
175
+ generation: metadata.generation.toString(),
176
+ });
118
177
  });
119
- gcloudStorageAPI.post("/upload/storage/v1/b/:bucketId/o", (req, res) => {
178
+ gcloudStorageAPI.post("/upload/storage/v1/b/:bucketId/o", async (req, res) => {
120
179
  if (!req.query.name) {
121
180
  res.sendStatus(400);
122
181
  return;
@@ -125,69 +184,112 @@ function createCloudEndpoints(emulator) {
125
184
  if (name.startsWith("/")) {
126
185
  name = name.slice(1);
127
186
  }
128
- const contentType = req.header("content-type") || req.header("x-upload-content-type");
129
- if (!contentType) {
130
- res.sendStatus(400);
131
- return;
187
+ const contentTypeHeader = req.header("content-type") || req.header("x-upload-content-type");
188
+ if (!contentTypeHeader) {
189
+ return res.sendStatus(400);
132
190
  }
133
- if (req.query.uploadType == "resumable") {
134
- const upload = storageLayer.startUpload(req.params.bucketId, name, contentType, req.body);
191
+ if (req.query.uploadType === "resumable") {
135
192
  const emulatorInfo = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE);
136
- if (emulatorInfo == undefined) {
137
- res.sendStatus(500);
138
- return;
193
+ if (emulatorInfo === undefined) {
194
+ return res.sendStatus(500);
139
195
  }
196
+ const upload = uploadService.startResumableUpload({
197
+ bucketId: req.params.bucketId,
198
+ objectId: name,
199
+ metadataRaw: JSON.stringify(req.body),
200
+ authorization: req.header("authorization"),
201
+ });
140
202
  const { host, port } = emulatorInfo;
141
- const uploadUrl = `http://${host}:${port}/upload/storage/v1/b/${upload.bucketId}/o?name=${upload.fileLocation}&uploadType=resumable&upload_id=${upload.uploadId}`;
142
- res.header("location", uploadUrl).status(200).send();
143
- return;
203
+ const uploadUrl = `http://${host}:${port}/upload/storage/v1/b/${req.params.bucketId}/o?name=${name}&uploadType=resumable&upload_id=${upload.id}`;
204
+ return res.header("location", uploadUrl).sendStatus(200);
144
205
  }
145
- if (!contentType.startsWith("multipart/related")) {
146
- res.sendStatus(400);
147
- return;
206
+ let metadataRaw;
207
+ let dataRaw;
208
+ try {
209
+ ({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await reqBodyToBuffer(req)));
148
210
  }
149
- const boundary = `--${contentType.split("boundary=")[1]}`;
150
- const bodyString = req.body.toString();
151
- const bodyStringParts = bodyString.split(boundary).filter((v) => v);
152
- const metadataString = bodyStringParts[0].split(/\r?\n/)[3];
153
- const blobParts = bodyStringParts[1].split(/\r?\n/);
154
- const blobContentTypeString = blobParts[1];
155
- if (!blobContentTypeString || !blobContentTypeString.startsWith("Content-Type: ")) {
156
- res.sendStatus(400);
157
- return;
211
+ catch (err) {
212
+ return res.status(400).json({
213
+ error: {
214
+ code: 400,
215
+ message: err,
216
+ },
217
+ });
158
218
  }
159
- const blobContentType = blobContentTypeString.slice("Content-Type: ".length);
160
- const bodyBuffer = req.body;
161
- const metadataSegment = `${boundary}${bodyString.split(boundary)[1]}`;
162
- const dataSegment = `${boundary}${bodyString.split(boundary).slice(2)[0]}`;
163
- const dataSegmentHeader = (dataSegment.match(/.+Content-Type:.+?\r?\n\r?\n/s) || [])[0];
164
- if (!dataSegmentHeader) {
165
- res.sendStatus(400);
166
- return;
219
+ const upload = uploadService.multipartUpload({
220
+ bucketId: req.params.bucketId,
221
+ objectId: name,
222
+ metadataRaw: metadataRaw,
223
+ dataRaw: dataRaw,
224
+ authorization: req.header("authorization"),
225
+ });
226
+ let metadata;
227
+ try {
228
+ metadata = await storageLayer.handleUploadObject(upload, true);
167
229
  }
168
- const bufferOffset = metadataSegment.length + dataSegmentHeader.length;
169
- const blobBytes = Buffer.from(bodyBuffer.slice(bufferOffset, -`\r\n${boundary}--`.length));
170
- const metadata = storageLayer.oneShotUpload(req.params.bucketId, name, blobContentType, JSON.parse(metadataString), blobBytes);
171
- if (!metadata) {
172
- res.sendStatus(400);
173
- return;
230
+ catch (err) {
231
+ if (err instanceof errors_1.ForbiddenError) {
232
+ throw new Error("Request failed unexpectedly due to Firebase Rules.");
233
+ }
234
+ throw err;
174
235
  }
175
- res.status(200).json(new metadata_1.CloudStorageObjectMetadata(metadata)).send();
176
- return;
236
+ return res.status(200).json(new metadata_1.CloudStorageObjectMetadata(metadata));
177
237
  });
178
- gcloudStorageAPI.get("/:bucketId/:objectId(**)", (req, res) => {
238
+ gcloudStorageAPI.get("/:bucketId/:objectId(**)", async (req, res) => {
239
+ let getObjectResponse;
240
+ try {
241
+ getObjectResponse = await storageLayer.handleGetObject({
242
+ bucketId: req.params.bucketId,
243
+ decodedObjectId: req.params.objectId,
244
+ }, true);
245
+ }
246
+ catch (err) {
247
+ if (err instanceof errors_1.NotFoundError) {
248
+ return sendObjectNotFound(req, res);
249
+ }
250
+ if (err instanceof errors_1.ForbiddenError) {
251
+ throw new Error("Request failed unexpectedly due to Firebase Rules.");
252
+ }
253
+ throw err;
254
+ }
255
+ return sendFileBytes(getObjectResponse.metadata, getObjectResponse.data, req, res);
256
+ });
257
+ gcloudStorageAPI.post("/b/:bucketId/o/:objectId/:method(rewriteTo|copyTo)/b/:destBucketId/o/:destObjectId", (req, res, next) => {
179
258
  const md = storageLayer.getMetadata(req.params.bucketId, req.params.objectId);
180
259
  if (!md) {
181
- res.sendStatus(404);
260
+ return sendObjectNotFound(req, res);
261
+ }
262
+ if (req.params.method === "rewriteTo" && req.query.rewriteToken) {
263
+ return next();
264
+ }
265
+ const metadata = storageLayer.copyFile(md, req.params.destBucketId, req.params.destObjectId, req.body);
266
+ if (!metadata) {
267
+ res.sendStatus(400);
182
268
  return;
183
269
  }
184
- return sendFileBytes(md, storageLayer, req, res);
270
+ const resource = new metadata_1.CloudStorageObjectMetadata(metadata);
271
+ res.status(200);
272
+ if (req.params.method === "copyTo") {
273
+ return res.json(resource);
274
+ }
275
+ else if (req.params.method === "rewriteTo") {
276
+ return res.json({
277
+ kind: "storage#rewriteResponse",
278
+ totalBytesRewritten: String(metadata.size),
279
+ objectSize: String(metadata.size),
280
+ done: true,
281
+ resource,
282
+ });
283
+ }
284
+ else {
285
+ return next();
286
+ }
185
287
  });
186
288
  gcloudStorageAPI.all("/**", (req, res) => {
187
289
  if (process.env.STORAGE_EMULATOR_DEBUG) {
188
290
  console.table(req.headers);
189
291
  console.log(req.method, req.url);
190
- res.json("endpoint not implemented");
292
+ res.status(501).json("endpoint not implemented");
191
293
  }
192
294
  else {
193
295
  res.sendStatus(501);
@@ -196,32 +298,50 @@ function createCloudEndpoints(emulator) {
196
298
  return gcloudStorageAPI;
197
299
  }
198
300
  exports.createCloudEndpoints = createCloudEndpoints;
199
- function sendFileBytes(md, storageLayer, req, res) {
200
- let data = storageLayer.getBytes(req.params.bucketId, req.params.objectId);
201
- if (!data) {
202
- res.sendStatus(404);
203
- return;
204
- }
205
- const isGZipped = md.contentEncoding == "gzip";
301
+ function sendFileBytes(md, data, req, res) {
302
+ const isGZipped = md.contentEncoding === "gzip";
206
303
  if (isGZipped) {
207
304
  data = (0, zlib_1.gunzipSync)(data);
208
305
  }
209
306
  res.setHeader("Accept-Ranges", "bytes");
210
307
  res.setHeader("Content-Type", md.contentType);
211
308
  res.setHeader("Content-Disposition", md.contentDisposition);
212
- res.setHeader("Content-Encoding", "identity");
213
- const byteRange = [...(req.header("range") || "").split("bytes="), "", ""];
214
- const [rangeStart, rangeEnd] = byteRange[1].split("-");
215
- if (rangeStart) {
216
- const range = {
217
- start: parseInt(rangeStart),
218
- end: rangeEnd ? parseInt(rangeEnd) : data.byteLength,
219
- };
220
- res.setHeader("Content-Range", `bytes ${range.start}-${range.end - 1}/${data.byteLength}`);
221
- res.status(206).end(data.slice(range.start, range.end));
309
+ res.setHeader("Content-Encoding", md.contentEncoding);
310
+ res.setHeader("ETag", md.etag);
311
+ res.setHeader("Cache-Control", md.cacheControl);
312
+ res.setHeader("x-goog-generation", `${md.generation}`);
313
+ res.setHeader("x-goog-metadatageneration", `${md.metageneration}`);
314
+ res.setHeader("x-goog-storage-class", md.storageClass);
315
+ res.setHeader("x-goog-hash", `crc32c=${(0, crc_1.crc32cToString)(md.crc32c)},md5=${md.md5Hash}`);
316
+ const byteRange = req.range(data.byteLength, { combine: true });
317
+ if (Array.isArray(byteRange) && byteRange.type === "bytes" && byteRange.length > 0) {
318
+ const range = byteRange[0];
319
+ res.setHeader("Content-Range", `${byteRange.type} ${range.start}-${range.end}/${data.byteLength}`);
320
+ res.status(206).end(data.slice(range.start, range.end + 1));
222
321
  }
223
322
  else {
224
323
  res.end(data);
225
324
  }
226
- return;
325
+ }
326
+ function sendObjectNotFound(req, res) {
327
+ res.status(404);
328
+ const message = `No such object: ${req.params.bucketId}/${req.params.objectId}`;
329
+ if (req.method === "GET" && req.query.alt === "media") {
330
+ res.send(message);
331
+ }
332
+ else {
333
+ res.json({
334
+ error: {
335
+ code: 404,
336
+ message,
337
+ errors: [
338
+ {
339
+ message,
340
+ domain: "global",
341
+ reason: "notFound",
342
+ },
343
+ ],
344
+ },
345
+ });
346
+ }
227
347
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.crc32c = void 0;
3
+ exports.crc32cToString = exports.crc32c = void 0;
4
4
  function makeCRCTable(poly) {
5
5
  let c;
6
6
  const crcTable = [];
@@ -24,3 +24,7 @@ function crc32c(bytes) {
24
24
  return (crc ^ -1) >>> 0;
25
25
  }
26
26
  exports.crc32c = crc32c;
27
+ function crc32cToString(crc32cValue) {
28
+ return "----" + Buffer.from([crc32cValue]).toString("base64");
29
+ }
30
+ exports.crc32cToString = crc32cToString;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ForbiddenError = exports.NotFoundError = void 0;
4
+ class NotFoundError extends Error {
5
+ }
6
+ exports.NotFoundError = NotFoundError;
7
+ class ForbiddenError extends Error {
8
+ }
9
+ exports.ForbiddenError = ForbiddenError;