uploads.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.createForm = exports.multipartFormRequestOptions = exports.maybeMultipartFormRequestOptions = exports.isMultipartBody = exports.toFile = exports.isUploadable = exports.isBlobLike = exports.isFileLike = exports.isResponseLike = exports.fileFromPath = void 0;
  4. const index_1 = require("./_shims/index.js");
  5. var index_2 = require("./_shims/index.js");
  6. Object.defineProperty(exports, "fileFromPath", { enumerable: true, get: function () { return index_2.fileFromPath; } });
  7. const isResponseLike = (value) => value != null &&
  8. typeof value === 'object' &&
  9. typeof value.url === 'string' &&
  10. typeof value.blob === 'function';
  11. exports.isResponseLike = isResponseLike;
  12. const isFileLike = (value) => value != null &&
  13. typeof value === 'object' &&
  14. typeof value.name === 'string' &&
  15. typeof value.lastModified === 'number' &&
  16. (0, exports.isBlobLike)(value);
  17. exports.isFileLike = isFileLike;
  18. /**
  19. * The BlobLike type omits arrayBuffer() because @types/node-fetch@^2.6.4 lacks it; but this check
  20. * adds the arrayBuffer() method type because it is available and used at runtime
  21. */
  22. const isBlobLike = (value) => value != null &&
  23. typeof value === 'object' &&
  24. typeof value.size === 'number' &&
  25. typeof value.type === 'string' &&
  26. typeof value.text === 'function' &&
  27. typeof value.slice === 'function' &&
  28. typeof value.arrayBuffer === 'function';
  29. exports.isBlobLike = isBlobLike;
  30. const isUploadable = (value) => {
  31. return (0, exports.isFileLike)(value) || (0, exports.isResponseLike)(value) || (0, index_1.isFsReadStream)(value);
  32. };
  33. exports.isUploadable = isUploadable;
  34. /**
  35. * Helper for creating a {@link File} to pass to an SDK upload method from a variety of different data formats
  36. * @param value the raw content of the file. Can be an {@link Uploadable}, {@link BlobLikePart}, or {@link AsyncIterable} of {@link BlobLikePart}s
  37. * @param {string=} name the name of the file. If omitted, toFile will try to determine a file name from bits if possible
  38. * @param {Object=} options additional properties
  39. * @param {string=} options.type the MIME type of the content
  40. * @param {number=} options.lastModified the last modified timestamp
  41. * @returns a {@link File} with the given properties
  42. */
  43. async function toFile(value, name, options) {
  44. // If it's a promise, resolve it.
  45. value = await value;
  46. // If we've been given a `File` we don't need to do anything
  47. if ((0, exports.isFileLike)(value)) {
  48. return value;
  49. }
  50. if ((0, exports.isResponseLike)(value)) {
  51. const blob = await value.blob();
  52. name || (name = new URL(value.url).pathname.split(/[\\/]/).pop() ?? 'unknown_file');
  53. // we need to convert the `Blob` into an array buffer because the `Blob` class
  54. // that `node-fetch` defines is incompatible with the web standard which results
  55. // in `new File` interpreting it as a string instead of binary data.
  56. const data = (0, exports.isBlobLike)(blob) ? [(await blob.arrayBuffer())] : [blob];
  57. return new index_1.File(data, name, options);
  58. }
  59. const bits = await getBytes(value);
  60. name || (name = getName(value) ?? 'unknown_file');
  61. if (!options?.type) {
  62. const type = bits[0]?.type;
  63. if (typeof type === 'string') {
  64. options = { ...options, type };
  65. }
  66. }
  67. return new index_1.File(bits, name, options);
  68. }
  69. exports.toFile = toFile;
  70. async function getBytes(value) {
  71. let parts = [];
  72. if (typeof value === 'string' ||
  73. ArrayBuffer.isView(value) || // includes Uint8Array, Buffer, etc.
  74. value instanceof ArrayBuffer) {
  75. parts.push(value);
  76. }
  77. else if ((0, exports.isBlobLike)(value)) {
  78. parts.push(await value.arrayBuffer());
  79. }
  80. else if (isAsyncIterableIterator(value) // includes Readable, ReadableStream, etc.
  81. ) {
  82. for await (const chunk of value) {
  83. parts.push(chunk); // TODO, consider validating?
  84. }
  85. }
  86. else {
  87. throw new Error(`Unexpected data type: ${typeof value}; constructor: ${value?.constructor
  88. ?.name}; props: ${propsForError(value)}`);
  89. }
  90. return parts;
  91. }
  92. function propsForError(value) {
  93. const props = Object.getOwnPropertyNames(value);
  94. return `[${props.map((p) => `"${p}"`).join(', ')}]`;
  95. }
  96. function getName(value) {
  97. return (getStringFromMaybeBuffer(value.name) ||
  98. getStringFromMaybeBuffer(value.filename) ||
  99. // For fs.ReadStream
  100. getStringFromMaybeBuffer(value.path)?.split(/[\\/]/).pop());
  101. }
  102. const getStringFromMaybeBuffer = (x) => {
  103. if (typeof x === 'string')
  104. return x;
  105. if (typeof Buffer !== 'undefined' && x instanceof Buffer)
  106. return String(x);
  107. return undefined;
  108. };
  109. const isAsyncIterableIterator = (value) => value != null && typeof value === 'object' && typeof value[Symbol.asyncIterator] === 'function';
  110. const isMultipartBody = (body) => body && typeof body === 'object' && body.body && body[Symbol.toStringTag] === 'MultipartBody';
  111. exports.isMultipartBody = isMultipartBody;
  112. /**
  113. * Returns a multipart/form-data request if any part of the given request body contains a File / Blob value.
  114. * Otherwise returns the request as is.
  115. */
  116. const maybeMultipartFormRequestOptions = async (opts) => {
  117. if (!hasUploadableValue(opts.body))
  118. return opts;
  119. const form = await (0, exports.createForm)(opts.body);
  120. return (0, index_1.getMultipartRequestOptions)(form, opts);
  121. };
  122. exports.maybeMultipartFormRequestOptions = maybeMultipartFormRequestOptions;
  123. const multipartFormRequestOptions = async (opts) => {
  124. const form = await (0, exports.createForm)(opts.body);
  125. return (0, index_1.getMultipartRequestOptions)(form, opts);
  126. };
  127. exports.multipartFormRequestOptions = multipartFormRequestOptions;
  128. const createForm = async (body) => {
  129. const form = new index_1.FormData();
  130. await Promise.all(Object.entries(body || {}).map(([key, value]) => addFormValue(form, key, value)));
  131. return form;
  132. };
  133. exports.createForm = createForm;
  134. const hasUploadableValue = (value) => {
  135. if ((0, exports.isUploadable)(value))
  136. return true;
  137. if (Array.isArray(value))
  138. return value.some(hasUploadableValue);
  139. if (value && typeof value === 'object') {
  140. for (const k in value) {
  141. if (hasUploadableValue(value[k]))
  142. return true;
  143. }
  144. }
  145. return false;
  146. };
  147. const addFormValue = async (form, key, value) => {
  148. if (value === undefined)
  149. return;
  150. if (value == null) {
  151. throw new TypeError(`Received null for "${key}"; to pass null in FormData, you must use the string 'null'`);
  152. }
  153. // TODO: make nested formats configurable
  154. if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
  155. form.append(key, String(value));
  156. }
  157. else if ((0, exports.isUploadable)(value)) {
  158. const file = await toFile(value);
  159. form.append(key, file);
  160. }
  161. else if (Array.isArray(value)) {
  162. await Promise.all(value.map((entry) => addFormValue(form, key + '[]', entry)));
  163. }
  164. else if (typeof value === 'object') {
  165. await Promise.all(Object.entries(value).map(([name, prop]) => addFormValue(form, `${key}[${name}]`, prop)));
  166. }
  167. else {
  168. throw new TypeError(`Invalid value given to form, expected a string, number, boolean, object, Array, File or Blob but got ${value} instead`);
  169. }
  170. };
  171. //# sourceMappingURL=uploads.js.map