firebase-tools 11.6.0 → 11.8.1

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 (52) hide show
  1. package/lib/auth.js +1 -1
  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/functions-delete.js +2 -0
  6. package/lib/commands/functions-secrets-get.js +2 -0
  7. package/lib/commands/index.js +3 -0
  8. package/lib/crashlytics/buildToolsJarHelper.js +51 -0
  9. package/lib/deploy/functions/backend.js +4 -4
  10. package/lib/deploy/functions/build.js +98 -17
  11. package/lib/deploy/functions/cache/applyHash.js +29 -0
  12. package/lib/deploy/functions/cache/hash.js +30 -0
  13. package/lib/deploy/functions/cel.js +249 -0
  14. package/lib/deploy/functions/checkIam.js +6 -5
  15. package/lib/deploy/functions/functionsDeployHelper.js +12 -1
  16. package/lib/deploy/functions/params.js +262 -105
  17. package/lib/deploy/functions/prepare.js +34 -4
  18. package/lib/deploy/functions/prepareFunctionsUpload.js +12 -4
  19. package/lib/deploy/functions/release/fabricator.js +39 -6
  20. package/lib/deploy/functions/release/index.js +2 -0
  21. package/lib/deploy/functions/release/planner.js +17 -0
  22. package/lib/deploy/functions/runtimes/discovery/index.js +1 -16
  23. package/lib/deploy/functions/runtimes/discovery/parsing.js +16 -0
  24. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +59 -131
  25. package/lib/deploy/functions/runtimes/node/parseTriggers.js +10 -1
  26. package/lib/emulator/constants.js +1 -1
  27. package/lib/emulator/controller.js +6 -11
  28. package/lib/emulator/extensionsEmulator.js +1 -0
  29. package/lib/emulator/functionsEmulator.js +18 -59
  30. package/lib/emulator/functionsEmulatorRuntime.js +12 -23
  31. package/lib/emulator/functionsRuntimeWorker.js +38 -7
  32. package/lib/emulator/storage/apis/firebase.js +145 -129
  33. package/lib/emulator/storage/apis/gcloud.js +102 -42
  34. package/lib/emulator/storage/files.js +25 -15
  35. package/lib/emulator/storage/metadata.js +86 -56
  36. package/lib/emulator/storage/multipart.js +2 -2
  37. package/lib/emulator/storage/rules/runtime.js +10 -2
  38. package/lib/emulator/storage/upload.js +45 -9
  39. package/lib/extensions/extensionsHelper.js +1 -1
  40. package/lib/functions/constants.js +14 -0
  41. package/lib/functions/env.js +9 -9
  42. package/lib/functions/secrets.js +8 -1
  43. package/lib/gcp/cloudfunctions.js +15 -18
  44. package/lib/gcp/cloudfunctionsv2.js +15 -18
  45. package/lib/gcp/cloudscheduler.js +32 -14
  46. package/lib/gcp/secretManager.js +15 -1
  47. package/lib/gcp/storage.js +15 -1
  48. package/lib/previews.js +1 -1
  49. package/lib/track.js +3 -0
  50. package/npm-shrinkwrap.json +563 -30
  51. package/package.json +7 -5
  52. package/templates/init/storage/storage.rules +1 -1
@@ -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);
@@ -142,20 +142,26 @@ class StorageLayer {
142
142
  }
143
143
  const storedMetadata = this.getMetadata(upload.bucketId, upload.objectId);
144
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
+ }
145
152
  const metadata = new metadata_1.StoredFileMetadata({
146
153
  name: upload.objectId,
147
154
  bucket: upload.bucketId,
148
- contentType: upload.metadata.contentType || "application/octet-stream",
149
- contentDisposition: upload.metadata.contentDisposition,
150
- contentEncoding: upload.metadata.contentEncoding,
151
- contentLanguage: upload.metadata.contentLanguage,
152
- cacheControl: upload.metadata.cacheControl,
153
- 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"),
154
161
  }, this._cloudFunctions, this._persistence.readBytes(upload.path, upload.size));
155
- metadata.update(upload.metadata, false);
156
162
  const authorized = await this._rulesValidator.validate(["b", upload.bucketId, "o", upload.objectId].join("/"), upload.bucketId, types_1.RulesetOperationMethod.CREATE, {
157
163
  before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource(),
158
- after: metadata === null || metadata === void 0 ? void 0 : metadata.asRulesResource(),
164
+ after: metadata.asRulesResource(),
159
165
  }, upload.authorization);
160
166
  if (!authorized) {
161
167
  this._persistence.deleteFile(upload.path);
@@ -192,16 +198,20 @@ class StorageLayer {
192
198
  newMetadata.metadata[k] = "";
193
199
  }
194
200
  }
201
+ function getMetadata(field) {
202
+ const value = newMetadata[field];
203
+ return value === null ? undefined : value;
204
+ }
195
205
  const copiedFileMetadata = new metadata_1.StoredFileMetadata({
196
206
  name: destinationObject,
197
207
  bucket: destinationBucket,
198
- contentType: newMetadata.contentType || "application/octet-stream",
199
- contentDisposition: newMetadata.contentDisposition,
200
- contentEncoding: newMetadata.contentEncoding,
201
- contentLanguage: newMetadata.contentLanguage,
202
- cacheControl: newMetadata.cacheControl,
203
- customMetadata: newMetadata.metadata,
204
- }, 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);
205
215
  const file = new StoredFile(copiedFileMetadata);
206
216
  this._files.set(destinationFilePath, file);
207
217
  this._cloudFunctions.dispatch("finalize", new metadata_1.CloudStorageObjectMetadata(file.metadata));
@@ -7,20 +7,19 @@ const registry_1 = require("../registry");
7
7
  const types_1 = require("../types");
8
8
  const crc_1 = require("./crc");
9
9
  class StoredFileMetadata {
10
- constructor(opts, _cloudFunctions, bytes, incomingMetadata) {
10
+ constructor(opts, _cloudFunctions, bytes) {
11
11
  this._cloudFunctions = _cloudFunctions;
12
12
  this.name = opts.name;
13
13
  this.bucket = opts.bucket;
14
- this.contentType = opts.contentType;
15
14
  this.metageneration = opts.metageneration || 1;
16
15
  this.generation = opts.generation || Date.now();
16
+ this.contentType = opts.contentType || "application/octet-stream";
17
17
  this.storageClass = opts.storageClass || "STANDARD";
18
- this.contentDisposition = opts.contentDisposition || "inline";
19
- this.cacheControl = opts.cacheControl || "public, max-age=3600";
18
+ this.contentDisposition = opts.contentDisposition;
19
+ this.cacheControl = opts.cacheControl;
20
20
  this.contentLanguage = opts.contentLanguage;
21
21
  this.customTime = opts.customTime;
22
- this.contentEncoding = opts.contentEncoding || "identity";
23
- this.customMetadata = opts.customMetadata;
22
+ this.contentEncoding = opts.contentEncoding;
24
23
  this.downloadTokens = opts.downloadTokens || [];
25
24
  if (opts.etag) {
26
25
  this.etag = opts.etag;
@@ -28,6 +27,16 @@ class StoredFileMetadata {
28
27
  else {
29
28
  this.etag = generateETag(this.generation, this.metageneration);
30
29
  }
30
+ if (opts.customMetadata) {
31
+ this.customMetadata = {};
32
+ for (const [k, v] of Object.entries(opts.customMetadata)) {
33
+ let stringVal = v;
34
+ if (typeof stringVal !== "string") {
35
+ stringVal = JSON.stringify(v);
36
+ }
37
+ this.customMetadata[k] = stringVal || "";
38
+ }
39
+ }
31
40
  this.timeCreated = opts.timeCreated ? new Date(opts.timeCreated) : new Date();
32
41
  this.updated = opts.updated ? new Date(opts.updated) : this.timeCreated;
33
42
  if (bytes) {
@@ -43,42 +52,54 @@ class StoredFileMetadata {
43
52
  else {
44
53
  throw new Error("Must pass bytes array or opts object with size, md5hash, and crc32c");
45
54
  }
46
- if (incomingMetadata) {
47
- this.update(incomingMetadata, false);
48
- }
49
55
  this.deleteFieldsSetAsNull();
50
56
  this.setDownloadTokensFromCustomMetadata();
51
57
  }
52
- asRulesResource(proposedChanges) {
53
- let rulesResource = {
58
+ clone() {
59
+ const clone = new StoredFileMetadata({
54
60
  name: this.name,
55
61
  bucket: this.bucket,
56
62
  generation: this.generation,
57
63
  metageneration: this.metageneration,
64
+ contentType: this.contentType,
65
+ storageClass: this.storageClass,
58
66
  size: this.size,
59
- timeCreated: this.timeCreated,
60
- updated: this.updated,
61
67
  md5Hash: this.md5Hash,
68
+ contentEncoding: this.contentEncoding,
69
+ contentDisposition: this.contentDisposition,
70
+ contentLanguage: this.contentLanguage,
71
+ cacheControl: this.cacheControl,
72
+ customTime: this.customTime,
62
73
  crc32c: this.crc32c,
63
74
  etag: this.etag,
64
- contentDisposition: this.contentDisposition,
65
- contentEncoding: this.contentEncoding,
66
- contentType: this.contentType,
67
- metadata: this.customMetadata || {},
68
- };
75
+ downloadTokens: this.downloadTokens,
76
+ customMetadata: this.customMetadata,
77
+ }, this._cloudFunctions);
78
+ clone.timeCreated = this.timeCreated;
79
+ clone.updated = this.updated;
80
+ return clone;
81
+ }
82
+ asRulesResource(proposedChanges) {
83
+ const proposedMetadata = this.clone();
69
84
  if (proposedChanges) {
70
- if (proposedChanges.md5Hash !== rulesResource.md5Hash) {
71
- rulesResource.generation = Date.now();
72
- rulesResource.metageneration = 1;
73
- rulesResource.timeCreated = new Date();
74
- rulesResource.updated = rulesResource.timeCreated;
75
- }
76
- else {
77
- rulesResource.metageneration++;
78
- }
79
- rulesResource = Object.assign(Object.assign({}, rulesResource), proposedChanges);
85
+ proposedMetadata.update(proposedChanges, false);
80
86
  }
81
- return rulesResource;
87
+ return {
88
+ name: proposedMetadata.name,
89
+ bucket: proposedMetadata.bucket,
90
+ generation: proposedMetadata.generation,
91
+ metageneration: proposedMetadata.metageneration,
92
+ size: proposedMetadata.size,
93
+ timeCreated: proposedMetadata.timeCreated,
94
+ updated: proposedMetadata.updated,
95
+ md5Hash: proposedMetadata.md5Hash,
96
+ crc32c: proposedMetadata.crc32c,
97
+ etag: proposedMetadata.etag,
98
+ contentDisposition: proposedMetadata.contentDisposition,
99
+ contentEncoding: proposedMetadata.contentEncoding,
100
+ contentType: proposedMetadata.contentType,
101
+ metadata: proposedMetadata.customMetadata || {},
102
+ };
82
103
  }
83
104
  setDownloadTokensFromCustomMetadata() {
84
105
  if (!this.customMetadata) {
@@ -118,43 +139,52 @@ class StoredFileMetadata {
118
139
  }
119
140
  }
120
141
  update(incoming, shouldTrigger = true) {
121
- if (incoming.contentDisposition) {
122
- this.contentDisposition = incoming.contentDisposition;
142
+ if (incoming.contentDisposition !== undefined) {
143
+ this.contentDisposition =
144
+ incoming.contentDisposition === null ? undefined : incoming.contentDisposition;
123
145
  }
124
- if (incoming.contentType) {
125
- this.contentType = incoming.contentType;
146
+ if (incoming.contentType !== undefined) {
147
+ this.contentType = incoming.contentType === null ? undefined : incoming.contentType;
126
148
  }
127
- if (incoming.metadata) {
128
- this.customMetadata = this.customMetadata ? Object.assign({}, this.customMetadata) : {};
129
- for (const [k, v] of Object.entries(incoming.metadata)) {
130
- this.customMetadata[k] = v === null ? null : String(v);
131
- }
149
+ if (incoming.contentLanguage !== undefined) {
150
+ this.contentLanguage =
151
+ incoming.contentLanguage === null ? undefined : incoming.contentLanguage;
132
152
  }
133
- if (incoming.contentLanguage) {
134
- this.contentLanguage = incoming.contentLanguage;
153
+ if (incoming.contentEncoding !== undefined) {
154
+ this.contentEncoding =
155
+ incoming.contentEncoding === null ? undefined : incoming.contentEncoding;
135
156
  }
136
- if (incoming.contentEncoding) {
137
- this.contentEncoding = incoming.contentEncoding;
157
+ if (incoming.cacheControl !== undefined) {
158
+ this.cacheControl = incoming.cacheControl === null ? undefined : incoming.cacheControl;
138
159
  }
139
- if (this.generation) {
140
- this.generation++;
160
+ if (incoming.metadata !== undefined) {
161
+ if (incoming.metadata === null) {
162
+ this.customMetadata = undefined;
163
+ }
164
+ else {
165
+ this.customMetadata = this.customMetadata || {};
166
+ for (const [k, v] of Object.entries(incoming.metadata)) {
167
+ if (v === null) {
168
+ delete this.customMetadata[k];
169
+ }
170
+ else {
171
+ this.customMetadata[k] = String(v);
172
+ }
173
+ }
174
+ if (Object.keys(this.customMetadata).length === 0) {
175
+ this.customMetadata = undefined;
176
+ }
177
+ }
141
178
  }
179
+ this.metageneration++;
142
180
  this.updated = new Date();
143
- if (incoming.cacheControl) {
144
- this.cacheControl = incoming.cacheControl;
145
- }
146
181
  this.setDownloadTokensFromCustomMetadata();
147
- this.deleteFieldsSetAsNull();
148
182
  if (shouldTrigger) {
149
183
  this._cloudFunctions.dispatch("metadataUpdate", new CloudStorageObjectMetadata(this));
150
184
  }
151
185
  }
152
186
  addDownloadToken(shouldTrigger = true) {
153
- if (!this.downloadTokens.length) {
154
- this.downloadTokens.push(uuid.v4());
155
- return;
156
- }
157
- this.downloadTokens = [...this.downloadTokens, uuid.v4()];
187
+ this.downloadTokens = [...(this.downloadTokens || []), uuid.v4()];
158
188
  this.update({}, shouldTrigger);
159
189
  }
160
190
  deleteDownloadToken(token) {
@@ -197,7 +227,7 @@ class OutgoingFirebaseMetadata {
197
227
  this.crc32c = metadata.crc32c;
198
228
  this.etag = metadata.etag;
199
229
  this.downloadTokens = metadata.downloadTokens.join(",");
200
- this.contentEncoding = metadata.contentEncoding;
230
+ this.contentEncoding = metadata.contentEncoding || "identity";
201
231
  this.contentDisposition = metadata.contentDisposition;
202
232
  this.metadata = metadata.customMetadata;
203
233
  this.contentLanguage = metadata.contentLanguage;
@@ -208,7 +238,7 @@ exports.OutgoingFirebaseMetadata = OutgoingFirebaseMetadata;
208
238
  class CloudStorageBucketMetadata {
209
239
  constructor(id) {
210
240
  var _a, _b;
211
- this.kind = "#storage/bucket";
241
+ this.kind = "storage#bucket";
212
242
  this.name = id;
213
243
  this.id = id;
214
244
  this.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}/v1/b/${this.id}`;
@@ -240,7 +270,7 @@ exports.CloudStorageObjectAccessControlMetadata = CloudStorageObjectAccessContro
240
270
  class CloudStorageObjectMetadata {
241
271
  constructor(metadata) {
242
272
  var _a, _b, _c, _d;
243
- this.kind = "#storage#object";
273
+ this.kind = "storage#object";
244
274
  this.name = metadata.name;
245
275
  this.bucket = metadata.bucket;
246
276
  this.generation = metadata.generation.toString();
@@ -45,11 +45,11 @@ function parseMultipartRequestBodyPart(bodyPart) {
45
45
  }
46
46
  function parseObjectUploadMultipartRequest(contentTypeHeader, body) {
47
47
  if (!contentTypeHeader.startsWith("multipart/related")) {
48
- throw new Error(`Invalid Content-Type: ${contentTypeHeader}`);
48
+ throw new Error(`Bad content type. ${contentTypeHeader}`);
49
49
  }
50
50
  const boundaryId = contentTypeHeader.split("boundary=")[1];
51
51
  if (!boundaryId) {
52
- throw new Error(`Invalid Content-Type header: ${contentTypeHeader}`);
52
+ throw new Error(`Bad content type. ${contentTypeHeader}`);
53
53
  }
54
54
  const parsedBody = parseMultipartRequestBody(boundaryId, body);
55
55
  if (parsedBody.length !== 2) {
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.StorageRulesRuntime = exports.StorageRulesIssues = exports.StorageRulesetInstance = void 0;
4
4
  const cross_spawn_1 = require("cross-spawn");
5
5
  const error_1 = require("../../../error");
6
+ const AsyncLock = require("async-lock");
6
7
  const types_1 = require("./types");
7
8
  const jwt = require("jsonwebtoken");
8
9
  const emulatorLogger_1 = require("../../emulatorLogger");
@@ -13,6 +14,8 @@ const constants_1 = require("../../constants");
13
14
  const download_1 = require("../../download");
14
15
  const fs = require("fs-extra");
15
16
  const downloadableEmulators_1 = require("../../downloadableEmulators");
17
+ const lock = new AsyncLock();
18
+ const synchonizationKey = "key";
16
19
  class StorageRulesetInstance {
17
20
  constructor(runtime, rulesVersion, rulesetName) {
18
21
  this.runtime = runtime;
@@ -154,13 +157,18 @@ class StorageRulesRuntime {
154
157
  throw new error_1.FirebaseError("Attempted to send Cloud Storage rules request with stale id");
155
158
  }
156
159
  return new Promise((resolve) => {
157
- var _a, _b;
158
160
  this._requests[runtimeActionRequest.id] = {
159
161
  request: runtimeActionRequest,
160
162
  handler: resolve,
161
163
  };
162
164
  const serializedRequest = JSON.stringify(runtimeActionRequest);
163
- (_b = (_a = this._childprocess) === null || _a === void 0 ? void 0 : _a.stdin) === null || _b === void 0 ? void 0 : _b.write(serializedRequest + "\n");
165
+ lock.acquire(synchonizationKey, (done) => {
166
+ var _a, _b;
167
+ (_b = (_a = this._childprocess) === null || _a === void 0 ? void 0 : _a.stdin) === null || _b === void 0 ? void 0 : _b.write(serializedRequest + "\n");
168
+ setTimeout(() => {
169
+ done();
170
+ }, 15);
171
+ });
164
172
  });
165
173
  }
166
174
  async loadRuleset(source) {
@@ -1,12 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.UploadService = exports.NotCancellableError = exports.UploadNotActiveError = exports.UploadStatus = exports.UploadType = void 0;
3
+ exports.UploadService = exports.NotCancellableError = exports.UploadPreviouslyFinalizedError = exports.UploadNotActiveError = exports.UploadStatus = exports.UploadType = void 0;
4
4
  const uuid_1 = require("uuid");
5
5
  const errors_1 = require("./errors");
6
6
  var UploadType;
7
7
  (function (UploadType) {
8
- UploadType[UploadType["MULTIPART"] = 0] = "MULTIPART";
9
- UploadType[UploadType["RESUMABLE"] = 1] = "RESUMABLE";
8
+ UploadType[UploadType["MEDIA"] = 0] = "MEDIA";
9
+ UploadType[UploadType["MULTIPART"] = 1] = "MULTIPART";
10
+ UploadType[UploadType["RESUMABLE"] = 2] = "RESUMABLE";
10
11
  })(UploadType = exports.UploadType || (exports.UploadType = {}));
11
12
  var UploadStatus;
12
13
  (function (UploadStatus) {
@@ -17,6 +18,9 @@ var UploadStatus;
17
18
  class UploadNotActiveError extends Error {
18
19
  }
19
20
  exports.UploadNotActiveError = UploadNotActiveError;
21
+ class UploadPreviouslyFinalizedError extends Error {
22
+ }
23
+ exports.UploadPreviouslyFinalizedError = UploadPreviouslyFinalizedError;
20
24
  class NotCancellableError extends Error {
21
25
  }
22
26
  exports.NotCancellableError = NotCancellableError;
@@ -28,23 +32,42 @@ class UploadService {
28
32
  reset() {
29
33
  this._uploads = new Map();
30
34
  }
35
+ mediaUpload(request) {
36
+ const upload = this.startOneShotUpload({
37
+ bucketId: request.bucketId,
38
+ objectId: request.objectId,
39
+ uploadType: UploadType.MEDIA,
40
+ dataRaw: request.dataRaw,
41
+ authorization: request.authorization,
42
+ });
43
+ this._persistence.deleteFile(upload.path, true);
44
+ this._persistence.appendBytes(upload.path, request.dataRaw);
45
+ return upload;
46
+ }
31
47
  multipartUpload(request) {
32
- const upload = this.startMultipartUpload(request, request.dataRaw.byteLength);
48
+ const upload = this.startOneShotUpload({
49
+ bucketId: request.bucketId,
50
+ objectId: request.objectId,
51
+ uploadType: UploadType.MULTIPART,
52
+ dataRaw: request.dataRaw,
53
+ metadata: JSON.parse(request.metadataRaw),
54
+ authorization: request.authorization,
55
+ });
33
56
  this._persistence.deleteFile(upload.path, true);
34
57
  this._persistence.appendBytes(upload.path, request.dataRaw);
35
58
  return upload;
36
59
  }
37
- startMultipartUpload(request, sizeInBytes) {
60
+ startOneShotUpload(request) {
38
61
  const id = (0, uuid_1.v4)();
39
62
  const upload = {
40
- id: (0, uuid_1.v4)(),
63
+ id,
41
64
  bucketId: request.bucketId,
42
65
  objectId: request.objectId,
43
- type: UploadType.MULTIPART,
66
+ type: request.uploadType,
44
67
  path: this.getStagingFileName(id, request.bucketId, request.objectId),
45
68
  status: UploadStatus.FINISHED,
46
- metadata: JSON.parse(request.metadataRaw),
47
- size: sizeInBytes,
69
+ metadata: request.metadata,
70
+ size: request.dataRaw.byteLength,
48
71
  authorization: request.authorization,
49
72
  };
50
73
  this._uploads.set(upload.id, upload);
@@ -94,12 +117,25 @@ class UploadService {
94
117
  }
95
118
  finalizeResumableUpload(uploadId) {
96
119
  const upload = this.getResumableUpload(uploadId);
120
+ if (upload.status === UploadStatus.FINISHED) {
121
+ throw new UploadPreviouslyFinalizedError();
122
+ }
97
123
  if (upload.status === UploadStatus.CANCELLED) {
98
124
  throw new UploadNotActiveError();
99
125
  }
100
126
  upload.status = UploadStatus.FINISHED;
101
127
  return upload;
102
128
  }
129
+ setResponseCode(uploadId, code) {
130
+ const upload = this._uploads.get(uploadId);
131
+ if (upload) {
132
+ upload.prevResponseCode = code;
133
+ }
134
+ }
135
+ getPreviousResponseCode(uploadId) {
136
+ var _a;
137
+ return ((_a = this._uploads.get(uploadId)) === null || _a === void 0 ? void 0 : _a.prevResponseCode) || 200;
138
+ }
103
139
  getStagingFileName(uploadId, bucketId, objectId) {
104
140
  return encodeURIComponent(`${uploadId}_b_${bucketId}_o_${objectId}`);
105
141
  }
@@ -350,7 +350,7 @@ async function publishExtensionVersionFromLocalSource(args) {
350
350
  if (extension &&
351
351
  extension.latestVersion &&
352
352
  semver.lt(extensionSpec.version, extension.latestVersion)) {
353
- throw new error_1.FirebaseError(`The version you are trying to publish (${clc.bold(extensionSpec.version)}) is lower than the current version (${clc.bold(extension.latestVersion)}) for the extension '${clc.bold(`${args.publisherId}/${args.extensionId}`)}'. Please make sure this version is greater than the current version (${clc.bold(extension.latestVersion)}) inside of extension.yaml.\n`);
353
+ throw new error_1.FirebaseError(`The version you are trying to publish (${clc.bold(extensionSpec.version)}) is lower than the current version (${clc.bold(extension.latestVersion)}) for the extension '${clc.bold(`${args.publisherId}/${args.extensionId}`)}'. Please make sure this version is greater than the current version (${clc.bold(extension.latestVersion)}) inside of extension.yaml.\n`, { exit: 104 });
354
354
  }
355
355
  else if (extension &&
356
356
  extension.latestVersion &&
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BLOCKING_EVENT_TO_LABEL_KEY = exports.BLOCKING_LABEL_KEY_TO_EVENT = exports.BLOCKING_LABEL = exports.HASH_LABEL = exports.CODEBASE_LABEL = void 0;
4
+ exports.CODEBASE_LABEL = "firebase-functions-codebase";
5
+ exports.HASH_LABEL = "firebase-functions-hash";
6
+ exports.BLOCKING_LABEL = "deployment-blocking";
7
+ exports.BLOCKING_LABEL_KEY_TO_EVENT = {
8
+ "before-create": "providers/cloud.auth/eventTypes/user.beforeCreate",
9
+ "before-sign-in": "providers/cloud.auth/eventTypes/user.beforeSignIn",
10
+ };
11
+ exports.BLOCKING_EVENT_TO_LABEL_KEY = {
12
+ "providers/cloud.auth/eventTypes/user.beforeCreate": "before-create",
13
+ "providers/cloud.auth/eventTypes/user.beforeSignIn": "before-sign-in",
14
+ };