firebase-tools 10.4.2 → 10.5.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.
@@ -20,7 +20,7 @@ marked.setOptions({
20
20
  renderer: new TerminalRenderer(),
21
21
  });
22
22
  const updateMessage = `Update available ${clc.xterm(240)("{currentVersion}")} → ${clc.green("{latestVersion}")}\n` +
23
- `To update to the latest version using npm, run ${clc.cyan("npm install -g firebase-tools")}\n` +
23
+ `To update to the latest version using npm, run\n${clc.cyan("npm install -g firebase-tools")}\n` +
24
24
  `For other CLI management options, visit the ${marked("[CLI documentation](https://firebase.google.com/docs/cli#update-cli)")}`;
25
25
  updateNotifier.notify({ defer: true, isGlobal: true, message: updateMessage });
26
26
  const client = require("..");
@@ -8,6 +8,7 @@ const registry_1 = require("../emulator/registry");
8
8
  const types_1 = require("../emulator/types");
9
9
  const clc = require("cli-color");
10
10
  const constants_1 = require("../emulator/constants");
11
+ const utils_1 = require("../utils");
11
12
  const Table = require("cli-table");
12
13
  function stylizeLink(url) {
13
14
  return clc.underline(clc.bold(url));
@@ -22,8 +23,9 @@ module.exports = new command_1.Command("emulators:start")
22
23
  .option(commandUtils.FLAG_EXPORT_ON_EXIT, commandUtils.DESC_EXPORT_ON_EXIT)
23
24
  .action(async (options) => {
24
25
  const killSignalPromise = commandUtils.shutdownWhenKilled(options);
26
+ let deprecationNotices;
25
27
  try {
26
- await controller.startAll(options);
28
+ ({ deprecationNotices } = await controller.startAll(options));
27
29
  }
28
30
  catch (e) {
29
31
  await controller.cleanShutdown();
@@ -85,5 +87,8 @@ ${clc.blackBright(" Other reserved ports:")} ${reservedPortsString}
85
87
 
86
88
  Issues? Report them at ${stylizeLink("https://github.com/firebase/firebase-tools/issues")} and attach the *-debug.log files.
87
89
  `);
90
+ for (const notice of deprecationNotices) {
91
+ (0, utils_1.logLabeledWarning)("emulators", notice, "warn");
92
+ }
88
93
  await killSignalPromise;
89
94
  });
@@ -19,9 +19,10 @@ module.exports = new command_1.Command("ext:dev:emulators:start")
19
19
  .action(async (options) => {
20
20
  const killSignalPromise = commandUtils.shutdownWhenKilled(options);
21
21
  const emulatorOptions = await optionsHelper.buildOptions(options);
22
+ let deprecationNotices;
22
23
  try {
23
24
  commandUtils.beforeEmulatorCommand(emulatorOptions);
24
- await controller.startAll(emulatorOptions);
25
+ ({ deprecationNotices } = await controller.startAll(emulatorOptions));
25
26
  }
26
27
  catch (e) {
27
28
  await controller.cleanShutdown();
@@ -31,5 +32,8 @@ module.exports = new command_1.Command("ext:dev:emulators:start")
31
32
  throw e;
32
33
  }
33
34
  utils.logSuccess("All emulators ready, it is now safe to connect.");
35
+ for (const notice of deprecationNotices) {
36
+ utils.logLabeledWarning("emulators", notice, "warn");
37
+ }
34
38
  await killSignalPromise;
35
39
  });
@@ -39,6 +39,7 @@ exports.authOperations = {
39
39
  projects: {
40
40
  createSessionCookie,
41
41
  queryAccounts,
42
+ updateConfig,
42
43
  accounts: {
43
44
  _: signUp,
44
45
  delete: deleteAccount,
@@ -1236,28 +1237,17 @@ function getEmulatorProjectConfig(state) {
1236
1237
  usageMode: state.usageMode,
1237
1238
  };
1238
1239
  }
1239
- function updateEmulatorProjectConfig(state, reqBody) {
1240
+ function updateEmulatorProjectConfig(state, reqBody, ctx) {
1240
1241
  var _a;
1241
- const allowDuplicateEmails = (_a = reqBody.signIn) === null || _a === void 0 ? void 0 : _a.allowDuplicateEmails;
1242
- if (allowDuplicateEmails != null) {
1243
- (0, errors_1.assert)(state instanceof state_1.AgentProjectState, "((Only top level projects can set oneAccountPerEmail.))");
1244
- state.oneAccountPerEmail = !allowDuplicateEmails;
1245
- }
1246
- const usageMode = reqBody.usageMode;
1247
- if (usageMode != null) {
1248
- (0, errors_1.assert)(state instanceof state_1.AgentProjectState, "((Only top level projects can set usageMode.))");
1249
- switch (usageMode) {
1250
- case "PASSTHROUGH":
1251
- (0, errors_1.assert)(state.getUserCount() === 0, "Users are present, unable to set passthrough mode");
1252
- state.usageMode = state_1.UsageMode.PASSTHROUGH;
1253
- break;
1254
- case "DEFAULT":
1255
- state.usageMode = state_1.UsageMode.DEFAULT;
1256
- break;
1257
- default:
1258
- throw new errors_1.BadRequestError("Invalid usage mode provided");
1259
- }
1242
+ const updateMask = [];
1243
+ if (((_a = reqBody.signIn) === null || _a === void 0 ? void 0 : _a.allowDuplicateEmails) != null) {
1244
+ updateMask.push("signIn.allowDuplicateEmails");
1245
+ }
1246
+ if (reqBody.usageMode) {
1247
+ updateMask.push("usageMode");
1260
1248
  }
1249
+ ctx.params.query.updateMask = updateMask.join();
1250
+ updateConfig(state, reqBody, ctx);
1261
1251
  return getEmulatorProjectConfig(state);
1262
1252
  }
1263
1253
  function listOobCodesInProject(state) {
@@ -1390,6 +1380,17 @@ function mfaSignInFinalize(state, reqBody) {
1390
1380
  refreshToken,
1391
1381
  };
1392
1382
  }
1383
+ function updateConfig(state, reqBody, ctx) {
1384
+ var _a;
1385
+ (0, errors_1.assert)(state instanceof state_1.AgentProjectState, "((Can only update top-level configurations on agent projects.))");
1386
+ for (const event in (_a = reqBody.blockingFunctions) === null || _a === void 0 ? void 0 : _a.triggers) {
1387
+ if (Object.prototype.hasOwnProperty.call(reqBody.blockingFunctions.triggers, event)) {
1388
+ (0, errors_1.assert)(Object.values(state_1.BlockingFunctionEvents).includes(event), "INVALID_BLOCKING_FUNCTION: ((Event type is invalid.))");
1389
+ (0, errors_1.assert)((0, utils_1.parseAbsoluteUri)(reqBody.blockingFunctions.triggers[event].functionUri), "INVALID_BLOCKING_FUNCTION: ((Expected an absolute URI with valid scheme and host.))");
1390
+ }
1391
+ }
1392
+ return state.updateConfig(reqBody, ctx.params.query.updateMask);
1393
+ }
1393
1394
  function coercePrimitiveToString(value) {
1394
1395
  switch (typeof value) {
1395
1396
  case "string":
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.UsageMode = exports.TenantProjectState = exports.AgentProjectState = exports.ProjectState = exports.SIGNIN_METHOD_EMAIL_LINK = exports.PROVIDER_GAME_CENTER = exports.PROVIDER_CUSTOM = exports.PROVIDER_ANONYMOUS = exports.PROVIDER_PHONE = exports.PROVIDER_PASSWORD = void 0;
3
+ exports.BlockingFunctionEvents = exports.UsageMode = exports.TenantProjectState = exports.AgentProjectState = exports.ProjectState = exports.SIGNIN_METHOD_EMAIL_LINK = exports.PROVIDER_GAME_CENTER = exports.PROVIDER_CUSTOM = exports.PROVIDER_ANONYMOUS = exports.PROVIDER_PHONE = exports.PROVIDER_PASSWORD = void 0;
4
4
  const utils_1 = require("./utils");
5
5
  const cloudFunctions_1 = require("./cloudFunctions");
6
6
  const errors_1 = require("./errors");
@@ -422,25 +422,28 @@ exports.ProjectState = ProjectState;
422
422
  class AgentProjectState extends ProjectState {
423
423
  constructor(projectId) {
424
424
  super(projectId);
425
- this._oneAccountPerEmail = true;
426
- this._usageMode = UsageMode.DEFAULT;
427
425
  this.tenantProjectForTenantId = new Map();
428
426
  this._authCloudFunction = new cloudFunctions_1.AuthCloudFunction(this.projectId);
427
+ this._config = {
428
+ signIn: { allowDuplicateEmails: false },
429
+ usageMode: UsageMode.DEFAULT,
430
+ blockingFunctions: {},
431
+ };
429
432
  }
430
433
  get authCloudFunction() {
431
434
  return this._authCloudFunction;
432
435
  }
433
436
  get oneAccountPerEmail() {
434
- return this._oneAccountPerEmail;
437
+ return !this._config.signIn.allowDuplicateEmails;
435
438
  }
436
439
  set oneAccountPerEmail(oneAccountPerEmail) {
437
- this._oneAccountPerEmail = oneAccountPerEmail;
440
+ this._config.signIn.allowDuplicateEmails = !oneAccountPerEmail;
438
441
  }
439
442
  get usageMode() {
440
- return this._usageMode;
443
+ return this._config.usageMode;
441
444
  }
442
445
  set usageMode(usageMode) {
443
- this._usageMode = usageMode;
446
+ this._config.usageMode = usageMode;
444
447
  }
445
448
  get allowPasswordSignup() {
446
449
  return true;
@@ -457,6 +460,31 @@ class AgentProjectState extends ProjectState {
457
460
  get enableEmailLinkSignin() {
458
461
  return true;
459
462
  }
463
+ get config() {
464
+ return this._config;
465
+ }
466
+ get blockingFunctionsConfig() {
467
+ return this._config.blockingFunctions;
468
+ }
469
+ set blockingFunctionsConfig(blockingFunctions) {
470
+ this._config.blockingFunctions = blockingFunctions;
471
+ }
472
+ updateConfig(update, updateMask) {
473
+ var _a, _b, _c, _d;
474
+ if (update.usageMode) {
475
+ (0, errors_1.assert)(update.usageMode !== UsageMode.USAGE_MODE_UNSPECIFIED, "INVALID_USAGE_MODE: ((Invalid usage mode provided.))");
476
+ if (update.usageMode === UsageMode.PASSTHROUGH) {
477
+ (0, errors_1.assert)(this.getUserCount() === 0, "USERS_STILL_EXIST: ((Users are present, unable to set passthrough mode.))");
478
+ }
479
+ }
480
+ if (!updateMask) {
481
+ this.oneAccountPerEmail = (_b = !((_a = update.signIn) === null || _a === void 0 ? void 0 : _a.allowDuplicateEmails)) !== null && _b !== void 0 ? _b : true;
482
+ this.blockingFunctionsConfig = (_c = update.blockingFunctions) !== null && _c !== void 0 ? _c : {};
483
+ this.usageMode = (_d = update.usageMode) !== null && _d !== void 0 ? _d : UsageMode.DEFAULT;
484
+ return this.config;
485
+ }
486
+ return applyMask(updateMask, this.config, update);
487
+ }
460
488
  getTenantProject(tenantId) {
461
489
  if (!this.tenantProjectForTenantId.has(tenantId)) {
462
490
  this.createTenantWithTenantId(tenantId, {
@@ -574,40 +602,21 @@ class TenantProjectState extends ProjectState {
574
602
  };
575
603
  return this.tenantConfig;
576
604
  }
577
- const paths = updateMask.split(",");
578
- for (const path of paths) {
579
- const fields = path.split(".");
580
- let updateField = update;
581
- let existingField = this._tenantConfig;
582
- let field;
583
- for (let i = 0; i < fields.length - 1; i++) {
584
- field = fields[i];
585
- if (updateField[field] == null) {
586
- console.warn(`Unable to find field '${field}' in update '${updateField}`);
587
- break;
588
- }
589
- if (Array.isArray(updateField[field]) ||
590
- Object(updateField[field]) !== updateField[field]) {
591
- console.warn(`Field '${field}' is singular and cannot have sub-fields`);
592
- break;
593
- }
594
- if (!existingField[field]) {
595
- existingField[field] = {};
596
- }
597
- updateField = updateField[field];
598
- existingField = existingField[field];
599
- }
600
- field = fields[fields.length - 1];
601
- if (updateField[field] == null) {
602
- console.warn(`Unable to find field '${field}' in update '${JSON.stringify(updateField)}`);
603
- continue;
604
- }
605
- existingField[field] = updateField[field];
606
- }
607
- return this.tenantConfig;
605
+ return applyMask(updateMask, this.tenantConfig, update);
608
606
  }
609
607
  }
610
608
  exports.TenantProjectState = TenantProjectState;
609
+ var UsageMode;
610
+ (function (UsageMode) {
611
+ UsageMode["USAGE_MODE_UNSPECIFIED"] = "USAGE_MODE_UNSPECIFIED";
612
+ UsageMode["DEFAULT"] = "DEFAULT";
613
+ UsageMode["PASSTHROUGH"] = "PASSTHROUGH";
614
+ })(UsageMode = exports.UsageMode || (exports.UsageMode = {}));
615
+ var BlockingFunctionEvents;
616
+ (function (BlockingFunctionEvents) {
617
+ BlockingFunctionEvents["BEFORE_CREATE"] = "beforeCreate";
618
+ BlockingFunctionEvents["BEFORE_SIGN_IN"] = "beforeSignIn";
619
+ })(BlockingFunctionEvents = exports.BlockingFunctionEvents || (exports.BlockingFunctionEvents = {}));
611
620
  function getProviderEmailsForUser(user) {
612
621
  var _a;
613
622
  const emails = new Set();
@@ -618,8 +627,35 @@ function getProviderEmailsForUser(user) {
618
627
  });
619
628
  return emails;
620
629
  }
621
- var UsageMode;
622
- (function (UsageMode) {
623
- UsageMode["DEFAULT"] = "DEFAULT";
624
- UsageMode["PASSTHROUGH"] = "PASSTHROUGH";
625
- })(UsageMode = exports.UsageMode || (exports.UsageMode = {}));
630
+ function applyMask(updateMask, dest, update) {
631
+ const paths = updateMask.split(",");
632
+ for (const path of paths) {
633
+ const fields = path.split(".");
634
+ let updateField = update;
635
+ let existingField = dest;
636
+ let field;
637
+ for (let i = 0; i < fields.length - 1; i++) {
638
+ field = fields[i];
639
+ if (updateField[field] == null) {
640
+ console.warn(`Unable to find field '${field}' in update '${updateField}`);
641
+ break;
642
+ }
643
+ if (Array.isArray(updateField[field]) || Object(updateField[field]) !== updateField[field]) {
644
+ console.warn(`Field '${field}' is singular and cannot have sub-fields`);
645
+ break;
646
+ }
647
+ if (!existingField[field]) {
648
+ existingField[field] = {};
649
+ }
650
+ updateField = updateField[field];
651
+ existingField = existingField[field];
652
+ }
653
+ field = fields[fields.length - 1];
654
+ if (updateField[field] == null) {
655
+ console.warn(`Unable to find field '${field}' in update '${JSON.stringify(updateField)}`);
656
+ continue;
657
+ }
658
+ existingField[field] = updateField[field];
659
+ }
660
+ return dest;
661
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.emulatorExec = exports.shutdownWhenKilled = exports.setExportOnExitOptions = exports.parseInspectionPort = exports.beforeEmulatorCommand = exports.warnEmulatorNotSupported = exports.printNoticeIfEmulated = exports.DESC_TEST_PARAMS = exports.FLAG_TEST_PARAMS = exports.DESC_TEST_CONFIG = exports.FLAG_TEST_CONFIG = exports.DESC_UI = exports.FLAG_UI = exports.EXPORT_ON_EXIT_CWD_DANGER = exports.EXPORT_ON_EXIT_USAGE_ERROR = exports.DESC_EXPORT_ON_EXIT = exports.FLAG_EXPORT_ON_EXIT = exports.FLAG_EXPORT_ON_EXIT_NAME = exports.DESC_IMPORT = exports.FLAG_IMPORT = exports.DESC_INSPECT_FUNCTIONS = exports.FLAG_INSPECT_FUNCTIONS = exports.DESC_ONLY = exports.FLAG_ONLY = void 0;
3
+ exports.JAVA_DEPRECATION_WARNING = exports.checkJavaSupported = exports.emulatorExec = exports.shutdownWhenKilled = exports.setExportOnExitOptions = exports.parseInspectionPort = exports.beforeEmulatorCommand = exports.warnEmulatorNotSupported = exports.printNoticeIfEmulated = exports.DESC_TEST_PARAMS = exports.FLAG_TEST_PARAMS = exports.DESC_TEST_CONFIG = exports.FLAG_TEST_CONFIG = exports.DESC_UI = exports.FLAG_UI = exports.EXPORT_ON_EXIT_CWD_DANGER = exports.EXPORT_ON_EXIT_USAGE_ERROR = exports.DESC_EXPORT_ON_EXIT = exports.FLAG_EXPORT_ON_EXIT = exports.FLAG_EXPORT_ON_EXIT_NAME = exports.DESC_IMPORT = exports.FLAG_IMPORT = exports.DESC_INSPECT_FUNCTIONS = exports.FLAG_INSPECT_FUNCTIONS = exports.DESC_ONLY = exports.FLAG_ONLY = void 0;
4
4
  const clc = require("cli-color");
5
5
  const childProcess = require("child_process");
6
6
  const controller = require("../emulator/controller");
@@ -287,15 +287,19 @@ async function emulatorExec(script, options) {
287
287
  extraEnv.GCLOUD_PROJECT = projectId;
288
288
  }
289
289
  let exitCode = 0;
290
+ let deprecationNotices;
290
291
  try {
291
292
  const showUI = !!options.ui;
292
- await controller.startAll(options, showUI);
293
+ ({ deprecationNotices } = await controller.startAll(options, showUI));
293
294
  exitCode = await runScript(script, extraEnv);
294
295
  await (0, controller_1.onExit)(options);
295
296
  }
296
297
  finally {
297
298
  await controller.cleanShutdown();
298
299
  }
300
+ for (const notice of deprecationNotices) {
301
+ utils.logLabeledWarning("emulators", notice, "warn");
302
+ }
299
303
  if (exitCode !== 0) {
300
304
  throw new error_1.FirebaseError(`Script "${clc.bold(script)}" exited with code ${exitCode}`, {
301
305
  exit: exitCode,
@@ -303,3 +307,69 @@ async function emulatorExec(script, options) {
303
307
  }
304
308
  }
305
309
  exports.emulatorExec = emulatorExec;
310
+ const JAVA_VERSION_REGEX = /version "([1-9][0-9]*)/;
311
+ const MIN_SUPPORTED_JAVA_MAJOR_VERSION = 11;
312
+ const JAVA_HINT = "Please make sure Java is installed and on your system PATH.";
313
+ async function checkJavaSupported() {
314
+ return new Promise((resolve, reject) => {
315
+ var _a, _b;
316
+ let child;
317
+ try {
318
+ child = childProcess.spawn("java", ["-Duser.language=en", "-Dfile.encoding=UTF-8", "-version"], {
319
+ stdio: ["inherit", "pipe", "pipe"],
320
+ });
321
+ }
322
+ catch (err) {
323
+ return reject(new error_1.FirebaseError(`Could not spawn \`java -version\`. ${JAVA_HINT}`, { original: err }));
324
+ }
325
+ let output = "";
326
+ let error = "";
327
+ (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on("data", (data) => {
328
+ const str = data.toString("utf8");
329
+ logger_1.logger.debug(str);
330
+ output += str;
331
+ });
332
+ (_b = child.stderr) === null || _b === void 0 ? void 0 : _b.on("data", (data) => {
333
+ const str = data.toString("utf8");
334
+ logger_1.logger.debug(str);
335
+ error += str;
336
+ });
337
+ child.once("error", (err) => {
338
+ reject(new error_1.FirebaseError(`Could not spawn \`java -version\`. ${JAVA_HINT}`, { original: err }));
339
+ });
340
+ child.once("exit", (code, signal) => {
341
+ if (signal) {
342
+ reject(new error_1.FirebaseError(`Process \`java -version\` was killed by signal ${signal}.`));
343
+ }
344
+ else if (code && code !== 0) {
345
+ reject(new error_1.FirebaseError(`Process \`java -version\` has exited with code ${code}. ${JAVA_HINT}\n` +
346
+ `-----Original stdout-----\n${output}` +
347
+ `-----Original stderr-----\n${error}`));
348
+ }
349
+ else {
350
+ resolve(`${output}\n${error}`);
351
+ }
352
+ });
353
+ }).then((output) => {
354
+ const match = output.match(JAVA_VERSION_REGEX);
355
+ if (match) {
356
+ const version = match[1];
357
+ const versionInt = parseInt(version, 10);
358
+ if (!versionInt) {
359
+ utils.logLabeledWarning("emulators", `Failed to parse Java version. Got "${match[0]}".`, "warn");
360
+ }
361
+ else {
362
+ logger_1.logger.debug(`Parsed Java major version: ${versionInt}`);
363
+ return versionInt >= MIN_SUPPORTED_JAVA_MAJOR_VERSION;
364
+ }
365
+ }
366
+ else {
367
+ logger_1.logger.debug("java -version outputs:", output);
368
+ logger_1.logger.warn(`Failed to parse Java version.`);
369
+ }
370
+ return false;
371
+ });
372
+ }
373
+ exports.checkJavaSupported = checkJavaSupported;
374
+ exports.JAVA_DEPRECATION_WARNING = "Support for Java version <= 10 will be dropped soon in firebase-tools@11. " +
375
+ "Please upgrade to Java version 11 or above to continue using the emulators.";
@@ -39,6 +39,7 @@ const auth_2 = require("../auth");
39
39
  const extensionsEmulator_1 = require("./extensionsEmulator");
40
40
  const previews_1 = require("../previews");
41
41
  const projectConfig_1 = require("../functions/projectConfig");
42
+ const downloadableEmulators_1 = require("./downloadableEmulators");
42
43
  const START_LOGGING_EMULATOR = utils.envOverride("START_LOGGING_EMULATOR", "false", (val) => val === "true");
43
44
  async function getAndCheckAddress(emulator, options) {
44
45
  var _a, _b, _c, _d;
@@ -216,6 +217,13 @@ async function startAll(options, showUI = true) {
216
217
  if (targets.length === 0) {
217
218
  throw new error_1.FirebaseError(`No emulators to start, run ${clc.bold("firebase init emulators")} to get started.`);
218
219
  }
220
+ const deprecationNotices = [];
221
+ if (targets.some(downloadableEmulators_1.requiresJava)) {
222
+ if (!(await commandUtils.checkJavaSupported())) {
223
+ utils.logLabeledWarning("emulators", commandUtils_1.JAVA_DEPRECATION_WARNING, "warn");
224
+ deprecationNotices.push(commandUtils_1.JAVA_DEPRECATION_WARNING);
225
+ }
226
+ }
219
227
  const hubLogger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.HUB);
220
228
  hubLogger.logLabeled("BULLET", "emulators", `Starting emulators: ${targets.join(", ")}`);
221
229
  const projectId = (0, projectUtils_1.getProjectId)(options) || "";
@@ -479,6 +487,7 @@ async function startAll(options, showUI = true) {
479
487
  await instance.connect();
480
488
  }
481
489
  }
490
+ return { deprecationNotices };
482
491
  }
483
492
  exports.startAll = startAll;
484
493
  async function exportEmulatorData(exportPath, options) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.start = exports.downloadIfNecessary = exports.stop = exports.getPID = exports.get = exports.getDownloadDetails = exports.handleEmulatorProcessError = exports._getCommand = exports.getLogFileName = exports.DownloadDetails = void 0;
3
+ exports.start = exports.downloadIfNecessary = exports.stop = exports.getPID = exports.get = exports.getDownloadDetails = exports.requiresJava = exports.handleEmulatorProcessError = exports._getCommand = exports.getLogFileName = exports.DownloadDetails = void 0;
4
4
  const types_1 = require("./types");
5
5
  const constants_1 = require("./constants");
6
6
  const error_1 = require("../error");
@@ -29,13 +29,13 @@ exports.DownloadDetails = {
29
29
  },
30
30
  },
31
31
  firestore: {
32
- downloadPath: path.join(CACHE_DIR, "cloud-firestore-emulator-v1.13.1.jar"),
33
- version: "1.13.1",
32
+ downloadPath: path.join(CACHE_DIR, "cloud-firestore-emulator-v1.14.1.jar"),
33
+ version: "1.14.1",
34
34
  opts: {
35
35
  cacheDir: CACHE_DIR,
36
- remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.13.1.jar",
37
- expectedSize: 60486708,
38
- expectedChecksum: "e0590880408eacb790874643147c0081",
36
+ remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.14.1.jar",
37
+ expectedSize: 60416634,
38
+ expectedChecksum: "33cffe8065d4250816f257eb19245932",
39
39
  namePrefix: "cloud-firestore-emulator",
40
40
  },
41
41
  },
@@ -236,6 +236,13 @@ async function handleEmulatorProcessError(emulator, err) {
236
236
  }
237
237
  }
238
238
  exports.handleEmulatorProcessError = handleEmulatorProcessError;
239
+ function requiresJava(emulator) {
240
+ if (emulator in Commands) {
241
+ return Commands[emulator].binary === "java";
242
+ }
243
+ return false;
244
+ }
245
+ exports.requiresJava = requiresJava;
239
246
  async function _runBinary(emulator, command, extraEnv) {
240
247
  return new Promise((resolve) => {
241
248
  var _a, _b;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.reqBodyToBuffer = void 0;
4
+ async function reqBodyToBuffer(req) {
5
+ if (req.body instanceof Buffer) {
6
+ return Buffer.from(req.body);
7
+ }
8
+ const bufs = [];
9
+ req.on("data", (data) => {
10
+ bufs.push(data);
11
+ });
12
+ await new Promise((resolve) => {
13
+ req.on("end", () => {
14
+ resolve();
15
+ });
16
+ });
17
+ return Buffer.concat(bufs);
18
+ }
19
+ exports.reqBodyToBuffer = reqBodyToBuffer;
@@ -10,6 +10,7 @@ const registry_1 = require("../../registry");
10
10
  const multipart_1 = require("../multipart");
11
11
  const errors_1 = require("../errors");
12
12
  const upload_1 = require("../upload");
13
+ const request_1 = require("../../shared/request");
13
14
  function createFirebaseEndpoints(emulator) {
14
15
  const firebaseStorageAPI = (0, express_1.Router)();
15
16
  const { storageLayer, uploadService } = emulator;
@@ -71,7 +72,7 @@ function createFirebaseEndpoints(emulator) {
71
72
  let metadata;
72
73
  let data;
73
74
  try {
74
- ({ metadata, data } = await storageLayer.handleGetObject({
75
+ ({ metadata, data } = await storageLayer.getObject({
75
76
  bucketId: req.params.bucketId,
76
77
  decodedObjectId: decodeURIComponent(req.params.objectId),
77
78
  authorization: req.header("authorization"),
@@ -117,11 +118,11 @@ function createFirebaseEndpoints(emulator) {
117
118
  return res.json(new metadata_1.OutgoingFirebaseMetadata(metadata));
118
119
  });
119
120
  firebaseStorageAPI.get("/b/:bucketId/o", async (req, res) => {
120
- var _a, _b;
121
+ var _a, _b, _c, _d, _e;
121
122
  const maxResults = (_a = req.query.maxResults) === null || _a === void 0 ? void 0 : _a.toString();
122
- let response;
123
+ let listResponse;
123
124
  try {
124
- response = await storageLayer.handleListObjects({
125
+ listResponse = await storageLayer.listObjects({
125
126
  bucketId: req.params.bucketId,
126
127
  prefix: req.query.prefix ? req.query.prefix.toString() : "",
127
128
  delimiter: req.query.delimiter ? req.query.delimiter.toString() : "",
@@ -141,23 +142,14 @@ function createFirebaseEndpoints(emulator) {
141
142
  }
142
143
  throw err;
143
144
  }
144
- return res.json(response);
145
- });
146
- const reqBodyToBuffer = async (req) => {
147
- if (req.body instanceof Buffer) {
148
- return Buffer.from(req.body);
149
- }
150
- const bufs = [];
151
- req.on("data", (data) => {
152
- bufs.push(data);
145
+ return res.status(200).json({
146
+ nextPageToken: listResponse.nextPageToken,
147
+ prefixes: (_c = listResponse.prefixes) !== null && _c !== void 0 ? _c : [],
148
+ items: (_e = (_d = listResponse.items) === null || _d === void 0 ? void 0 : _d.map((item) => {
149
+ return { name: item.name, bucket: item.bucket };
150
+ })) !== null && _e !== void 0 ? _e : [],
153
151
  });
154
- await new Promise((resolve) => {
155
- req.on("end", () => {
156
- resolve();
157
- });
158
- });
159
- return Buffer.concat(bufs);
160
- };
152
+ });
161
153
  const handleUpload = async (req, res) => {
162
154
  if (!req.query.name) {
163
155
  res.sendStatus(400);
@@ -174,7 +166,7 @@ function createFirebaseEndpoints(emulator) {
174
166
  let metadataRaw;
175
167
  let dataRaw;
176
168
  try {
177
- ({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await reqBodyToBuffer(req)));
169
+ ({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await (0, request_1.reqBodyToBuffer)(req)));
178
170
  }
179
171
  catch (err) {
180
172
  if (err instanceof Error) {
@@ -196,7 +188,7 @@ function createFirebaseEndpoints(emulator) {
196
188
  });
197
189
  let metadata;
198
190
  try {
199
- metadata = await storageLayer.handleUploadObject(upload);
191
+ metadata = await storageLayer.uploadObject(upload);
200
192
  }
201
193
  catch (err) {
202
194
  if (err instanceof errors_1.ForbiddenError) {
@@ -268,7 +260,7 @@ function createFirebaseEndpoints(emulator) {
268
260
  if (uploadCommand.includes("upload")) {
269
261
  let upload;
270
262
  try {
271
- upload = uploadService.continueResumableUpload(uploadId, await reqBodyToBuffer(req));
263
+ upload = uploadService.continueResumableUpload(uploadId, await (0, request_1.reqBodyToBuffer)(req));
272
264
  }
273
265
  catch (err) {
274
266
  if (err instanceof errors_1.NotFoundError) {
@@ -301,7 +293,7 @@ function createFirebaseEndpoints(emulator) {
301
293
  }
302
294
  let metadata;
303
295
  try {
304
- metadata = await storageLayer.handleUploadObject(upload);
296
+ metadata = await storageLayer.uploadObject(upload);
305
297
  }
306
298
  catch (err) {
307
299
  if (err instanceof errors_1.ForbiddenError) {
@@ -333,7 +325,7 @@ function createFirebaseEndpoints(emulator) {
333
325
  return res.sendStatus(400);
334
326
  }
335
327
  try {
336
- metadata = storageLayer.handleCreateDownloadToken({
328
+ metadata = storageLayer.createDownloadToken({
337
329
  bucketId,
338
330
  decodedObjectId,
339
331
  authorization,
@@ -356,7 +348,7 @@ function createFirebaseEndpoints(emulator) {
356
348
  }
357
349
  else {
358
350
  try {
359
- metadata = storageLayer.handleDeleteDownloadToken({
351
+ metadata = storageLayer.deleteDownloadToken({
360
352
  bucketId,
361
353
  decodedObjectId,
362
354
  token: (_b = (_a = req.query["delete_token"]) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : "",
@@ -390,7 +382,7 @@ function createFirebaseEndpoints(emulator) {
390
382
  const handleMetadataUpdate = async (req, res) => {
391
383
  let metadata;
392
384
  try {
393
- metadata = await storageLayer.handleUpdateObjectMetadata({
385
+ metadata = await storageLayer.updateObjectMetadata({
394
386
  bucketId: req.params.bucketId,
395
387
  decodedObjectId: decodeURIComponent(req.params.objectId),
396
388
  metadata: req.body,
@@ -427,7 +419,7 @@ function createFirebaseEndpoints(emulator) {
427
419
  firebaseStorageAPI.post("/b/:bucketId/o/:objectId?", handleObjectPostRequest);
428
420
  firebaseStorageAPI.delete("/b/:bucketId/o/:objectId", async (req, res) => {
429
421
  try {
430
- await storageLayer.handleDeleteObject({
422
+ await storageLayer.deleteObject({
431
423
  bucketId: req.params.bucketId,
432
424
  decodedObjectId: decodeURIComponent(req.params.objectId),
433
425
  authorization: req.header("authorization"),
@@ -11,33 +11,34 @@ const crc_1 = require("../crc");
11
11
  const multipart_1 = require("../multipart");
12
12
  const upload_1 = require("../upload");
13
13
  const errors_1 = require("../errors");
14
+ const request_1 = require("../../shared/request");
14
15
  function createCloudEndpoints(emulator) {
15
16
  const gcloudStorageAPI = (0, express_1.Router)();
16
- const { storageLayer, uploadService } = emulator;
17
+ const { adminStorageLayer, uploadService } = emulator;
17
18
  gcloudStorageAPI.use(/.*\/b\/(.+?)\/.*/, (req, res, next) => {
18
- storageLayer.createBucket(req.params[0]);
19
+ adminStorageLayer.createBucket(req.params[0]);
19
20
  next();
20
21
  });
21
22
  gcloudStorageAPI.get("/b", async (req, res) => {
22
23
  res.json({
23
24
  kind: "storage#buckets",
24
- items: await storageLayer.listBuckets(),
25
+ items: await adminStorageLayer.listBuckets(),
25
26
  });
26
27
  });
27
28
  gcloudStorageAPI.get(["/b/:bucketId/o/:objectId", "/download/storage/v1/b/:bucketId/o/:objectId"], async (req, res) => {
28
29
  let getObjectResponse;
29
30
  try {
30
- getObjectResponse = await storageLayer.handleGetObject({
31
+ getObjectResponse = await adminStorageLayer.getObject({
31
32
  bucketId: req.params.bucketId,
32
33
  decodedObjectId: req.params.objectId,
33
- }, true);
34
+ });
34
35
  }
35
36
  catch (err) {
36
37
  if (err instanceof errors_1.NotFoundError) {
37
38
  return sendObjectNotFound(req, res);
38
39
  }
39
40
  if (err instanceof errors_1.ForbiddenError) {
40
- throw new Error("Request failed unexpectedly due to Firebase Rules.");
41
+ return res.sendStatus(403);
41
42
  }
42
43
  throw err;
43
44
  }
@@ -49,67 +50,67 @@ function createCloudEndpoints(emulator) {
49
50
  gcloudStorageAPI.patch("/b/:bucketId/o/:objectId", async (req, res) => {
50
51
  let updatedMetadata;
51
52
  try {
52
- updatedMetadata = await storageLayer.handleUpdateObjectMetadata({
53
+ updatedMetadata = await adminStorageLayer.updateObjectMetadata({
53
54
  bucketId: req.params.bucketId,
54
55
  decodedObjectId: req.params.objectId,
55
56
  metadata: req.body,
56
- }, true);
57
+ });
57
58
  }
58
59
  catch (err) {
59
60
  if (err instanceof errors_1.NotFoundError) {
60
61
  return sendObjectNotFound(req, res);
61
62
  }
62
63
  if (err instanceof errors_1.ForbiddenError) {
63
- throw new Error("Request failed unexpectedly due to Firebase Rules.");
64
+ return res.sendStatus(403);
64
65
  }
65
66
  throw err;
66
67
  }
67
68
  return res.json(new metadata_1.CloudStorageObjectMetadata(updatedMetadata));
68
69
  });
69
- gcloudStorageAPI.get("/b/:bucketId/o", (req, res) => {
70
- let maxRes = undefined;
71
- if (req.query.maxResults) {
72
- maxRes = +req.query.maxResults.toString();
73
- }
74
- const delimiter = req.query.delimiter ? req.query.delimiter.toString() : "";
75
- const pageToken = req.query.pageToken ? req.query.pageToken.toString() : undefined;
76
- const prefix = req.query.prefix ? req.query.prefix.toString() : "";
77
- const listResult = storageLayer.listItems(req.params.bucketId, prefix, delimiter, pageToken, maxRes);
78
- res.json(Object.assign(Object.assign({}, listResult), { kind: "#storage/objects" }));
70
+ gcloudStorageAPI.get("/b/:bucketId/o", async (req, res) => {
71
+ var _a;
72
+ let listResponse;
73
+ try {
74
+ listResponse = await adminStorageLayer.listObjects({
75
+ bucketId: req.params.bucketId,
76
+ prefix: req.query.prefix ? req.query.prefix.toString() : "",
77
+ delimiter: req.query.delimiter ? req.query.delimiter.toString() : "",
78
+ pageToken: req.query.pageToken ? req.query.pageToken.toString() : undefined,
79
+ maxResults: req.query.maxResults ? +req.query.maxResults.toString() : undefined,
80
+ authorization: req.header("authorization"),
81
+ });
82
+ }
83
+ catch (err) {
84
+ if (err instanceof errors_1.ForbiddenError) {
85
+ return res.sendStatus(403);
86
+ }
87
+ throw err;
88
+ }
89
+ return res.status(200).json({
90
+ kind: "#storage/objects",
91
+ nextPageToken: listResponse.nextPageToken,
92
+ prefixes: listResponse.prefixes,
93
+ items: (_a = listResponse.items) === null || _a === void 0 ? void 0 : _a.map((item) => new metadata_1.CloudStorageObjectMetadata(item)),
94
+ });
79
95
  });
80
96
  gcloudStorageAPI.delete("/b/:bucketId/o/:objectId", async (req, res) => {
81
97
  try {
82
- await storageLayer.handleDeleteObject({
98
+ await adminStorageLayer.deleteObject({
83
99
  bucketId: req.params.bucketId,
84
100
  decodedObjectId: req.params.objectId,
85
- }, true);
101
+ });
86
102
  }
87
103
  catch (err) {
88
104
  if (err instanceof errors_1.NotFoundError) {
89
105
  return sendObjectNotFound(req, res);
90
106
  }
91
107
  if (err instanceof errors_1.ForbiddenError) {
92
- throw new Error("Request failed unexpectedly due to Firebase Rules.");
108
+ return res.sendStatus(403);
93
109
  }
94
110
  throw err;
95
111
  }
96
112
  return res.sendStatus(204);
97
113
  });
98
- const reqBodyToBuffer = async (req) => {
99
- if (req.body instanceof Buffer) {
100
- return Buffer.from(req.body);
101
- }
102
- const bufs = [];
103
- req.on("data", (data) => {
104
- bufs.push(data);
105
- });
106
- await new Promise((resolve) => {
107
- req.on("end", () => {
108
- resolve();
109
- });
110
- });
111
- return Buffer.concat(bufs);
112
- };
113
114
  gcloudStorageAPI.put("/upload/storage/v1/b/:bucketId/o", async (req, res) => {
114
115
  if (!req.query.upload_id) {
115
116
  res.sendStatus(400);
@@ -118,7 +119,7 @@ function createCloudEndpoints(emulator) {
118
119
  const uploadId = req.query.upload_id.toString();
119
120
  let upload;
120
121
  try {
121
- uploadService.continueResumableUpload(uploadId, await reqBodyToBuffer(req));
122
+ uploadService.continueResumableUpload(uploadId, await (0, request_1.reqBodyToBuffer)(req));
122
123
  upload = uploadService.finalizeResumableUpload(uploadId);
123
124
  }
124
125
  catch (err) {
@@ -132,11 +133,11 @@ function createCloudEndpoints(emulator) {
132
133
  }
133
134
  let metadata;
134
135
  try {
135
- metadata = await storageLayer.handleUploadObject(upload, true);
136
+ metadata = await adminStorageLayer.uploadObject(upload);
136
137
  }
137
138
  catch (err) {
138
139
  if (err instanceof errors_1.ForbiddenError) {
139
- throw new Error("Request failed unexpectedly due to Firebase Rules.");
140
+ return res.sendStatus(403);
140
141
  }
141
142
  throw err;
142
143
  }
@@ -147,17 +148,17 @@ function createCloudEndpoints(emulator) {
147
148
  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.");
148
149
  let getObjectResponse;
149
150
  try {
150
- getObjectResponse = await storageLayer.handleGetObject({
151
+ getObjectResponse = await adminStorageLayer.getObject({
151
152
  bucketId: req.params.bucketId,
152
153
  decodedObjectId: req.params.objectId,
153
- }, true);
154
+ });
154
155
  }
155
156
  catch (err) {
156
157
  if (err instanceof errors_1.NotFoundError) {
157
158
  return sendObjectNotFound(req, res);
158
159
  }
159
160
  if (err instanceof errors_1.ForbiddenError) {
160
- throw new Error("Request failed unexpectedly due to Firebase Rules.");
161
+ return res.sendStatus(403);
161
162
  }
162
163
  throw err;
163
164
  }
@@ -206,7 +207,7 @@ function createCloudEndpoints(emulator) {
206
207
  let metadataRaw;
207
208
  let dataRaw;
208
209
  try {
209
- ({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await reqBodyToBuffer(req)));
210
+ ({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await (0, request_1.reqBodyToBuffer)(req)));
210
211
  }
211
212
  catch (err) {
212
213
  return res.status(400).json({
@@ -225,11 +226,11 @@ function createCloudEndpoints(emulator) {
225
226
  });
226
227
  let metadata;
227
228
  try {
228
- metadata = await storageLayer.handleUploadObject(upload, true);
229
+ metadata = await adminStorageLayer.uploadObject(upload);
229
230
  }
230
231
  catch (err) {
231
232
  if (err instanceof errors_1.ForbiddenError) {
232
- throw new Error("Request failed unexpectedly due to Firebase Rules.");
233
+ return res.sendStatus(403);
233
234
  }
234
235
  throw err;
235
236
  }
@@ -238,34 +239,45 @@ function createCloudEndpoints(emulator) {
238
239
  gcloudStorageAPI.get("/:bucketId/:objectId(**)", async (req, res) => {
239
240
  let getObjectResponse;
240
241
  try {
241
- getObjectResponse = await storageLayer.handleGetObject({
242
+ getObjectResponse = await adminStorageLayer.getObject({
242
243
  bucketId: req.params.bucketId,
243
244
  decodedObjectId: req.params.objectId,
244
- }, true);
245
+ });
245
246
  }
246
247
  catch (err) {
247
248
  if (err instanceof errors_1.NotFoundError) {
248
249
  return sendObjectNotFound(req, res);
249
250
  }
250
251
  if (err instanceof errors_1.ForbiddenError) {
251
- throw new Error("Request failed unexpectedly due to Firebase Rules.");
252
+ return res.sendStatus(403);
252
253
  }
253
254
  throw err;
254
255
  }
255
256
  return sendFileBytes(getObjectResponse.metadata, getObjectResponse.data, req, res);
256
257
  });
257
258
  gcloudStorageAPI.post("/b/:bucketId/o/:objectId/:method(rewriteTo|copyTo)/b/:destBucketId/o/:destObjectId", (req, res, next) => {
258
- const md = storageLayer.getMetadata(req.params.bucketId, req.params.objectId);
259
- if (!md) {
260
- return sendObjectNotFound(req, res);
261
- }
262
259
  if (req.params.method === "rewriteTo" && req.query.rewriteToken) {
263
260
  return next();
264
261
  }
265
- const metadata = storageLayer.copyFile(md, req.params.destBucketId, req.params.destObjectId, req.body);
266
- if (!metadata) {
267
- res.sendStatus(400);
268
- return;
262
+ let metadata;
263
+ try {
264
+ metadata = adminStorageLayer.copyObject({
265
+ sourceBucket: req.params.bucketId,
266
+ sourceObject: req.params.objectId,
267
+ destinationBucket: req.params.destBucketId,
268
+ destinationObject: req.params.destObjectId,
269
+ incomingMetadata: req.body,
270
+ authorization: "Bearer owner",
271
+ });
272
+ }
273
+ catch (err) {
274
+ if (err instanceof errors_1.NotFoundError) {
275
+ return sendObjectNotFound(req, res);
276
+ }
277
+ if (err instanceof errors_1.ForbiddenError) {
278
+ return res.sendStatus(403);
279
+ }
280
+ throw err;
269
281
  }
270
282
  const resource = new metadata_1.CloudStorageObjectMetadata(metadata);
271
283
  res.status(200);
@@ -9,12 +9,10 @@ var __asyncValues = (this && this.__asyncValues) || function (o) {
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.StorageLayer = exports.StoredFile = void 0;
11
11
  const fs_1 = require("fs");
12
- const list_1 = require("./list");
13
12
  const metadata_1 = require("./metadata");
14
13
  const errors_1 = require("./errors");
15
14
  const path = require("path");
16
15
  const fse = require("fs-extra");
17
- const cloudFunctions_1 = require("./cloudFunctions");
18
16
  const logger_1 = require("../../logger");
19
17
  const adminSdkConfig_1 = require("../adminSdkConfig");
20
18
  const types_1 = require("./rules/types");
@@ -39,17 +37,14 @@ class StoredFile {
39
37
  }
40
38
  exports.StoredFile = StoredFile;
41
39
  class StorageLayer {
42
- constructor(_projectId, _rulesValidator, _adminCredsValidator, _persistence) {
40
+ constructor(_projectId, _files, _buckets, _rulesValidator, _adminCredsValidator, _persistence, _cloudFunctions) {
43
41
  this._projectId = _projectId;
42
+ this._files = _files;
43
+ this._buckets = _buckets;
44
44
  this._rulesValidator = _rulesValidator;
45
45
  this._adminCredsValidator = _adminCredsValidator;
46
46
  this._persistence = _persistence;
47
- this.reset();
48
- this._cloudFunctions = new cloudFunctions_1.StorageCloudFunctions(this._projectId);
49
- }
50
- reset() {
51
- this._files = new Map();
52
- this._buckets = new Map();
47
+ this._cloudFunctions = _cloudFunctions;
53
48
  }
54
49
  createBucket(id) {
55
50
  if (!this._buckets.has(id)) {
@@ -66,11 +61,11 @@ class StorageLayer {
66
61
  }
67
62
  return [...this._buckets.values()];
68
63
  }
69
- async handleGetObject(request, skipAuth = false) {
64
+ async getObject(request) {
70
65
  var _a;
71
66
  const metadata = this.getMetadata(request.bucketId, request.decodedObjectId);
72
67
  const hasValidDownloadToken = ((metadata === null || metadata === void 0 ? void 0 : metadata.downloadTokens) || []).includes((_a = request.downloadToken) !== null && _a !== void 0 ? _a : "");
73
- let authorized = skipAuth || hasValidDownloadToken;
68
+ let authorized = hasValidDownloadToken;
74
69
  if (!authorized) {
75
70
  authorized = await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.GET, { before: metadata === null || metadata === void 0 ? void 0 : metadata.asRulesResource() }, request.authorization);
76
71
  }
@@ -99,10 +94,9 @@ class StorageLayer {
99
94
  }
100
95
  return undefined;
101
96
  }
102
- async handleDeleteObject(request, skipAuth = false) {
97
+ async deleteObject(request) {
103
98
  const storedMetadata = this.getMetadata(request.bucketId, request.decodedObjectId);
104
- const authorized = skipAuth ||
105
- (await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.DELETE, { before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource() }, request.authorization));
99
+ const authorized = await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.DELETE, { before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource() }, request.authorization);
106
100
  if (!authorized) {
107
101
  throw new errors_1.ForbiddenError();
108
102
  }
@@ -131,13 +125,12 @@ class StorageLayer {
131
125
  return true;
132
126
  }
133
127
  }
134
- async handleUpdateObjectMetadata(request, skipAuth = false) {
128
+ async updateObjectMetadata(request) {
135
129
  const storedMetadata = this.getMetadata(request.bucketId, request.decodedObjectId);
136
- const authorized = skipAuth ||
137
- (await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.UPDATE, {
138
- before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource(),
139
- after: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource(request.metadata),
140
- }, request.authorization));
130
+ const authorized = await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.UPDATE, {
131
+ before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource(),
132
+ after: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource(request.metadata),
133
+ }, request.authorization);
141
134
  if (!authorized) {
142
135
  throw new errors_1.ForbiddenError();
143
136
  }
@@ -147,7 +140,7 @@ class StorageLayer {
147
140
  storedMetadata.update(request.metadata);
148
141
  return storedMetadata;
149
142
  }
150
- async handleUploadObject(upload, skipAuth = false) {
143
+ async uploadObject(upload) {
151
144
  if (upload.status !== upload_1.UploadStatus.FINISHED) {
152
145
  throw new Error(`Unexpected upload status encountered: ${upload.status}.`);
153
146
  }
@@ -162,8 +155,7 @@ class StorageLayer {
162
155
  cacheControl: upload.metadata.cacheControl,
163
156
  customMetadata: upload.metadata.metadata,
164
157
  }, this._cloudFunctions, this._persistence.readBytes(upload.path, upload.size));
165
- const authorized = skipAuth ||
166
- (await this._rulesValidator.validate(["b", upload.bucketId, "o", upload.objectId].join("/"), upload.bucketId, types_1.RulesetOperationMethod.CREATE, { after: metadata === null || metadata === void 0 ? void 0 : metadata.asRulesResource() }, upload.authorization));
158
+ const authorized = await this._rulesValidator.validate(["b", upload.bucketId, "o", upload.objectId].join("/"), upload.bucketId, types_1.RulesetOperationMethod.CREATE, { after: metadata === null || metadata === void 0 ? void 0 : metadata.asRulesResource() }, upload.authorization);
167
159
  if (!authorized) {
168
160
  this._persistence.deleteFile(upload.path);
169
161
  throw new errors_1.ForbiddenError();
@@ -174,17 +166,24 @@ class StorageLayer {
174
166
  this._cloudFunctions.dispatch("finalize", new metadata_1.CloudStorageObjectMetadata(metadata));
175
167
  return metadata;
176
168
  }
177
- copyFile(sourceFile, destinationBucket, destinationObject, incomingMetadata) {
178
- const filePath = this.path(destinationBucket, destinationObject);
179
- this._persistence.deleteFile(filePath, true);
180
- const bytes = this.getBytes(sourceFile.bucket, sourceFile.name);
181
- this._persistence.appendBytes(filePath, bytes);
182
- const newMetadata = Object.assign(Object.assign(Object.assign({}, sourceFile), { metadata: sourceFile.customMetadata }), incomingMetadata);
183
- if (sourceFile.downloadTokens.length &&
169
+ copyObject({ sourceBucket, sourceObject, destinationBucket, destinationObject, incomingMetadata, authorization, }) {
170
+ if (!this._adminCredsValidator.validate(authorization)) {
171
+ throw new errors_1.ForbiddenError();
172
+ }
173
+ const sourceMetadata = this.getMetadata(sourceBucket, sourceObject);
174
+ if (!sourceMetadata) {
175
+ throw new errors_1.NotFoundError();
176
+ }
177
+ const sourceBytes = this.getBytes(sourceBucket, sourceObject);
178
+ const destinationFilePath = this.path(destinationBucket, destinationObject);
179
+ this._persistence.deleteFile(destinationFilePath, true);
180
+ this._persistence.appendBytes(destinationFilePath, sourceBytes);
181
+ const newMetadata = Object.assign(Object.assign(Object.assign({}, sourceMetadata), { metadata: sourceMetadata.customMetadata }), incomingMetadata);
182
+ if (sourceMetadata.downloadTokens.length &&
184
183
  !((incomingMetadata === null || incomingMetadata === void 0 ? void 0 : incomingMetadata.metadata) && Object.keys(incomingMetadata === null || incomingMetadata === void 0 ? void 0 : incomingMetadata.metadata).length)) {
185
184
  if (!newMetadata.metadata)
186
185
  newMetadata.metadata = {};
187
- newMetadata.metadata.firebaseStorageDownloadTokens = sourceFile.downloadTokens.join(",");
186
+ newMetadata.metadata.firebaseStorageDownloadTokens = sourceMetadata.downloadTokens.join(",");
188
187
  }
189
188
  if (newMetadata.metadata) {
190
189
  for (const [k, v] of Object.entries(newMetadata.metadata)) {
@@ -201,27 +200,23 @@ class StorageLayer {
201
200
  contentLanguage: newMetadata.contentLanguage,
202
201
  cacheControl: newMetadata.cacheControl,
203
202
  customMetadata: newMetadata.metadata,
204
- }, this._cloudFunctions, bytes, incomingMetadata);
205
- const file = new StoredFile(copiedFileMetadata, this._persistence.getDiskPath(filePath));
206
- this._files.set(filePath, file);
203
+ }, this._cloudFunctions, sourceBytes, incomingMetadata);
204
+ const file = new StoredFile(copiedFileMetadata, this._persistence.getDiskPath(destinationFilePath));
205
+ this._files.set(destinationFilePath, file);
207
206
  this._cloudFunctions.dispatch("finalize", new metadata_1.CloudStorageObjectMetadata(file.metadata));
208
207
  return file.metadata;
209
208
  }
210
- async handleListObjects(request, skipAuth = false) {
211
- var _a, _b, _c;
212
- const authorized = skipAuth ||
213
- (await this._rulesValidator.validate(["b", request.bucketId, "o", request.prefix].join("/"), request.bucketId, types_1.RulesetOperationMethod.LIST, {}, request.authorization));
209
+ async listObjects(request) {
210
+ var _a;
211
+ const { bucketId, prefix, delimiter, pageToken, authorization } = request;
212
+ const authorized = await this._rulesValidator.validate(["b", bucketId, "o", prefix].join("/"), bucketId, types_1.RulesetOperationMethod.LIST, {}, authorization);
214
213
  if (!authorized) {
215
214
  throw new errors_1.ForbiddenError();
216
215
  }
217
- const itemsResults = this.listItems(request.bucketId, request.prefix, request.delimiter, request.pageToken, request.maxResults);
218
- return new list_1.ListResponse((_a = itemsResults.prefixes) !== null && _a !== void 0 ? _a : [], (_c = (_b = itemsResults.items) === null || _b === void 0 ? void 0 : _b.map((i) => new list_1.ListItem(i.name, i.bucket))) !== null && _c !== void 0 ? _c : [], itemsResults.nextPageToken);
219
- }
220
- listItems(bucket, prefix, delimiter, pageToken, maxResults) {
221
216
  let items = [];
222
217
  const prefixes = new Set();
223
218
  for (const [, file] of this._files) {
224
- if (file.metadata.bucket !== bucket) {
219
+ if (file.metadata.bucket !== bucketId) {
225
220
  continue;
226
221
  }
227
222
  const name = file.metadata.name;
@@ -258,9 +253,7 @@ class StorageLayer {
258
253
  items = items.slice(idx);
259
254
  }
260
255
  }
261
- if (!maxResults) {
262
- maxResults = 1000;
263
- }
256
+ const maxResults = (_a = request.maxResults) !== null && _a !== void 0 ? _a : 1000;
264
257
  let nextPageToken = undefined;
265
258
  if (items.length > maxResults) {
266
259
  nextPageToken = items[maxResults].name;
@@ -269,10 +262,10 @@ class StorageLayer {
269
262
  return {
270
263
  nextPageToken,
271
264
  prefixes: prefixes.size > 0 ? [...prefixes].sort() : undefined,
272
- items: items.length > 0 ? items.map((item) => new metadata_1.CloudStorageObjectMetadata(item)) : undefined,
265
+ items: items.length > 0 ? items : undefined,
273
266
  };
274
267
  }
275
- handleCreateDownloadToken(request) {
268
+ createDownloadToken(request) {
276
269
  if (!this._adminCredsValidator.validate(request.authorization)) {
277
270
  throw new errors_1.ForbiddenError();
278
271
  }
@@ -283,7 +276,7 @@ class StorageLayer {
283
276
  metadata.addDownloadToken();
284
277
  return metadata;
285
278
  }
286
- handleDeleteDownloadToken(request) {
279
+ deleteDownloadToken(request) {
287
280
  if (!this._adminCredsValidator.validate(request.authorization)) {
288
281
  throw new errors_1.ForbiddenError();
289
282
  }
@@ -13,19 +13,30 @@ const runtime_1 = require("./rules/runtime");
13
13
  const utils_1 = require("./rules/utils");
14
14
  const persistence_1 = require("./persistence");
15
15
  const upload_1 = require("./upload");
16
+ const cloudFunctions_1 = require("./cloudFunctions");
16
17
  class StorageEmulator {
17
18
  constructor(args) {
18
19
  this.args = args;
19
20
  this._logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE);
21
+ this._files = new Map();
22
+ this._buckets = new Map();
20
23
  this._rulesRuntime = new runtime_1.StorageRulesRuntime();
21
24
  this._rulesManager = (0, manager_1.createStorageRulesManager)(this.args.rules, this._rulesRuntime);
25
+ this._cloudFunctions = new cloudFunctions_1.StorageCloudFunctions(args.projectId);
22
26
  this._persistence = new persistence_1.Persistence(this.getPersistenceTmpDir());
23
- this._storageLayer = new files_1.StorageLayer(args.projectId, (0, utils_1.getRulesValidator)((resource) => this._rulesManager.getRuleset(resource)), (0, utils_1.getAdminCredentialValidator)(), this._persistence);
24
27
  this._uploadService = new upload_1.UploadService(this._persistence);
28
+ const createStorageLayer = (rulesValidator) => {
29
+ return new files_1.StorageLayer(args.projectId, this._files, this._buckets, rulesValidator, (0, utils_1.getAdminCredentialValidator)(), this._persistence, this._cloudFunctions);
30
+ };
31
+ this._storageLayer = createStorageLayer((0, utils_1.getFirebaseRulesValidator)((resource) => this._rulesManager.getRuleset(resource)));
32
+ this._adminStorageLayer = createStorageLayer((0, utils_1.getAdminOnlyFirebaseRulesValidator)());
25
33
  }
26
34
  get storageLayer() {
27
35
  return this._storageLayer;
28
36
  }
37
+ get adminStorageLayer() {
38
+ return this._adminStorageLayer;
39
+ }
29
40
  get uploadService() {
30
41
  return this._uploadService;
31
42
  }
@@ -36,7 +47,8 @@ class StorageEmulator {
36
47
  return this._logger;
37
48
  }
38
49
  reset() {
39
- this._storageLayer.reset();
50
+ this._files.clear();
51
+ this._buckets.clear();
40
52
  this._persistence.reset(this.getPersistenceTmpDir());
41
53
  this._uploadService.reset();
42
54
  }
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isPermitted = exports.getAdminCredentialValidator = exports.getRulesValidator = void 0;
3
+ exports.isPermitted = exports.getAdminCredentialValidator = exports.getAdminOnlyFirebaseRulesValidator = exports.getFirebaseRulesValidator = void 0;
4
4
  const emulatorLogger_1 = require("../../emulatorLogger");
5
5
  const types_1 = require("../../types");
6
- function getRulesValidator(rulesetProvider) {
6
+ function getFirebaseRulesValidator(rulesetProvider) {
7
7
  return {
8
8
  validate: async (path, bucketId, method, variableOverrides, authorization) => {
9
9
  return await isPermitted({
@@ -16,7 +16,15 @@ function getRulesValidator(rulesetProvider) {
16
16
  },
17
17
  };
18
18
  }
19
- exports.getRulesValidator = getRulesValidator;
19
+ exports.getFirebaseRulesValidator = getFirebaseRulesValidator;
20
+ function getAdminOnlyFirebaseRulesValidator() {
21
+ return {
22
+ validate: (_path, _bucketId, _method, _variableOverrides, _authorization) => {
23
+ return Promise.resolve(true);
24
+ },
25
+ };
26
+ }
27
+ exports.getAdminOnlyFirebaseRulesValidator = getAdminOnlyFirebaseRulesValidator;
20
28
  function getAdminCredentialValidator() {
21
29
  return { validate: isValidAdminCredentials };
22
30
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "10.4.2",
3
+ "version": "10.5.0",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "firebase-tools",
9
- "version": "10.4.2",
9
+ "version": "10.5.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@google-cloud/pubsub": "^2.18.4",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "10.4.2",
3
+ "version": "10.5.0",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {
@@ -1,18 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ListResponse = exports.ListItem = void 0;
4
- class ListItem {
5
- constructor(name, bucket) {
6
- this.name = name;
7
- this.bucket = bucket;
8
- }
9
- }
10
- exports.ListItem = ListItem;
11
- class ListResponse {
12
- constructor(prefixes, items, nextPageToken) {
13
- this.prefixes = prefixes;
14
- this.items = items;
15
- this.nextPageToken = nextPageToken;
16
- }
17
- }
18
- exports.ListResponse = ListResponse;