to-file.mjs 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import { getName, makeFile, isAsyncIterable } from "./uploads.mjs";
  2. import { checkFileSupport } from "./uploads.mjs";
  3. /**
  4. * This check adds the arrayBuffer() method type because it is available and used at runtime
  5. */
  6. const isBlobLike = (value) => value != null &&
  7. typeof value === 'object' &&
  8. typeof value.size === 'number' &&
  9. typeof value.type === 'string' &&
  10. typeof value.text === 'function' &&
  11. typeof value.slice === 'function' &&
  12. typeof value.arrayBuffer === 'function';
  13. /**
  14. * This check adds the arrayBuffer() method type because it is available and used at runtime
  15. */
  16. const isFileLike = (value) => value != null &&
  17. typeof value === 'object' &&
  18. typeof value.name === 'string' &&
  19. typeof value.lastModified === 'number' &&
  20. isBlobLike(value);
  21. const isResponseLike = (value) => value != null &&
  22. typeof value === 'object' &&
  23. typeof value.url === 'string' &&
  24. typeof value.blob === 'function';
  25. /**
  26. * Helper for creating a {@link File} to pass to an SDK upload method from a variety of different data formats
  27. * @param value the raw content of the file. Can be an {@link Uploadable}, BlobLikePart, or AsyncIterable of BlobLikeParts
  28. * @param {string=} name the name of the file. If omitted, toFile will try to determine a file name from bits if possible
  29. * @param {Object=} options additional properties
  30. * @param {string=} options.type the MIME type of the content
  31. * @param {number=} options.lastModified the last modified timestamp
  32. * @returns a {@link File} with the given properties
  33. */
  34. export async function toFile(value, name, options) {
  35. checkFileSupport();
  36. // If it's a promise, resolve it.
  37. value = await value;
  38. name || (name = getName(value, true));
  39. // If we've been given a `File` we don't need to do anything if the name / options
  40. // have not been customised.
  41. if (isFileLike(value)) {
  42. if (value instanceof File && name == null && options == null) {
  43. return value;
  44. }
  45. return makeFile([await value.arrayBuffer()], name ?? value.name, {
  46. type: value.type,
  47. lastModified: value.lastModified,
  48. ...options,
  49. });
  50. }
  51. if (isResponseLike(value)) {
  52. const blob = await value.blob();
  53. name || (name = new URL(value.url).pathname.split(/[\\/]/).pop());
  54. return makeFile(await getBytes(blob), name, options);
  55. }
  56. const parts = await getBytes(value);
  57. if (!options?.type) {
  58. const type = parts.find((part) => typeof part === 'object' && 'type' in part && part.type);
  59. if (typeof type === 'string') {
  60. options = { ...options, type };
  61. }
  62. }
  63. return makeFile(parts, name, options);
  64. }
  65. async function getBytes(value) {
  66. let parts = [];
  67. if (typeof value === 'string' ||
  68. ArrayBuffer.isView(value) || // includes Uint8Array, Buffer, etc.
  69. value instanceof ArrayBuffer) {
  70. parts.push(value);
  71. }
  72. else if (isBlobLike(value)) {
  73. parts.push(value instanceof Blob ? value : await value.arrayBuffer());
  74. }
  75. else if (isAsyncIterable(value) // includes Readable, ReadableStream, etc.
  76. ) {
  77. for await (const chunk of value) {
  78. parts.push(...(await getBytes(chunk))); // TODO, consider validating?
  79. }
  80. }
  81. else {
  82. const constructor = value?.constructor?.name;
  83. throw new Error(`Unexpected data type: ${typeof value}${constructor ? `; constructor: ${constructor}` : ''}${propsForError(value)}`);
  84. }
  85. return parts;
  86. }
  87. function propsForError(value) {
  88. if (typeof value !== 'object' || value === null)
  89. return '';
  90. const props = Object.getOwnPropertyNames(value);
  91. return `; props: [${props.map((p) => `"${p}"`).join(', ')}]`;
  92. }
  93. //# sourceMappingURL=to-file.mjs.map