No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

metadata.js 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.toSerializedDate = exports.CloudStorageObjectMetadata = exports.CloudStorageObjectAccessControlMetadata = exports.CloudStorageBucketMetadata = exports.OutgoingFirebaseMetadata = exports.StoredFileMetadata = void 0;
  4. const uuid = require("uuid");
  5. const crypto = require("crypto");
  6. const registry_1 = require("../registry");
  7. const types_1 = require("../types");
  8. const crc_1 = require("./crc");
  9. class StoredFileMetadata {
  10. constructor(opts, _cloudFunctions, bytes) {
  11. this._cloudFunctions = _cloudFunctions;
  12. this.name = opts.name;
  13. this.bucket = opts.bucket;
  14. this.metageneration = opts.metageneration || 1;
  15. this.generation = opts.generation || Date.now();
  16. this.contentType = opts.contentType || "application/octet-stream";
  17. this.storageClass = opts.storageClass || "STANDARD";
  18. this.contentDisposition = opts.contentDisposition;
  19. this.cacheControl = opts.cacheControl;
  20. this.contentLanguage = opts.contentLanguage;
  21. this.customTime = opts.customTime;
  22. this.contentEncoding = opts.contentEncoding;
  23. this.downloadTokens = opts.downloadTokens || [];
  24. if (opts.etag) {
  25. this.etag = opts.etag;
  26. }
  27. else {
  28. this.etag = generateETag(this.generation, this.metageneration);
  29. }
  30. if (opts.customMetadata) {
  31. this.customMetadata = {};
  32. for (const [k, v] of Object.entries(opts.customMetadata)) {
  33. let stringVal = v;
  34. if (typeof stringVal !== "string") {
  35. stringVal = JSON.stringify(v);
  36. }
  37. this.customMetadata[k] = stringVal || "";
  38. }
  39. }
  40. this.timeCreated = opts.timeCreated ? new Date(opts.timeCreated) : new Date();
  41. this.updated = opts.updated ? new Date(opts.updated) : this.timeCreated;
  42. if (bytes) {
  43. this.size = bytes.byteLength;
  44. this.md5Hash = generateMd5Hash(bytes);
  45. this.crc32c = `${(0, crc_1.crc32c)(bytes)}`;
  46. }
  47. else if (opts.size !== undefined && opts.md5Hash && opts.crc32c) {
  48. this.size = opts.size;
  49. this.md5Hash = opts.md5Hash;
  50. this.crc32c = opts.crc32c;
  51. }
  52. else {
  53. throw new Error("Must pass bytes array or opts object with size, md5hash, and crc32c");
  54. }
  55. this.deleteFieldsSetAsNull();
  56. this.setDownloadTokensFromCustomMetadata();
  57. }
  58. clone() {
  59. const clone = new StoredFileMetadata({
  60. name: this.name,
  61. bucket: this.bucket,
  62. generation: this.generation,
  63. metageneration: this.metageneration,
  64. contentType: this.contentType,
  65. storageClass: this.storageClass,
  66. size: this.size,
  67. md5Hash: this.md5Hash,
  68. contentEncoding: this.contentEncoding,
  69. contentDisposition: this.contentDisposition,
  70. contentLanguage: this.contentLanguage,
  71. cacheControl: this.cacheControl,
  72. customTime: this.customTime,
  73. crc32c: this.crc32c,
  74. etag: this.etag,
  75. downloadTokens: this.downloadTokens,
  76. customMetadata: this.customMetadata,
  77. }, this._cloudFunctions);
  78. clone.timeCreated = this.timeCreated;
  79. clone.updated = this.updated;
  80. return clone;
  81. }
  82. asRulesResource(proposedChanges) {
  83. const proposedMetadata = this.clone();
  84. if (proposedChanges) {
  85. proposedMetadata.update(proposedChanges, false);
  86. }
  87. return {
  88. name: proposedMetadata.name,
  89. bucket: proposedMetadata.bucket,
  90. generation: proposedMetadata.generation,
  91. metageneration: proposedMetadata.metageneration,
  92. size: proposedMetadata.size,
  93. timeCreated: proposedMetadata.timeCreated,
  94. updated: proposedMetadata.updated,
  95. md5Hash: proposedMetadata.md5Hash,
  96. crc32c: proposedMetadata.crc32c,
  97. etag: proposedMetadata.etag,
  98. contentDisposition: proposedMetadata.contentDisposition,
  99. contentEncoding: proposedMetadata.contentEncoding,
  100. contentType: proposedMetadata.contentType,
  101. metadata: proposedMetadata.customMetadata || {},
  102. };
  103. }
  104. setDownloadTokensFromCustomMetadata() {
  105. if (!this.customMetadata) {
  106. return;
  107. }
  108. if (this.customMetadata.firebaseStorageDownloadTokens) {
  109. this.downloadTokens = [
  110. ...new Set([
  111. ...this.downloadTokens,
  112. ...this.customMetadata.firebaseStorageDownloadTokens.split(","),
  113. ]),
  114. ];
  115. delete this.customMetadata.firebaseStorageDownloadTokens;
  116. }
  117. }
  118. deleteFieldsSetAsNull() {
  119. const deletableFields = [
  120. "contentDisposition",
  121. "contentType",
  122. "contentLanguage",
  123. "contentEncoding",
  124. "cacheControl",
  125. ];
  126. deletableFields.map((field) => {
  127. if (this[field] === null) {
  128. delete this[field];
  129. }
  130. });
  131. if (this.customMetadata) {
  132. Object.keys(this.customMetadata).map((key) => {
  133. if (!this.customMetadata)
  134. return;
  135. if (this.customMetadata[key] === null) {
  136. delete this.customMetadata[key];
  137. }
  138. });
  139. }
  140. }
  141. update(incoming, shouldTrigger = true) {
  142. if (incoming.contentDisposition !== undefined) {
  143. this.contentDisposition =
  144. incoming.contentDisposition === null ? undefined : incoming.contentDisposition;
  145. }
  146. if (incoming.contentType !== undefined) {
  147. this.contentType = incoming.contentType === null ? undefined : incoming.contentType;
  148. }
  149. if (incoming.contentLanguage !== undefined) {
  150. this.contentLanguage =
  151. incoming.contentLanguage === null ? undefined : incoming.contentLanguage;
  152. }
  153. if (incoming.contentEncoding !== undefined) {
  154. this.contentEncoding =
  155. incoming.contentEncoding === null ? undefined : incoming.contentEncoding;
  156. }
  157. if (incoming.cacheControl !== undefined) {
  158. this.cacheControl = incoming.cacheControl === null ? undefined : incoming.cacheControl;
  159. }
  160. if (incoming.metadata !== undefined) {
  161. if (incoming.metadata === null) {
  162. this.customMetadata = undefined;
  163. }
  164. else {
  165. this.customMetadata = this.customMetadata || {};
  166. for (const [k, v] of Object.entries(incoming.metadata)) {
  167. if (v === null) {
  168. delete this.customMetadata[k];
  169. }
  170. else {
  171. this.customMetadata[k] = String(v);
  172. }
  173. }
  174. if (Object.keys(this.customMetadata).length === 0) {
  175. this.customMetadata = undefined;
  176. }
  177. }
  178. }
  179. this.metageneration++;
  180. this.updated = new Date();
  181. this.setDownloadTokensFromCustomMetadata();
  182. if (shouldTrigger) {
  183. this._cloudFunctions.dispatch("metadataUpdate", new CloudStorageObjectMetadata(this));
  184. }
  185. }
  186. addDownloadToken(shouldTrigger = true) {
  187. this.downloadTokens = [...(this.downloadTokens || []), uuid.v4()];
  188. this.update({}, shouldTrigger);
  189. }
  190. deleteDownloadToken(token) {
  191. if (!this.downloadTokens.length) {
  192. return;
  193. }
  194. const remainingTokens = this.downloadTokens.filter((t) => t !== token);
  195. this.downloadTokens = remainingTokens;
  196. if (remainingTokens.length === 0) {
  197. this.addDownloadToken(false);
  198. }
  199. this.update({});
  200. }
  201. static fromJSON(data, cloudFunctions) {
  202. const opts = JSON.parse(data);
  203. return new StoredFileMetadata(opts, cloudFunctions);
  204. }
  205. static toJSON(metadata) {
  206. return JSON.stringify(metadata, (key, value) => {
  207. if (key.startsWith("_")) {
  208. return undefined;
  209. }
  210. return value;
  211. }, 2);
  212. }
  213. }
  214. exports.StoredFileMetadata = StoredFileMetadata;
  215. class OutgoingFirebaseMetadata {
  216. constructor(metadata) {
  217. this.name = metadata.name;
  218. this.bucket = metadata.bucket;
  219. this.generation = metadata.generation.toString();
  220. this.metageneration = metadata.metageneration.toString();
  221. this.contentType = metadata.contentType;
  222. this.timeCreated = toSerializedDate(metadata.timeCreated);
  223. this.updated = toSerializedDate(metadata.updated);
  224. this.storageClass = metadata.storageClass;
  225. this.size = metadata.size.toString();
  226. this.md5Hash = metadata.md5Hash;
  227. this.crc32c = metadata.crc32c;
  228. this.etag = metadata.etag;
  229. this.downloadTokens = metadata.downloadTokens.join(",");
  230. this.contentEncoding = metadata.contentEncoding || "identity";
  231. this.contentDisposition = metadata.contentDisposition;
  232. this.metadata = metadata.customMetadata;
  233. this.contentLanguage = metadata.contentLanguage;
  234. this.cacheControl = metadata.cacheControl;
  235. }
  236. }
  237. exports.OutgoingFirebaseMetadata = OutgoingFirebaseMetadata;
  238. class CloudStorageBucketMetadata {
  239. constructor(id) {
  240. this.kind = "storage#bucket";
  241. this.name = id;
  242. this.id = id;
  243. const selfLink = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE);
  244. selfLink.pathname = `/v1/b/${this.id}`;
  245. this.selfLink = selfLink.toString();
  246. this.timeCreated = toSerializedDate(new Date());
  247. this.updated = this.timeCreated;
  248. this.projectNumber = "000000000000";
  249. this.metageneration = "1";
  250. this.location = "US";
  251. this.storageClass = "STANDARD";
  252. this.etag = "====";
  253. this.locationType = "mutli-region";
  254. }
  255. }
  256. exports.CloudStorageBucketMetadata = CloudStorageBucketMetadata;
  257. class CloudStorageObjectAccessControlMetadata {
  258. constructor(object, generation, selfLink, id, role, entity, bucket, etag) {
  259. this.object = object;
  260. this.generation = generation;
  261. this.selfLink = selfLink;
  262. this.id = id;
  263. this.role = role;
  264. this.entity = entity;
  265. this.bucket = bucket;
  266. this.etag = etag;
  267. this.kind = "storage#objectAccessControl";
  268. }
  269. }
  270. exports.CloudStorageObjectAccessControlMetadata = CloudStorageObjectAccessControlMetadata;
  271. class CloudStorageObjectMetadata {
  272. constructor(metadata) {
  273. this.kind = "storage#object";
  274. this.name = metadata.name;
  275. this.bucket = metadata.bucket;
  276. this.generation = metadata.generation.toString();
  277. this.metageneration = metadata.metageneration.toString();
  278. this.contentType = metadata.contentType;
  279. this.contentDisposition = metadata.contentDisposition;
  280. this.timeCreated = toSerializedDate(metadata.timeCreated);
  281. this.updated = toSerializedDate(metadata.updated);
  282. this.storageClass = metadata.storageClass;
  283. this.size = metadata.size.toString();
  284. this.md5Hash = metadata.md5Hash;
  285. this.etag = metadata.etag;
  286. this.metadata = {};
  287. if (Object.keys(metadata.customMetadata || {})) {
  288. this.metadata = Object.assign(Object.assign({}, this.metadata), metadata.customMetadata);
  289. }
  290. if (metadata.downloadTokens.length) {
  291. this.metadata = Object.assign(Object.assign({}, this.metadata), { firebaseStorageDownloadTokens: metadata.downloadTokens.join(",") });
  292. }
  293. if (!Object.keys(this.metadata).length) {
  294. delete this.metadata;
  295. }
  296. if (metadata.contentLanguage) {
  297. this.contentLanguage = metadata.contentLanguage;
  298. }
  299. if (metadata.cacheControl) {
  300. this.cacheControl = metadata.cacheControl;
  301. }
  302. if (metadata.contentDisposition) {
  303. this.contentDisposition = metadata.contentDisposition;
  304. }
  305. if (metadata.contentEncoding) {
  306. this.contentEncoding = metadata.contentEncoding;
  307. }
  308. if (metadata.customTime) {
  309. this.customTime = toSerializedDate(metadata.customTime);
  310. }
  311. this.crc32c = (0, crc_1.crc32cToString)(metadata.crc32c);
  312. this.timeStorageClassUpdated = toSerializedDate(metadata.timeCreated);
  313. this.id = `${metadata.bucket}/${metadata.name}/${metadata.generation}`;
  314. const selfLink = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE);
  315. selfLink.pathname = `/storage/v1/b/${metadata.bucket}/o/${encodeURIComponent(metadata.name)}`;
  316. this.selfLink = selfLink.toString();
  317. const mediaLink = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE);
  318. mediaLink.pathname = `/download/storage/v1/b/${metadata.bucket}/o/${encodeURIComponent(metadata.name)}`;
  319. mediaLink.searchParams.set("generation", metadata.generation.toString());
  320. mediaLink.searchParams.set("alt", "media");
  321. this.mediaLink = mediaLink.toString();
  322. }
  323. }
  324. exports.CloudStorageObjectMetadata = CloudStorageObjectMetadata;
  325. function toSerializedDate(d) {
  326. const day = `${d.getUTCFullYear()}-${(d.getUTCMonth() + 1).toString().padStart(2, "0")}-${d
  327. .getUTCDate()
  328. .toString()
  329. .padStart(2, "0")}`;
  330. const time = `${d.getUTCHours().toString().padStart(2, "0")}:${d
  331. .getUTCMinutes()
  332. .toString()
  333. .padStart(2, "0")}:${d.getUTCSeconds().toString().padStart(2, "0")}.${d
  334. .getUTCMilliseconds()
  335. .toString()
  336. .padStart(3, "0")}`;
  337. return `${day}T${time}Z`;
  338. }
  339. exports.toSerializedDate = toSerializedDate;
  340. function generateMd5Hash(bytes) {
  341. const hash = crypto.createHash("md5");
  342. hash.update(bytes);
  343. return hash.digest("base64");
  344. }
  345. function generateETag(generation, metadatageneration) {
  346. const hash = crypto.createHash("sha1");
  347. hash.update(`${generation}/${metadatageneration}`);
  348. return hash.digest("base64").slice(0, -1);
  349. }