firebase-tools 11.16.0 → 11.17.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.
@@ -1126,7 +1126,8 @@ async function signInWithIdp(state, reqBody) {
1126
1126
  oauthExpiresIn: coercePrimitiveToString(response.oauthExpireIn),
1127
1127
  };
1128
1128
  if (response.isNewUser) {
1129
- let updates = Object.assign(Object.assign({}, accountUpdates.fields), { lastLoginAt: Date.now().toString(), providerUserInfo: [providerUserInfo], tenantId: state instanceof state_1.TenantProjectState ? state.tenantId : undefined });
1129
+ const timestamp = new Date();
1130
+ let updates = Object.assign(Object.assign({}, accountUpdates.fields), { createdAt: timestamp.getTime().toString(), lastLoginAt: timestamp.getTime().toString(), providerUserInfo: [providerUserInfo], tenantId: state instanceof state_1.TenantProjectState ? state.tenantId : undefined });
1130
1131
  const localId = state.generateLocalId();
1131
1132
  const userBeforeCreate = Object.assign({ localId }, updates);
1132
1133
  const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_CREATE, userBeforeCreate, {
@@ -1187,7 +1188,8 @@ async function signInWithIdp(state, reqBody) {
1187
1188
  async function signInWithPassword(state, reqBody) {
1188
1189
  (0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
1189
1190
  (0, errors_1.assert)(state.allowPasswordSignup, "PASSWORD_LOGIN_DISABLED");
1190
- (0, errors_1.assert)(reqBody.email, "MISSING_EMAIL");
1191
+ (0, errors_1.assert)(reqBody.email !== undefined, "MISSING_EMAIL");
1192
+ (0, errors_1.assert)((0, utils_1.isValidEmailAddress)(reqBody.email), "INVALID_EMAIL");
1191
1193
  (0, errors_1.assert)(reqBody.password, "MISSING_PASSWORD");
1192
1194
  if (reqBody.captchaResponse || reqBody.captchaChallenge) {
1193
1195
  throw new errors_1.NotImplementedError("captcha unimplemented");
@@ -97,14 +97,24 @@ async function createApp(defaultProjectId, singleProjectMode = index_1.SinglePro
97
97
  registerLegacyRoutes(app);
98
98
  (0, handlers_1.registerHandlers)(app, (apiKey, tenantId) => getProjectStateById(getProjectIdByApiKey(apiKey), tenantId));
99
99
  const apiKeyAuthenticator = (ctx, info) => {
100
- if (info.in !== "query") {
101
- throw new Error('apiKey must be defined as in: "query" in API spec.');
102
- }
103
100
  if (!info.name) {
104
- throw new Error("apiKey param name is undefined in API spec.");
101
+ throw new Error("apiKey param/header name is undefined in API spec.");
102
+ }
103
+ let key;
104
+ const req = ctx.req;
105
+ switch (info.in) {
106
+ case "header":
107
+ key = req.get(info.name);
108
+ break;
109
+ case "query": {
110
+ const q = req.query[info.name];
111
+ key = typeof q === "string" ? q : undefined;
112
+ break;
113
+ }
114
+ default:
115
+ throw new Error('apiKey must be defined as in: "query" or "header" in API spec.');
105
116
  }
106
- const key = ctx.req.query[info.name];
107
- if (typeof key === "string" && key.length > 0) {
117
+ if (key) {
108
118
  return { type: "success", user: getProjectIdByApiKey(key) };
109
119
  }
110
120
  else {
@@ -138,7 +148,8 @@ async function createApp(defaultProjectId, singleProjectMode = index_1.SinglePro
138
148
  const apis = await exegesisExpress.middleware(specForRouter(), {
139
149
  controllers: { auth: toExegesisController(operations_1.authOperations, getProjectStateById) },
140
150
  authenticators: {
141
- apiKey: apiKeyAuthenticator,
151
+ apiKeyQuery: apiKeyAuthenticator,
152
+ apiKeyHeader: apiKeyAuthenticator,
142
153
  Oauth2: oauth2Authenticator,
143
154
  },
144
155
  autoHandleHttpErrors(err) {
@@ -206,7 +217,7 @@ async function createApp(defaultProjectId, singleProjectMode = index_1.SinglePro
206
217
  postController(ctx) {
207
218
  if (ctx.res.statusCode === 401) {
208
219
  const requirements = ctx.api.operationObject.security;
209
- if (requirements === null || requirements === void 0 ? void 0 : requirements.some((req) => req.apiKey)) {
220
+ if (requirements === null || requirements === void 0 ? void 0 : requirements.some((req) => req.apiKeyQuery || req.apiKeyHeader)) {
210
221
  throw new errors_2.PermissionDeniedError("The request is missing a valid API key.");
211
222
  }
212
223
  else {
@@ -27,6 +27,7 @@ async function downloadEmulator(name) {
27
27
  if (emulator.unzipDir) {
28
28
  await unzip(emulator.downloadPath, emulator.unzipDir);
29
29
  }
30
+ await new Promise((f) => setTimeout(f, 2000));
30
31
  const executablePath = emulator.binaryPath || emulator.downloadPath;
31
32
  fs.chmodSync(executablePath, 0o755);
32
33
  removeOldFiles(name, emulator);
@@ -54,7 +55,7 @@ function unzip(zipPath, unzipDir) {
54
55
  fs.createReadStream(zipPath)
55
56
  .pipe(unzipper.Extract({ path: unzipDir }))
56
57
  .on("error", reject)
57
- .on("finish", resolve);
58
+ .on("close", resolve);
58
59
  });
59
60
  }
60
61
  function removeOldFiles(name, emulator, removeAllVersions = false) {
@@ -40,9 +40,9 @@ const EMULATOR_UPDATE_DETAILS = {
40
40
  expectedChecksum: "a4944414518be206280b495f526f18bf",
41
41
  },
42
42
  pubsub: {
43
- version: "0.1.0",
44
- expectedSize: 36623622,
45
- expectedChecksum: "81704b24737d4968734d3e175f4cde71",
43
+ version: "0.7.1",
44
+ expectedSize: 65137179,
45
+ expectedChecksum: "b59a6e705031a54a69e5e1dced7ca9bf",
46
46
  },
47
47
  };
48
48
  exports.DownloadDetails = {
@@ -8,6 +8,7 @@ const utils_1 = require("../utils");
8
8
  const emulatorLogger_1 = require("./emulatorLogger");
9
9
  const registry_1 = require("./registry");
10
10
  const error_1 = require("../error");
11
+ const eventarcEmulatorUtils_1 = require("./eventarcEmulatorUtils");
11
12
  class EventarcEmulator {
12
13
  constructor(args) {
13
14
  this.args = args;
@@ -89,7 +90,7 @@ class EventarcEmulator {
89
90
  .request({
90
91
  method: "POST",
91
92
  path: `/functions/projects/${trigger.projectId}/triggers/${trigger.triggerName}`,
92
- body: JSON.stringify(event),
93
+ body: JSON.stringify((0, eventarcEmulatorUtils_1.cloudEventFromProtoToJson)(event)),
93
94
  responseType: "stream",
94
95
  resolveOnHTTPError: true,
95
96
  })
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cloudEventFromProtoToJson = void 0;
4
+ const error_1 = require("../error");
5
+ const BUILT_IN_ATTRS = ["time", "datacontenttype", "subject"];
6
+ function cloudEventFromProtoToJson(ce) {
7
+ if (ce["id"] === undefined) {
8
+ throw new error_1.FirebaseError("CloudEvent 'id' is required.");
9
+ }
10
+ if (ce["type"] === undefined) {
11
+ throw new error_1.FirebaseError("CloudEvent 'type' is required.");
12
+ }
13
+ if (ce["specVersion"] === undefined) {
14
+ throw new error_1.FirebaseError("CloudEvent 'specVersion' is required.");
15
+ }
16
+ if (ce["source"] === undefined) {
17
+ throw new error_1.FirebaseError("CloudEvent 'source' is required.");
18
+ }
19
+ const out = {
20
+ id: ce["id"],
21
+ type: ce["type"],
22
+ specversion: ce["specVersion"],
23
+ source: ce["source"],
24
+ subject: getOptionalAttribute(ce, "subject", "ceString"),
25
+ time: getRequiredAttribute(ce, "time", "ceTimestamp"),
26
+ data: getData(ce),
27
+ datacontenttype: getRequiredAttribute(ce, "datacontenttype", "ceString"),
28
+ };
29
+ for (const attr in ce["attributes"]) {
30
+ if (BUILT_IN_ATTRS.includes(attr)) {
31
+ continue;
32
+ }
33
+ out[attr] = getRequiredAttribute(ce, attr, "ceString");
34
+ }
35
+ return out;
36
+ }
37
+ exports.cloudEventFromProtoToJson = cloudEventFromProtoToJson;
38
+ function getOptionalAttribute(ce, attr, type) {
39
+ return ce["attributes"][attr][type];
40
+ }
41
+ function getRequiredAttribute(ce, attr, type) {
42
+ const val = ce["attributes"][attr][type];
43
+ if (val === undefined) {
44
+ throw new error_1.FirebaseError("CloudEvent must contain " + attr + " attribute");
45
+ }
46
+ return val;
47
+ }
48
+ function getData(ce) {
49
+ const contentType = getRequiredAttribute(ce, "datacontenttype", "ceString");
50
+ switch (contentType) {
51
+ case "application/json":
52
+ return JSON.parse(ce["textData"]);
53
+ case "text/plain":
54
+ return ce["textData"];
55
+ case undefined:
56
+ return undefined;
57
+ default:
58
+ throw new error_1.FirebaseError("Unsupported content type: " + contentType);
59
+ }
60
+ }
@@ -44,7 +44,7 @@ class ExtensionsEmulator {
44
44
  if (!functionsEmulator) {
45
45
  throw new error_1.FirebaseError("Extensions Emulator is running but Functions emulator is not. This should never happen.");
46
46
  }
47
- return functionsEmulator.getInfo();
47
+ return Object.assign(Object.assign({}, functionsEmulator.getInfo()), { name: this.getName() });
48
48
  }
49
49
  getName() {
50
50
  return types_1.Emulators.EXTENSIONS;
@@ -137,10 +137,11 @@ class FunctionsEmulator {
137
137
  method: req.method,
138
138
  path: `/functions/projects/${projectId}/triggers/${triggerId}`,
139
139
  headers: req.headers,
140
- }, resolve);
140
+ });
141
141
  trigReq.on("error", reject);
142
142
  trigReq.write(rawBody);
143
143
  trigReq.end();
144
+ resolve();
144
145
  });
145
146
  });
146
147
  });
@@ -333,7 +334,7 @@ class FunctionsEmulator {
333
334
  added = await this.addFirestoreTrigger(this.args.projectId, key, definition.eventTrigger);
334
335
  break;
335
336
  case constants_1.Constants.SERVICE_REALTIME_DATABASE:
336
- added = await this.addRealtimeDatabaseTrigger(this.args.projectId, key, definition.eventTrigger, signature, definition.region);
337
+ added = await this.addRealtimeDatabaseTrigger(this.args.projectId, definition.id, key, definition.eventTrigger, signature, definition.region);
337
338
  break;
338
339
  case constants_1.Constants.SERVICE_PUBSUB:
339
340
  added = await this.addPubsubTrigger(definition.name, key, definition.eventTrigger, signature, definition.schedule);
@@ -437,7 +438,7 @@ class FunctionsEmulator {
437
438
  }
438
439
  return { bundle, apiPath, instance };
439
440
  }
440
- getV2DatabaseApiAttributes(projectId, key, eventTrigger, region) {
441
+ getV2DatabaseApiAttributes(projectId, id, key, eventTrigger, region) {
441
442
  var _a, _b, _c;
442
443
  const instance = ((_a = eventTrigger.eventFilters) === null || _a === void 0 ? void 0 : _a.instance) || ((_b = eventTrigger.eventFilterPathPatterns) === null || _b === void 0 ? void 0 : _b.instance);
443
444
  if (!instance) {
@@ -447,6 +448,9 @@ class FunctionsEmulator {
447
448
  if (!ref) {
448
449
  throw new error_1.FirebaseError("A database reference must be supplied.");
449
450
  }
451
+ if (region !== "us-central1") {
452
+ this.logger.logLabeled("WARN", `functions[${id}]`, `function region is defined outside the database region, will not trigger.`);
453
+ }
450
454
  const bundle = JSON.stringify({
451
455
  name: `projects/${projectId}/locations/${region}/triggers/${key}`,
452
456
  path: ref,
@@ -457,12 +461,12 @@ class FunctionsEmulator {
457
461
  const apiPath = "/.settings/functionTriggers.json";
458
462
  return { bundle, apiPath, instance };
459
463
  }
460
- async addRealtimeDatabaseTrigger(projectId, key, eventTrigger, signature, region) {
464
+ async addRealtimeDatabaseTrigger(projectId, id, key, eventTrigger, signature, region) {
461
465
  if (!registry_1.EmulatorRegistry.isRunning(types_1.Emulators.DATABASE)) {
462
466
  return false;
463
467
  }
464
468
  const { bundle, apiPath, instance } = signature === "cloudevent"
465
- ? this.getV2DatabaseApiAttributes(projectId, key, eventTrigger, region)
469
+ ? this.getV2DatabaseApiAttributes(projectId, id, key, eventTrigger, region)
466
470
  : this.getV1DatabaseApiAttributes(projectId, key, eventTrigger);
467
471
  logger_1.logger.debug(`addRealtimeDatabaseTrigger[${instance}]`, JSON.stringify(bundle));
468
472
  const client = registry_1.EmulatorRegistry.client(types_1.Emulators.DATABASE);
@@ -880,6 +884,10 @@ class FunctionsEmulator {
880
884
  return;
881
885
  }
882
886
  const record = this.getTriggerRecordByKey(triggerId);
887
+ if (!record.enabled) {
888
+ res.status(204).send("Background triggers are currently disabled.");
889
+ return;
890
+ }
883
891
  const trigger = record.def;
884
892
  logger_1.logger.debug(`Accepted request ${method} ${req.url} --> ${triggerId}`);
885
893
  const reqBody = req.rawBody;
@@ -4,9 +4,9 @@ exports.createFirebaseEndpoints = void 0;
4
4
  const emulatorLogger_1 = require("../../emulatorLogger");
5
5
  const types_1 = require("../../types");
6
6
  const uuid = require("uuid");
7
- const zlib_1 = require("zlib");
8
7
  const metadata_1 = require("../metadata");
9
8
  const express_1 = require("express");
9
+ const shared_1 = require("./shared");
10
10
  const registry_1 = require("../../registry");
11
11
  const multipart_1 = require("../multipart");
12
12
  const errors_1 = require("../errors");
@@ -17,7 +17,7 @@ function createFirebaseEndpoints(emulator) {
17
17
  const { storageLayer, uploadService } = emulator;
18
18
  if (process.env.STORAGE_EMULATOR_DEBUG) {
19
19
  firebaseStorageAPI.use((req, res, next) => {
20
- console.log("--------------INCOMING REQUEST--------------");
20
+ console.log("--------------INCOMING FIREBASE REQUEST--------------");
21
21
  console.log(`${req.method.toUpperCase()} ${req.path}`);
22
22
  console.log("-- query:");
23
23
  console.log(JSON.stringify(req.query, undefined, 2));
@@ -98,24 +98,7 @@ function createFirebaseEndpoints(emulator) {
98
98
  metadata.addDownloadToken(true);
99
99
  }
100
100
  if (req.query.alt === "media") {
101
- const isGZipped = metadata.contentEncoding === "gzip";
102
- if (isGZipped) {
103
- data = (0, zlib_1.gunzipSync)(data);
104
- }
105
- res.setHeader("Accept-Ranges", "bytes");
106
- res.setHeader("Content-Type", metadata.contentType || "application/octet-stream");
107
- res.setHeader("Content-Disposition", metadata.contentDisposition || "inline");
108
- setObjectHeaders(res, metadata, { "Content-Encoding": isGZipped ? "identity" : undefined });
109
- const byteRange = req.range(data.byteLength, { combine: true });
110
- if (Array.isArray(byteRange) && byteRange.type === "bytes" && byteRange.length > 0) {
111
- const range = byteRange[0];
112
- res.setHeader("Content-Range", `${byteRange.type} ${range.start}-${range.end}/${data.byteLength}`);
113
- res.status(206).end(data.slice(range.start, range.end + 1));
114
- }
115
- else {
116
- res.end(data);
117
- }
118
- return;
101
+ return (0, shared_1.sendFileBytes)(metadata, data, req, res);
119
102
  }
120
103
  return res.json(new metadata_1.OutgoingFirebaseMetadata(metadata));
121
104
  });
@@ -215,7 +198,7 @@ function createFirebaseEndpoints(emulator) {
215
198
  const upload = uploadService.startResumableUpload({
216
199
  bucketId,
217
200
  objectId,
218
- metadataRaw: JSON.stringify(req.body),
201
+ metadata: req.body,
219
202
  authorization: req.header("authorization"),
220
203
  });
221
204
  res.header("x-goog-upload-chunk-granularity", "10000");
@@ -330,7 +313,7 @@ function createFirebaseEndpoints(emulator) {
330
313
  const upload = uploadService.multipartUpload({
331
314
  bucketId,
332
315
  objectId,
333
- metadataRaw,
316
+ metadata: JSON.parse(metadataRaw),
334
317
  dataRaw: dataRaw,
335
318
  authorization: req.header("authorization"),
336
319
  });
@@ -480,14 +463,11 @@ function createFirebaseEndpoints(emulator) {
480
463
  return firebaseStorageAPI;
481
464
  }
482
465
  exports.createFirebaseEndpoints = createFirebaseEndpoints;
483
- function setObjectHeaders(res, metadata, headerOverride = { "Content-Encoding": undefined }) {
466
+ function setObjectHeaders(res, metadata) {
484
467
  if (metadata.contentDisposition) {
485
468
  res.setHeader("Content-Disposition", metadata.contentDisposition);
486
469
  }
487
- if (headerOverride["Content-Encoding"]) {
488
- res.setHeader("Content-Encoding", headerOverride["Content-Encoding"]);
489
- }
490
- else if (metadata.contentEncoding) {
470
+ if (metadata.contentEncoding) {
491
471
  res.setHeader("Content-Encoding", metadata.contentEncoding);
492
472
  }
493
473
  if (metadata.cacheControl) {
@@ -2,12 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createCloudEndpoints = void 0;
4
4
  const express_1 = require("express");
5
- const zlib_1 = require("zlib");
6
5
  const types_1 = require("../../types");
7
6
  const metadata_1 = require("../metadata");
7
+ const shared_1 = require("./shared");
8
8
  const registry_1 = require("../../registry");
9
9
  const emulatorLogger_1 = require("../../emulatorLogger");
10
- const crc_1 = require("../crc");
11
10
  const multipart_1 = require("../multipart");
12
11
  const upload_1 = require("../upload");
13
12
  const errors_1 = require("../errors");
@@ -17,7 +16,7 @@ function createCloudEndpoints(emulator) {
17
16
  const { adminStorageLayer, uploadService } = emulator;
18
17
  if (process.env.STORAGE_EMULATOR_DEBUG) {
19
18
  gcloudStorageAPI.use((req, res, next) => {
20
- console.log("--------------INCOMING REQUEST--------------");
19
+ console.log("--------------INCOMING GCS REQUEST--------------");
21
20
  console.log(`${req.method.toUpperCase()} ${req.path}`);
22
21
  console.log("-- query:");
23
22
  console.log(JSON.stringify(req.query, undefined, 2));
@@ -86,7 +85,7 @@ function createCloudEndpoints(emulator) {
86
85
  throw err;
87
86
  }
88
87
  if (req.query.alt === "media") {
89
- return sendFileBytes(getObjectResponse.metadata, getObjectResponse.data, req, res);
88
+ return (0, shared_1.sendFileBytes)(getObjectResponse.metadata, getObjectResponse.data, req, res);
90
89
  }
91
90
  return res.json(new metadata_1.CloudStorageObjectMetadata(getObjectResponse.metadata));
92
91
  });
@@ -110,7 +109,7 @@ function createCloudEndpoints(emulator) {
110
109
  }
111
110
  return res.json(new metadata_1.CloudStorageObjectMetadata(updatedMetadata));
112
111
  });
113
- gcloudStorageAPI.get("/b/:bucketId/o", async (req, res) => {
112
+ gcloudStorageAPI.get(["/b/:bucketId/o", "/storage/v1/b/:bucketId/o"], async (req, res) => {
114
113
  var _a;
115
114
  let listResponse;
116
115
  try {
@@ -228,10 +227,11 @@ function createCloudEndpoints(emulator) {
228
227
  res.sendStatus(400);
229
228
  return;
230
229
  }
230
+ const contentType = req.header("x-upload-content-type");
231
231
  const upload = uploadService.startResumableUpload({
232
232
  bucketId: req.params.bucketId,
233
233
  objectId: name,
234
- metadataRaw: JSON.stringify(req.body),
234
+ metadata: Object.assign({ contentType }, req.body),
235
235
  authorization: req.header("authorization"),
236
236
  });
237
237
  const uploadUrl = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE, req);
@@ -256,6 +256,7 @@ function createCloudEndpoints(emulator) {
256
256
  }
257
257
  if (uploadType === "multipart") {
258
258
  const contentTypeHeader = req.header("content-type") || req.header("x-upload-content-type");
259
+ const contentType = req.header("x-upload-content-type");
259
260
  if (!contentTypeHeader) {
260
261
  return res.sendStatus(400);
261
262
  }
@@ -283,7 +284,7 @@ function createCloudEndpoints(emulator) {
283
284
  const upload = uploadService.multipartUpload({
284
285
  bucketId: req.params.bucketId,
285
286
  objectId: name,
286
- metadataRaw: metadataRaw,
287
+ metadata: Object.assign({ contentType }, JSON.parse(metadataRaw)),
287
288
  dataRaw: dataRaw,
288
289
  authorization: req.header("authorization"),
289
290
  });
@@ -318,7 +319,7 @@ function createCloudEndpoints(emulator) {
318
319
  }
319
320
  throw err;
320
321
  }
321
- return sendFileBytes(getObjectResponse.metadata, getObjectResponse.data, req, res);
322
+ return (0, shared_1.sendFileBytes)(getObjectResponse.metadata, getObjectResponse.data, req, res);
322
323
  });
323
324
  gcloudStorageAPI.post("/b/:bucketId/o/:objectId/:method(rewriteTo|copyTo)/b/:destBucketId/o/:destObjectId", (req, res, next) => {
324
325
  if (req.params.method === "rewriteTo" && req.query.rewriteToken) {
@@ -375,31 +376,6 @@ function createCloudEndpoints(emulator) {
375
376
  return gcloudStorageAPI;
376
377
  }
377
378
  exports.createCloudEndpoints = createCloudEndpoints;
378
- function sendFileBytes(md, data, req, res) {
379
- const isGZipped = md.contentEncoding === "gzip";
380
- if (isGZipped) {
381
- data = (0, zlib_1.gunzipSync)(data);
382
- }
383
- res.setHeader("Accept-Ranges", "bytes");
384
- res.setHeader("Content-Type", md.contentType || "application/octet-stream");
385
- res.setHeader("Content-Disposition", md.contentDisposition || "attachment");
386
- res.setHeader("Content-Encoding", isGZipped ? "identity" : md.contentEncoding || "");
387
- res.setHeader("ETag", md.etag);
388
- res.setHeader("Cache-Control", md.cacheControl || "");
389
- res.setHeader("x-goog-generation", `${md.generation}`);
390
- res.setHeader("x-goog-metadatageneration", `${md.metageneration}`);
391
- res.setHeader("x-goog-storage-class", md.storageClass);
392
- res.setHeader("x-goog-hash", `crc32c=${(0, crc_1.crc32cToString)(md.crc32c)},md5=${md.md5Hash}`);
393
- const byteRange = req.range(data.byteLength, { combine: true });
394
- if (Array.isArray(byteRange) && byteRange.type === "bytes" && byteRange.length > 0) {
395
- const range = byteRange[0];
396
- res.setHeader("Content-Range", `${byteRange.type} ${range.start}-${range.end}/${data.byteLength}`);
397
- res.status(206).end(data.slice(range.start, range.end + 1));
398
- }
399
- else {
400
- res.end(data);
401
- }
402
- }
403
379
  function sendObjectNotFound(req, res) {
404
380
  res.status(404);
405
381
  const message = `No such object: ${req.params.bucketId}/${req.params.objectId}`;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sendFileBytes = void 0;
4
+ const zlib_1 = require("zlib");
5
+ const crc_1 = require("../crc");
6
+ function sendFileBytes(md, data, req, res) {
7
+ let didGunzip = false;
8
+ if (md.contentEncoding === "gzip") {
9
+ const acceptEncoding = req.header("accept-encoding") || "";
10
+ const shouldGunzip = !acceptEncoding.includes("gzip");
11
+ if (shouldGunzip) {
12
+ data = (0, zlib_1.gunzipSync)(data);
13
+ didGunzip = true;
14
+ }
15
+ }
16
+ res.setHeader("Accept-Ranges", "bytes");
17
+ res.setHeader("Content-Type", md.contentType || "application/octet-stream");
18
+ res.setHeader("Content-Disposition", md.contentDisposition || "attachment");
19
+ if (didGunzip) {
20
+ res.setHeader("Transfer-Encoding", "chunked");
21
+ }
22
+ else {
23
+ res.setHeader("Content-Encoding", md.contentEncoding || "");
24
+ }
25
+ res.setHeader("ETag", md.etag);
26
+ res.setHeader("Cache-Control", md.cacheControl || "");
27
+ res.setHeader("x-goog-generation", `${md.generation}`);
28
+ res.setHeader("x-goog-metadatageneration", `${md.metageneration}`);
29
+ res.setHeader("x-goog-storage-class", md.storageClass);
30
+ res.setHeader("x-goog-hash", `crc32c=${(0, crc_1.crc32cToString)(md.crc32c)},md5=${md.md5Hash}`);
31
+ const shouldRespectContentRange = !didGunzip;
32
+ if (shouldRespectContentRange) {
33
+ const byteRange = req.range(data.byteLength, { combine: true });
34
+ if (Array.isArray(byteRange) && byteRange.type === "bytes" && byteRange.length > 0) {
35
+ const range = byteRange[0];
36
+ res.setHeader("Content-Range", `${byteRange.type} ${range.start}-${range.end}/${data.byteLength}`);
37
+ res.status(206).end(data.slice(range.start, range.end + 1));
38
+ return;
39
+ }
40
+ }
41
+ res.end(data);
42
+ }
43
+ exports.sendFileBytes = sendFileBytes;
@@ -3,6 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getStorageRulesConfig = void 0;
4
4
  const error_1 = require("../../../error");
5
5
  const fsutils_1 = require("../../../fsutils");
6
+ const constants_1 = require("../../constants");
7
+ const types_1 = require("../../types");
8
+ const emulatorLogger_1 = require("../../emulatorLogger");
6
9
  function getSourceFile(rules, options) {
7
10
  const path = options.config.path(rules);
8
11
  return { name: path, content: (0, fsutils_1.readFile)(path) };
@@ -10,6 +13,12 @@ function getSourceFile(rules, options) {
10
13
  function getStorageRulesConfig(projectId, options) {
11
14
  const storageConfig = options.config.data.storage;
12
15
  if (!storageConfig) {
16
+ if (constants_1.Constants.isDemoProject(projectId)) {
17
+ const storageLogger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE);
18
+ storageLogger.logLabeled("BULLET", "storage", `Detected demo project ID "${projectId}", using a default (open) rules configuration.`);
19
+ const path = __dirname + "/../../../../templates/emulators/default_storage.rules";
20
+ return { name: path, content: (0, fsutils_1.readFile)(path) };
21
+ }
13
22
  throw new error_1.FirebaseError("Cannot start the Storage emulator without rules file specified in firebase.json: run 'firebase init' and set up your Storage configuration");
14
23
  }
15
24
  if (!Array.isArray(storageConfig)) {
@@ -50,7 +50,7 @@ class UploadService {
50
50
  objectId: request.objectId,
51
51
  uploadType: UploadType.MULTIPART,
52
52
  dataRaw: request.dataRaw,
53
- metadata: JSON.parse(request.metadataRaw),
53
+ metadata: request.metadata,
54
54
  authorization: request.authorization,
55
55
  });
56
56
  this._persistence.deleteFile(upload.path, true);
@@ -82,7 +82,7 @@ class UploadService {
82
82
  type: UploadType.RESUMABLE,
83
83
  path: this.getStagingFileName(id, request.bucketId, request.objectId),
84
84
  status: UploadStatus.ACTIVE,
85
- metadata: JSON.parse(request.metadataRaw),
85
+ metadata: request.metadata,
86
86
  size: 0,
87
87
  authorization: request.authorization,
88
88
  };
@@ -558,12 +558,15 @@ async function diagnoseAndFixProject(options) {
558
558
  }
559
559
  }
560
560
  exports.diagnoseAndFixProject = diagnoseAndFixProject;
561
- async function canonicalizeRefInput(extensionName) {
562
- if (extensionName.split("/").length < 2) {
563
- const [extensionID, version] = extensionName.split("@");
564
- extensionName = `firebase/${extensionID}@${version || "latest"}`;
561
+ async function canonicalizeRefInput(refInput) {
562
+ let inferredRef = refInput;
563
+ if (refInput.split("/").length < 2) {
564
+ inferredRef = `firebase/${inferredRef}`;
565
565
  }
566
- const ref = refs.parse(extensionName);
566
+ if (refInput.split("@").length < 2) {
567
+ inferredRef = `${inferredRef}@latest`;
568
+ }
569
+ const ref = refs.parse(inferredRef);
567
570
  ref.version = await (0, planner_1.resolveVersion)(ref);
568
571
  return refs.toExtensionVersionRef(ref);
569
572
  }
package/lib/utils.js CHANGED
@@ -363,7 +363,7 @@ function datetimeString(d) {
363
363
  }
364
364
  exports.datetimeString = datetimeString;
365
365
  function isCloudEnvironment() {
366
- return !!process.env.CODESPACES;
366
+ return !!process.env.CODESPACES || !!process.env.GOOGLE_CLOUD_WORKSTATIONS;
367
367
  }
368
368
  exports.isCloudEnvironment = isCloudEnvironment;
369
369
  function isRunningInWSL() {