firebase-tools 10.2.0 → 10.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/lib/apiv2.js +3 -0
  2. package/lib/appdistribution/options-parser-util.js +1 -1
  3. package/lib/auth.js +3 -3
  4. package/lib/command.js +1 -1
  5. package/lib/commands/apps-android-sha-create.js +2 -2
  6. package/lib/commands/apps-sdkconfig.js +1 -1
  7. package/lib/commands/auth-import.js +1 -1
  8. package/lib/commands/database-rules-list.js +2 -2
  9. package/lib/commands/emulators-start.js +1 -1
  10. package/lib/commands/ext-configure.js +58 -4
  11. package/lib/commands/ext-dev-init.js +49 -49
  12. package/lib/commands/ext-export.js +7 -2
  13. package/lib/commands/ext-install.js +163 -104
  14. package/lib/commands/ext-uninstall.js +17 -8
  15. package/lib/commands/ext-update.js +64 -11
  16. package/lib/commands/functions-config-clone.js +1 -1
  17. package/lib/commands/functions-config-export.js +1 -1
  18. package/lib/commands/hosting-clone.js +3 -3
  19. package/lib/commands/remoteconfig-get.js +1 -1
  20. package/lib/config.js +6 -3
  21. package/lib/deploy/extensions/deploymentSummary.js +3 -3
  22. package/lib/deploy/extensions/planner.js +7 -6
  23. package/lib/deploy/extensions/tasks.js +1 -1
  24. package/lib/deploy/functions/backend.js +21 -5
  25. package/lib/deploy/functions/checkIam.js +5 -5
  26. package/lib/deploy/functions/containerCleaner.js +3 -3
  27. package/lib/deploy/functions/ensure.js +3 -3
  28. package/lib/deploy/functions/functionsDeployHelper.js +2 -2
  29. package/lib/deploy/functions/prepare.js +5 -3
  30. package/lib/deploy/functions/pricing.js +1 -1
  31. package/lib/deploy/functions/prompts.js +2 -2
  32. package/lib/deploy/functions/release/fabricator.js +7 -7
  33. package/lib/deploy/functions/release/index.js +1 -1
  34. package/lib/deploy/functions/release/planner.js +43 -26
  35. package/lib/deploy/functions/release/reporter.js +3 -0
  36. package/lib/deploy/functions/runtimes/discovery/index.js +6 -6
  37. package/lib/deploy/functions/runtimes/discovery/parsing.js +1 -1
  38. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +22 -12
  39. package/lib/deploy/functions/runtimes/golang/index.js +2 -2
  40. package/lib/deploy/functions/runtimes/node/index.js +53 -0
  41. package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +2 -2
  42. package/lib/deploy/functions/runtimes/node/parseTriggers.js +52 -15
  43. package/lib/deploy/functions/runtimes/node/versioning.js +2 -2
  44. package/lib/deploy/functions/services/firebaseAlerts.js +30 -0
  45. package/lib/deploy/functions/services/index.js +9 -1
  46. package/lib/deploy/functions/services/storage.js +10 -4
  47. package/lib/deploy/functions/triggerRegionHelper.js +1 -1
  48. package/lib/deploy/functions/validate.js +3 -3
  49. package/lib/deploy/hosting/client.js +9 -0
  50. package/lib/deploy/hosting/convertConfig.js +6 -0
  51. package/lib/deploy/hosting/deploy.js +2 -2
  52. package/lib/deploy/hosting/hashcache.js +21 -19
  53. package/lib/deploy/hosting/index.js +5 -5
  54. package/lib/deploy/hosting/prepare.js +25 -25
  55. package/lib/deploy/hosting/release.js +21 -24
  56. package/lib/deploy/hosting/uploader.js +5 -5
  57. package/lib/deploy/remoteconfig/functions.js +2 -2
  58. package/lib/emulator/auth/cloudFunctions.js +1 -1
  59. package/lib/emulator/auth/operations.js +1 -1
  60. package/lib/emulator/commandUtils.js +5 -1
  61. package/lib/emulator/constants.js +4 -0
  62. package/lib/emulator/controller.js +54 -24
  63. package/lib/emulator/download.js +18 -1
  64. package/lib/emulator/downloadableEmulators.js +30 -13
  65. package/lib/emulator/emulatorLogger.js +12 -1
  66. package/lib/emulator/extensions/validation.js +70 -0
  67. package/lib/emulator/extensionsEmulator.js +175 -0
  68. package/lib/emulator/functionsEmulator.js +106 -44
  69. package/lib/emulator/functionsEmulatorRuntime.js +44 -36
  70. package/lib/emulator/functionsEmulatorShared.js +17 -10
  71. package/lib/emulator/functionsEmulatorShell.js +1 -1
  72. package/lib/emulator/functionsEmulatorUtils.js +4 -4
  73. package/lib/emulator/functionsRuntimeWorker.js +2 -2
  74. package/lib/emulator/hub.js +4 -3
  75. package/lib/emulator/loggingEmulator.js +1 -1
  76. package/lib/emulator/pubsubEmulator.js +1 -1
  77. package/lib/emulator/registry.js +10 -2
  78. package/lib/emulator/storage/apis/firebase.js +314 -332
  79. package/lib/emulator/storage/apis/gcloud.js +241 -121
  80. package/lib/emulator/storage/crc.js +5 -1
  81. package/lib/emulator/storage/errors.js +9 -0
  82. package/lib/emulator/storage/files.js +159 -300
  83. package/lib/emulator/storage/index.js +27 -73
  84. package/lib/emulator/storage/metadata.js +65 -51
  85. package/lib/emulator/storage/multipart.js +62 -0
  86. package/lib/emulator/storage/persistence.js +78 -0
  87. package/lib/emulator/storage/rules/config.js +33 -0
  88. package/lib/emulator/storage/rules/manager.js +81 -0
  89. package/lib/emulator/storage/rules/runtime.js +8 -7
  90. package/lib/emulator/storage/rules/utils.js +48 -0
  91. package/lib/emulator/storage/server.js +2 -2
  92. package/lib/emulator/storage/upload.js +106 -0
  93. package/lib/emulator/types.js +3 -0
  94. package/lib/ensureApiEnabled.js +5 -1
  95. package/lib/error.js +1 -1
  96. package/lib/extensions/askUserForParam.js +1 -1
  97. package/lib/extensions/changelog.js +3 -1
  98. package/lib/extensions/checkProjectBilling.js +1 -1
  99. package/lib/extensions/displayExtensionInfo.js +1 -1
  100. package/lib/extensions/emulator/optionsHelper.js +56 -8
  101. package/lib/extensions/emulator/specHelper.js +10 -23
  102. package/lib/extensions/export.js +1 -51
  103. package/lib/extensions/extensionsApi.js +1 -1
  104. package/lib/extensions/extensionsHelper.js +32 -19
  105. package/lib/extensions/listExtensions.js +2 -0
  106. package/lib/extensions/manifest.js +144 -0
  107. package/lib/extensions/metricsUtils.js +4 -4
  108. package/lib/extensions/paramHelper.js +9 -8
  109. package/lib/extensions/refs.js +1 -1
  110. package/lib/extensions/secretsUtils.js +3 -3
  111. package/lib/functional.js +1 -1
  112. package/lib/functions/env.js +6 -7
  113. package/lib/functions/events/v2.js +11 -0
  114. package/lib/gcp/cloudfunctions.js +42 -11
  115. package/lib/gcp/cloudfunctionsv2.js +48 -17
  116. package/lib/gcp/cloudtasks.js +1 -1
  117. package/lib/gcp/docker.js +2 -2
  118. package/lib/gcp/resourceManager.js +4 -4
  119. package/lib/gcp/run.js +2 -2
  120. package/lib/gcp/storage.js +1 -0
  121. package/lib/hosting/api.js +1 -1
  122. package/lib/hosting/functionsProxy.js +15 -5
  123. package/lib/hosting/proxy.js +2 -2
  124. package/lib/init/features/account.js +1 -1
  125. package/lib/management/database.js +1 -1
  126. package/lib/previews.js +1 -1
  127. package/lib/responseToError.js +16 -7
  128. package/lib/serve/functions.js +2 -1
  129. package/lib/serve/hosting.js +1 -1
  130. package/lib/utils.js +15 -2
  131. package/npm-shrinkwrap.json +904 -412
  132. package/package.json +3 -3
  133. package/schema/firebase-config.json +32 -0
  134. package/templates/init/functions/javascript/package.lint.json +3 -3
  135. package/templates/init/functions/javascript/package.nolint.json +2 -2
  136. package/templates/init/functions/typescript/package.lint.json +7 -7
  137. package/templates/init/functions/typescript/package.nolint.json +3 -3
  138. package/lib/deploy/extensions/params.js +0 -39
  139. package/lib/deploy/functions/eventTypes.js +0 -10
@@ -1,110 +1,61 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.StorageEmulator = void 0;
4
+ const os_1 = require("os");
4
5
  const utils = require("../../utils");
5
6
  const constants_1 = require("../constants");
6
7
  const types_1 = require("../types");
7
8
  const server_1 = require("./server");
8
9
  const files_1 = require("./files");
9
- const chokidar = require("chokidar");
10
10
  const emulatorLogger_1 = require("../emulatorLogger");
11
- const fs = require("fs");
11
+ const manager_1 = require("./rules/manager");
12
12
  const runtime_1 = require("./rules/runtime");
13
- const error_1 = require("../../error");
14
- const downloadableEmulators_1 = require("../downloadableEmulators");
13
+ const utils_1 = require("./rules/utils");
14
+ const persistence_1 = require("./persistence");
15
+ const upload_1 = require("./upload");
15
16
  class StorageEmulator {
16
17
  constructor(args) {
17
18
  this.args = args;
18
19
  this._logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE);
19
- const downloadDetails = (0, downloadableEmulators_1.getDownloadDetails)(types_1.Emulators.STORAGE);
20
20
  this._rulesRuntime = new runtime_1.StorageRulesRuntime();
21
- this._storageLayer = new files_1.StorageLayer(args.projectId);
21
+ this._rulesManager = new manager_1.StorageRulesManager(this._rulesRuntime);
22
+ this._persistence = new persistence_1.Persistence(this.getPersistenceTmpDir());
23
+ this._storageLayer = new files_1.StorageLayer(args.projectId, (0, utils_1.getRulesValidator)(() => this.rules), (0, utils_1.getAdminCredentialValidator)(), this._persistence);
24
+ this._uploadService = new upload_1.UploadService(this._persistence);
22
25
  }
23
26
  get storageLayer() {
24
27
  return this._storageLayer;
25
28
  }
29
+ get uploadService() {
30
+ return this._uploadService;
31
+ }
26
32
  get rules() {
27
- return this._rules;
33
+ return this._rulesManager.ruleset;
28
34
  }
29
35
  get logger() {
30
36
  return this._logger;
31
37
  }
38
+ reset() {
39
+ this._storageLayer.reset();
40
+ this._persistence.reset(this.getPersistenceTmpDir());
41
+ this._uploadService.reset();
42
+ }
32
43
  async start() {
33
44
  const { host, port } = this.getInfo();
34
45
  await this._rulesRuntime.start(this.args.auto_download);
46
+ await this._rulesManager.setSourceFile(this.args.rules[0].rules);
35
47
  this._app = await (0, server_1.createApp)(this.args.projectId, this);
36
- if (typeof this.args.rules == "string") {
37
- const rulesFile = this.args.rules;
38
- this.updateRulesSource(rulesFile);
39
- }
40
- else {
41
- this._rulesetSource = this.args.rules;
42
- }
43
- if (!this._rulesetSource || this._rulesetSource.files.length == 0) {
44
- throw new error_1.FirebaseError("Can not initialize Storage emulator without a rules source / file.");
45
- }
46
- else if (this._rulesetSource.files.length > 1) {
47
- throw new error_1.FirebaseError("Can not initialize Storage emulator with more than one rules source / file.");
48
- }
49
- await this.loadRuleset();
50
- const rulesPath = this._rulesetSource.files[0].name;
51
- this._rulesWatcher = chokidar.watch(rulesPath, { persistent: true, ignoreInitial: true });
52
- this._rulesWatcher.on("change", async () => {
53
- await new Promise((res) => setTimeout(res, 5));
54
- this._logger.logLabeled("BULLET", "storage", `Change detected, updating rules for Cloud Storage...`);
55
- this.updateRulesSource(rulesPath);
56
- await this.loadRuleset();
57
- });
58
48
  const server = this._app.listen(port, host);
59
49
  this.destroyServer = utils.createDestroyer(server);
60
50
  }
61
- updateRulesSource(rulesFile) {
62
- this._rulesetSource = {
63
- files: [
64
- {
65
- name: rulesFile,
66
- content: fs.readFileSync(rulesFile).toString(),
67
- },
68
- ],
69
- };
70
- }
71
- async loadRuleset(source) {
72
- if (source) {
73
- this._rulesetSource = source;
74
- }
75
- if (!this._rulesetSource) {
76
- const msg = "Attempting to update ruleset without a source.";
77
- this._logger.log("WARN", msg);
78
- const error = JSON.stringify({ error: msg });
79
- return new runtime_1.StorageRulesIssues([error], []);
80
- }
81
- const { ruleset, issues } = await this._rulesRuntime.loadRuleset(this._rulesetSource);
82
- if (!ruleset) {
83
- issues.all.forEach((issue) => {
84
- let parsedIssue;
85
- try {
86
- parsedIssue = JSON.parse(issue);
87
- }
88
- catch (_a) {
89
- }
90
- if (parsedIssue) {
91
- this._logger.log("WARN", `${parsedIssue.description_.replace(/\.$/, "")} in ${parsedIssue.sourcePosition_.fileName_}:${parsedIssue.sourcePosition_.line_}`);
92
- }
93
- else {
94
- this._logger.log("WARN", issue);
95
- }
96
- });
97
- delete this._rules;
98
- }
99
- else {
100
- this._rules = ruleset;
101
- }
102
- return issues;
103
- }
104
51
  async connect() {
105
52
  }
53
+ async setRules(rules) {
54
+ return this._rulesManager.setSourceFile(rules);
55
+ }
106
56
  async stop() {
107
- await this.storageLayer.deleteAll();
57
+ await this._persistence.deleteAll();
58
+ await this._rulesManager.close();
108
59
  return this.destroyServer ? this.destroyServer() : Promise.resolve();
109
60
  }
110
61
  getInfo() {
@@ -122,5 +73,8 @@ class StorageEmulator {
122
73
  getApp() {
123
74
  return this._app;
124
75
  }
76
+ getPersistenceTmpDir() {
77
+ return `${(0, os_1.tmpdir)()}/firebase/storage/blobs`;
78
+ }
125
79
  }
126
80
  exports.StorageEmulator = StorageEmulator;
@@ -15,14 +15,19 @@ class StoredFileMetadata {
15
15
  this.metageneration = opts.metageneration || 1;
16
16
  this.generation = opts.generation || Date.now();
17
17
  this.storageClass = opts.storageClass || "STANDARD";
18
- this.etag = opts.etag || "someETag";
19
18
  this.contentDisposition = opts.contentDisposition || "inline";
20
- this.cacheControl = opts.cacheControl;
19
+ this.cacheControl = opts.cacheControl || "public, max-age=3600";
21
20
  this.contentLanguage = opts.contentLanguage;
22
21
  this.customTime = opts.customTime;
23
22
  this.contentEncoding = opts.contentEncoding || "identity";
24
23
  this.customMetadata = opts.customMetadata;
25
24
  this.downloadTokens = opts.downloadTokens || [];
25
+ if (opts.etag) {
26
+ this.etag = opts.etag;
27
+ }
28
+ else {
29
+ this.etag = generateETag(this.generation, this.metageneration);
30
+ }
26
31
  this.timeCreated = opts.timeCreated ? new Date(opts.timeCreated) : new Date();
27
32
  this.updated = opts.updated ? new Date(opts.updated) : this.timeCreated;
28
33
  if (bytes) {
@@ -118,7 +123,10 @@ class StoredFileMetadata {
118
123
  this.contentType = incoming.contentType;
119
124
  }
120
125
  if (incoming.metadata) {
121
- this.customMetadata = incoming.metadata;
126
+ this.customMetadata = this.customMetadata ? Object.assign({}, this.customMetadata) : {};
127
+ for (const [k, v] of Object.entries(incoming.metadata)) {
128
+ this.customMetadata[k] = v === null ? null : String(v);
129
+ }
122
130
  }
123
131
  if (incoming.contentLanguage) {
124
132
  this.contentLanguage = incoming.contentLanguage;
@@ -149,9 +157,9 @@ class StoredFileMetadata {
149
157
  if (!this.downloadTokens.length) {
150
158
  return;
151
159
  }
152
- const remainingTokens = this.downloadTokens.filter((t) => t != token);
160
+ const remainingTokens = this.downloadTokens.filter((t) => t !== token);
153
161
  this.downloadTokens = remainingTokens;
154
- if (remainingTokens.length == 0) {
162
+ if (remainingTokens.length === 0) {
155
163
  this.addDownloadToken();
156
164
  }
157
165
  this.update({});
@@ -171,25 +179,25 @@ class StoredFileMetadata {
171
179
  }
172
180
  exports.StoredFileMetadata = StoredFileMetadata;
173
181
  class OutgoingFirebaseMetadata {
174
- constructor(md) {
175
- this.name = md.name;
176
- this.bucket = md.bucket;
177
- this.generation = md.generation.toString();
178
- this.metageneration = md.metageneration.toString();
179
- this.contentType = md.contentType;
180
- this.timeCreated = toSerializedDate(md.timeCreated);
181
- this.updated = toSerializedDate(md.updated);
182
- this.storageClass = md.storageClass;
183
- this.size = md.size.toString();
184
- this.md5Hash = md.md5Hash;
185
- this.crc32c = md.crc32c;
186
- this.etag = md.etag;
187
- this.downloadTokens = md.downloadTokens.join(",");
188
- this.contentEncoding = md.contentEncoding;
189
- this.contentDisposition = md.contentDisposition;
190
- this.metadata = md.customMetadata;
191
- this.contentLanguage = md.contentLanguage;
192
- this.cacheControl = md.cacheControl;
182
+ constructor(metadata) {
183
+ this.name = metadata.name;
184
+ this.bucket = metadata.bucket;
185
+ this.generation = metadata.generation.toString();
186
+ this.metageneration = metadata.metageneration.toString();
187
+ this.contentType = metadata.contentType;
188
+ this.timeCreated = toSerializedDate(metadata.timeCreated);
189
+ this.updated = toSerializedDate(metadata.updated);
190
+ this.storageClass = metadata.storageClass;
191
+ this.size = metadata.size.toString();
192
+ this.md5Hash = metadata.md5Hash;
193
+ this.crc32c = metadata.crc32c;
194
+ this.etag = metadata.etag;
195
+ this.downloadTokens = metadata.downloadTokens.join(",");
196
+ this.contentEncoding = metadata.contentEncoding;
197
+ this.contentDisposition = metadata.contentDisposition;
198
+ this.metadata = metadata.customMetadata;
199
+ this.contentLanguage = metadata.contentLanguage;
200
+ this.cacheControl = metadata.cacheControl;
193
201
  }
194
202
  }
195
203
  exports.OutgoingFirebaseMetadata = OutgoingFirebaseMetadata;
@@ -226,44 +234,45 @@ class CloudStorageObjectAccessControlMetadata {
226
234
  }
227
235
  exports.CloudStorageObjectAccessControlMetadata = CloudStorageObjectAccessControlMetadata;
228
236
  class CloudStorageObjectMetadata {
229
- constructor(md) {
237
+ constructor(metadata) {
230
238
  var _a, _b, _c, _d;
231
239
  this.kind = "#storage#object";
232
- this.name = md.name;
233
- this.bucket = md.bucket;
234
- this.generation = md.generation.toString();
235
- this.metageneration = md.metageneration.toString();
236
- this.contentType = md.contentType;
237
- this.timeCreated = toSerializedDate(md.timeCreated);
238
- this.updated = toSerializedDate(md.updated);
239
- this.storageClass = md.storageClass;
240
- this.size = md.size.toString();
241
- this.md5Hash = md.md5Hash;
242
- this.etag = md.etag;
240
+ this.name = metadata.name;
241
+ this.bucket = metadata.bucket;
242
+ this.generation = metadata.generation.toString();
243
+ this.metageneration = metadata.metageneration.toString();
244
+ this.contentType = metadata.contentType;
245
+ this.contentDisposition = metadata.contentDisposition;
246
+ this.timeCreated = toSerializedDate(metadata.timeCreated);
247
+ this.updated = toSerializedDate(metadata.updated);
248
+ this.storageClass = metadata.storageClass;
249
+ this.size = metadata.size.toString();
250
+ this.md5Hash = metadata.md5Hash;
251
+ this.etag = metadata.etag;
243
252
  this.metadata = {};
244
- if (Object.keys(md.customMetadata || {})) {
245
- this.metadata = Object.assign(Object.assign({}, this.metadata), md.customMetadata);
253
+ if (Object.keys(metadata.customMetadata || {})) {
254
+ this.metadata = Object.assign(Object.assign({}, this.metadata), metadata.customMetadata);
246
255
  }
247
- if (md.downloadTokens.length) {
248
- this.metadata = Object.assign(Object.assign({}, this.metadata), { firebaseStorageDownloadTokens: md.downloadTokens.join(",") });
256
+ if (metadata.downloadTokens.length) {
257
+ this.metadata = Object.assign(Object.assign({}, this.metadata), { firebaseStorageDownloadTokens: metadata.downloadTokens.join(",") });
249
258
  }
250
259
  if (!Object.keys(this.metadata).length) {
251
260
  delete this.metadata;
252
261
  }
253
- if (md.contentLanguage) {
254
- this.contentLanguage = md.contentLanguage;
262
+ if (metadata.contentLanguage) {
263
+ this.contentLanguage = metadata.contentLanguage;
255
264
  }
256
- if (md.cacheControl) {
257
- this.cacheControl = md.cacheControl;
265
+ if (metadata.cacheControl) {
266
+ this.cacheControl = metadata.cacheControl;
258
267
  }
259
- if (md.customTime) {
260
- this.customTime = toSerializedDate(md.customTime);
268
+ if (metadata.customTime) {
269
+ this.customTime = toSerializedDate(metadata.customTime);
261
270
  }
262
- this.crc32c = "----" + Buffer.from([md.crc32c]).toString("base64");
263
- this.timeStorageClassUpdated = toSerializedDate(md.timeCreated);
264
- this.id = `${md.bucket}/${md.name}/${md.generation}`;
265
- 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}/storage/v1/b/${md.bucket}/o/${encodeURIComponent(md.name)}`;
266
- this.mediaLink = `http://${(_c = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE)) === null || _c === void 0 ? void 0 : _c.host}:${(_d = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE)) === null || _d === void 0 ? void 0 : _d.port}/download/storage/v1/b/${md.bucket}/o/${encodeURIComponent(md.name)}?generation=${md.generation}&alt=media`;
271
+ this.crc32c = (0, crc_1.crc32cToString)(metadata.crc32c);
272
+ this.timeStorageClassUpdated = toSerializedDate(metadata.timeCreated);
273
+ this.id = `${metadata.bucket}/${metadata.name}/${metadata.generation}`;
274
+ 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}/storage/v1/b/${metadata.bucket}/o/${encodeURIComponent(metadata.name)}`;
275
+ this.mediaLink = `http://${(_c = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE)) === null || _c === void 0 ? void 0 : _c.host}:${(_d = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE)) === null || _d === void 0 ? void 0 : _d.port}/download/storage/v1/b/${metadata.bucket}/o/${encodeURIComponent(metadata.name)}?generation=${metadata.generation}&alt=media`;
267
276
  }
268
277
  }
269
278
  exports.CloudStorageObjectMetadata = CloudStorageObjectMetadata;
@@ -287,3 +296,8 @@ function generateMd5Hash(bytes) {
287
296
  hash.update(bytes);
288
297
  return hash.digest("base64");
289
298
  }
299
+ function generateETag(generation, metadatageneration) {
300
+ const hash = crypto.createHash("sha1");
301
+ hash.update(`${generation}/${metadatageneration}`);
302
+ return hash.digest("base64").slice(0, -1);
303
+ }
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseObjectUploadMultipartRequest = void 0;
4
+ const LINE_SEPARATOR = `\r\n`;
5
+ function splitBufferByDelimiter(buffer, delimiter, maxResults = -1) {
6
+ let offset = 0;
7
+ let nextDelimiterIndex = buffer.indexOf(delimiter, offset);
8
+ const bufferParts = [];
9
+ while (nextDelimiterIndex !== -1) {
10
+ if (maxResults === 0) {
11
+ return bufferParts;
12
+ }
13
+ else if (maxResults === 1) {
14
+ bufferParts.push(Buffer.from(buffer.slice(offset)));
15
+ return bufferParts;
16
+ }
17
+ bufferParts.push(Buffer.from(buffer.slice(offset, nextDelimiterIndex)));
18
+ offset = nextDelimiterIndex + delimiter.length;
19
+ nextDelimiterIndex = buffer.indexOf(delimiter, offset);
20
+ maxResults -= 1;
21
+ }
22
+ bufferParts.push(Buffer.from(buffer.slice(offset)));
23
+ return bufferParts;
24
+ }
25
+ function parseMultipartRequestBody(boundaryId, body) {
26
+ const boundaryString = `--${boundaryId}`;
27
+ const bodyParts = splitBufferByDelimiter(body, boundaryString).map((buf) => {
28
+ return Buffer.from(buf.slice(2));
29
+ });
30
+ const parsedParts = [];
31
+ for (const bodyPart of bodyParts.slice(1, bodyParts.length - 1)) {
32
+ parsedParts.push(parseMultipartRequestBodyPart(bodyPart));
33
+ }
34
+ return parsedParts;
35
+ }
36
+ function parseMultipartRequestBodyPart(bodyPart) {
37
+ const sections = splitBufferByDelimiter(bodyPart, LINE_SEPARATOR, 3);
38
+ const contentTypeRaw = sections[0].toString();
39
+ if (!contentTypeRaw.startsWith("Content-Type: ")) {
40
+ throw new Error(`Failed to parse multipart request body part. Missing content type.`);
41
+ }
42
+ const dataRaw = Buffer.from(sections[2]).slice(0, sections[2].byteLength - LINE_SEPARATOR.length);
43
+ return { contentTypeRaw, dataRaw };
44
+ }
45
+ function parseObjectUploadMultipartRequest(contentTypeHeader, body) {
46
+ if (!contentTypeHeader.startsWith("multipart/related")) {
47
+ throw new Error(`Invalid Content-Type: ${contentTypeHeader}`);
48
+ }
49
+ const boundaryId = contentTypeHeader.split("boundary=")[1];
50
+ if (!boundaryId) {
51
+ throw new Error(`Invalid Content-Type header: ${contentTypeHeader}`);
52
+ }
53
+ const parsedBody = parseMultipartRequestBody(boundaryId, body);
54
+ if (parsedBody.length !== 2) {
55
+ throw new Error(`Unexpected number of parts in request body`);
56
+ }
57
+ return {
58
+ metadataRaw: parsedBody[0].dataRaw.toString(),
59
+ dataRaw: Buffer.from(parsedBody[1].dataRaw),
60
+ };
61
+ }
62
+ exports.parseObjectUploadMultipartRequest = parseObjectUploadMultipartRequest;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Persistence = void 0;
4
+ const fs_1 = require("fs");
5
+ const rimraf = require("rimraf");
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+ class Persistence {
9
+ constructor(dirPath) {
10
+ this.reset(dirPath);
11
+ }
12
+ reset(dirPath) {
13
+ this._dirPath = dirPath;
14
+ (0, fs_1.mkdirSync)(dirPath, {
15
+ recursive: true,
16
+ });
17
+ }
18
+ get dirPath() {
19
+ return this._dirPath;
20
+ }
21
+ appendBytes(fileName, bytes) {
22
+ const filepath = this.getDiskPath(fileName);
23
+ let fd;
24
+ try {
25
+ fs.appendFileSync(filepath, bytes);
26
+ return filepath;
27
+ }
28
+ finally {
29
+ if (fd) {
30
+ (0, fs_1.closeSync)(fd);
31
+ }
32
+ }
33
+ }
34
+ readBytes(fileName, size, fileOffset) {
35
+ let fd;
36
+ try {
37
+ fd = (0, fs_1.openSync)(this.getDiskPath(fileName), "r");
38
+ const buf = Buffer.alloc(size);
39
+ const offset = fileOffset && fileOffset > 0 ? fileOffset : 0;
40
+ (0, fs_1.readSync)(fd, buf, 0, size, offset);
41
+ return buf;
42
+ }
43
+ finally {
44
+ if (fd) {
45
+ (0, fs_1.closeSync)(fd);
46
+ }
47
+ }
48
+ }
49
+ deleteFile(fileName, failSilently = false) {
50
+ try {
51
+ (0, fs_1.unlinkSync)(this.getDiskPath(fileName));
52
+ }
53
+ catch (err) {
54
+ if (!failSilently) {
55
+ throw err;
56
+ }
57
+ }
58
+ }
59
+ deleteAll() {
60
+ return new Promise((resolve, reject) => {
61
+ rimraf(this._dirPath, (err) => {
62
+ if (err) {
63
+ reject(err);
64
+ }
65
+ else {
66
+ resolve();
67
+ }
68
+ });
69
+ });
70
+ }
71
+ renameFile(oldName, newName) {
72
+ (0, fs_1.renameSync)(this.getDiskPath(oldName), this.getDiskPath(newName));
73
+ }
74
+ getDiskPath(fileName) {
75
+ return path.join(this._dirPath, encodeURIComponent(fileName));
76
+ }
77
+ }
78
+ exports.Persistence = Persistence;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getStorageRulesConfig = void 0;
4
+ const error_1 = require("../../../error");
5
+ function getAbsoluteRulesPath(rules, options) {
6
+ return options.config.path(rules);
7
+ }
8
+ function getStorageRulesConfig(projectId, options) {
9
+ const storageConfig = options.config.data.storage;
10
+ if (!storageConfig) {
11
+ 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");
12
+ }
13
+ if (!Array.isArray(storageConfig)) {
14
+ if (!storageConfig.rules) {
15
+ 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");
16
+ }
17
+ const resource = "default";
18
+ return [{ resource, rules: getAbsoluteRulesPath(storageConfig.rules, options) }];
19
+ }
20
+ const results = [];
21
+ const { rc } = options;
22
+ for (const targetConfig of storageConfig) {
23
+ if (!targetConfig.target) {
24
+ throw new error_1.FirebaseError("Must supply 'target' in Storage configuration");
25
+ }
26
+ rc.requireTarget(projectId, "storage", targetConfig.target);
27
+ rc.target(projectId, "storage", targetConfig.target).forEach((resource) => {
28
+ results.push({ resource, rules: getAbsoluteRulesPath(targetConfig.rules, options) });
29
+ });
30
+ }
31
+ return results;
32
+ }
33
+ exports.getStorageRulesConfig = getStorageRulesConfig;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StorageRulesManager = void 0;
4
+ const chokidar = require("chokidar");
5
+ const fs = require("fs");
6
+ const emulatorLogger_1 = require("../../emulatorLogger");
7
+ const types_1 = require("../../types");
8
+ const error_1 = require("../../../error");
9
+ class StorageRulesManager {
10
+ constructor(_runtime) {
11
+ this._runtime = _runtime;
12
+ this._watcher = new chokidar.FSWatcher();
13
+ this._logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE);
14
+ }
15
+ get ruleset() {
16
+ return this._ruleset;
17
+ }
18
+ async setSourceFile(rules) {
19
+ var _a;
20
+ const prevRulesFile = (_a = this._sourceFile) === null || _a === void 0 ? void 0 : _a.name;
21
+ let rulesFile;
22
+ if (typeof rules === "string") {
23
+ this._sourceFile = { name: rules, content: readSourceFile(rules) };
24
+ rulesFile = rules;
25
+ }
26
+ else {
27
+ this._sourceFile = rules;
28
+ rulesFile = rules.name;
29
+ }
30
+ const issues = await this.loadRuleset();
31
+ this.updateWatcher(rulesFile, prevRulesFile);
32
+ return issues;
33
+ }
34
+ async close() {
35
+ delete this._sourceFile;
36
+ delete this._ruleset;
37
+ await this._watcher.close();
38
+ }
39
+ updateWatcher(rulesFile, prevRulesFile) {
40
+ if (prevRulesFile) {
41
+ this._watcher.unwatch(prevRulesFile);
42
+ }
43
+ this._watcher = chokidar
44
+ .watch(rulesFile, { persistent: true, ignoreInitial: true })
45
+ .on("change", async () => {
46
+ await new Promise((res) => setTimeout(res, 5));
47
+ this._logger.logLabeled("BULLET", "storage", "Change detected, updating rules for Cloud Storage...");
48
+ await this.loadRuleset();
49
+ });
50
+ }
51
+ async loadRuleset() {
52
+ const { ruleset, issues } = await this._runtime.loadRuleset({ files: [this._sourceFile] });
53
+ if (ruleset) {
54
+ this._ruleset = ruleset;
55
+ return issues;
56
+ }
57
+ delete this._ruleset;
58
+ issues.all.forEach((issue) => {
59
+ try {
60
+ const parsedIssue = JSON.parse(issue);
61
+ this._logger.log("WARN", `${parsedIssue.description_.replace(/\.$/, "")} in ${parsedIssue.sourcePosition_.fileName_}:${parsedIssue.sourcePosition_.line_}`);
62
+ }
63
+ catch (_a) {
64
+ this._logger.log("WARN", issue);
65
+ }
66
+ });
67
+ return issues;
68
+ }
69
+ }
70
+ exports.StorageRulesManager = StorageRulesManager;
71
+ function readSourceFile(fileName) {
72
+ try {
73
+ return fs.readFileSync(fileName).toString();
74
+ }
75
+ catch (error) {
76
+ if (error.code === "ENOENT") {
77
+ throw new error_1.FirebaseError(`File not found: ${fileName}`);
78
+ }
79
+ throw error;
80
+ }
81
+ }
@@ -20,7 +20,7 @@ class StorageRulesetInstance {
20
20
  this.rulesetName = rulesetName;
21
21
  }
22
22
  async verify(opts, runtimeVariableOverrides = {}) {
23
- if (opts.method == types_1.RulesetOperationMethod.LIST && this.rulesVersion < 2) {
23
+ if (opts.method === types_1.RulesetOperationMethod.LIST && this.rulesVersion < 2) {
24
24
  const issues = new StorageRulesIssues();
25
25
  issues.warnings.push("Permission denied. List operations are only allowed for rules_version='2'.");
26
26
  return {
@@ -103,6 +103,7 @@ class StorageRulesRuntime {
103
103
  (_a = this._childprocess.stderr) === null || _a === void 0 ? void 0 : _a.on("data", (buf) => {
104
104
  const error = buf.toString();
105
105
  if (error.includes("jarfile")) {
106
+ emulatorLogger_1.EmulatorLogger.forEmulator(types_2.Emulators.STORAGE).log("ERROR", error);
106
107
  throw new error_1.FirebaseError("There was an issue starting the rules emulator, please run 'firebase setup:emulators:storage` again");
107
108
  }
108
109
  else {
@@ -111,7 +112,7 @@ class StorageRulesRuntime {
111
112
  });
112
113
  (_b = this._childprocess.stdout) === null || _b === void 0 ? void 0 : _b.on("data", (buf) => {
113
114
  const serializedRuntimeActionResponse = buf.toString("UTF8").trim();
114
- if (serializedRuntimeActionResponse != "") {
115
+ if (serializedRuntimeActionResponse !== "") {
115
116
  let rap;
116
117
  try {
117
118
  rap = JSON.parse(serializedRuntimeActionResponse);
@@ -217,14 +218,14 @@ class StorageRulesRuntime {
217
218
  }
218
219
  exports.StorageRulesRuntime = StorageRulesRuntime;
219
220
  function toExpressionValue(obj) {
220
- if (typeof obj == "string") {
221
+ if (typeof obj === "string") {
221
222
  return { string_value: obj };
222
223
  }
223
- if (typeof obj == "boolean") {
224
+ if (typeof obj === "boolean") {
224
225
  return { bool_value: obj };
225
226
  }
226
- if (typeof obj == "number") {
227
- if (Math.floor(obj) == obj) {
227
+ if (typeof obj === "number") {
228
+ if (Math.floor(obj) === obj) {
228
229
  return { int_value: obj };
229
230
  }
230
231
  else {
@@ -255,7 +256,7 @@ function toExpressionValue(obj) {
255
256
  null_value: 0,
256
257
  };
257
258
  }
258
- if (typeof obj == "object") {
259
+ if (typeof obj === "object") {
259
260
  const fields = {};
260
261
  Object.keys(obj).forEach((key) => {
261
262
  fields[key] = toExpressionValue(obj[key]);
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isPermitted = exports.getAdminCredentialValidator = exports.getRulesValidator = void 0;
4
+ const emulatorLogger_1 = require("../../emulatorLogger");
5
+ const types_1 = require("../../types");
6
+ function getRulesValidator(rulesetProvider) {
7
+ return {
8
+ validate: async (path, method, variableOverrides, authorization) => {
9
+ return await isPermitted({
10
+ ruleset: rulesetProvider(),
11
+ file: variableOverrides,
12
+ path,
13
+ method,
14
+ authorization,
15
+ });
16
+ },
17
+ };
18
+ }
19
+ exports.getRulesValidator = getRulesValidator;
20
+ function getAdminCredentialValidator() {
21
+ return { validate: isValidAdminCredentials };
22
+ }
23
+ exports.getAdminCredentialValidator = getAdminCredentialValidator;
24
+ async function isPermitted(opts) {
25
+ if (!opts.ruleset) {
26
+ emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE).log("WARN", `Can not process SDK request with no loaded ruleset`);
27
+ return false;
28
+ }
29
+ if (isValidAdminCredentials(opts.authorization)) {
30
+ return true;
31
+ }
32
+ const { permitted, issues } = await opts.ruleset.verify({
33
+ method: opts.method,
34
+ path: opts.path,
35
+ file: opts.file,
36
+ token: opts.authorization ? opts.authorization.split(" ")[1] : undefined,
37
+ });
38
+ if (issues.exist()) {
39
+ issues.all.forEach((warningOrError) => {
40
+ emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE).log("WARN", warningOrError);
41
+ });
42
+ }
43
+ return !!permitted;
44
+ }
45
+ exports.isPermitted = isPermitted;
46
+ function isValidAdminCredentials(authorization) {
47
+ return ["Bearer owner", "Firebase owner"].includes(authorization !== null && authorization !== void 0 ? authorization : "");
48
+ }