index.html 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. <!doctype html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <title>Uppy – AWS upload example</title>
  6. <link href="https://releases.transloadit.com/uppy/v3.14.0/uppy.min.css" rel="stylesheet">
  7. </head>
  8. <body>
  9. <h1>AWS upload example</h1>
  10. <div id="uppy"></div>
  11. <script type="module">
  12. import { Uppy, Dashboard, AwsS3 } from "https://releases.transloadit.com/uppy/v3.14.0/uppy.min.mjs"
  13. /**
  14. * This generator transforms a deep object into URL-encodable pairs
  15. * to work with `URLSearchParams` on the client and `body-parser` on the server.
  16. */
  17. function* serializeSubPart(key, value) {
  18. if (typeof value !== "object") {
  19. yield [key, value];
  20. return;
  21. }
  22. if (Array.isArray(value)) {
  23. for (const val of value) {
  24. yield* serializeSubPart(`${key}[]`, val);
  25. }
  26. return;
  27. }
  28. for (const [subkey, val] of Object.entries(value)) {
  29. yield* serializeSubPart(key ? `${key}[${subkey}]` : subkey, val);
  30. }
  31. }
  32. function serialize(data) {
  33. // If you want to avoid preflight requests, use URL-encoded syntax:
  34. return new URLSearchParams(serializeSubPart(null, data))
  35. // If you don't care about additional preflight requests, you can also use:
  36. // return JSON.stringify(data)
  37. // You'd also have to add `Content-Type` header with value `application/json`.
  38. }
  39. {
  40. const MiB = 0x10_00_00;
  41. const uppy = new Uppy()
  42. .use(Dashboard, {
  43. inline: true,
  44. target: '#uppy',
  45. })
  46. .use(AwsS3, {
  47. id: 'myAWSPlugin',
  48. // Files that are more than 100MiB should be uploaded in multiple parts.
  49. shouldUseMultipart: (file) => file.size > 100 * MiB,
  50. /**
  51. * This method tells Uppy how to retrieve a temporary token for signing on the client.
  52. * Signing on the client is optional, you can also do the signing from the server.
  53. */
  54. async getTemporarySecurityCredentials({signal}) {
  55. const response = await fetch('/sts', { signal })
  56. if (!response.ok) throw new Error('Unsuccessful request', { cause: response })
  57. return response.json()
  58. },
  59. // ========== Non-Multipart Uploads ==========
  60. /**
  61. * This method tells Uppy how to handle non-multipart uploads.
  62. * If for some reason you want to only support multipart uploads,
  63. * you don't need to implement it.
  64. */
  65. async getUploadParameters (file, options) {
  66. if (typeof crypto?.subtle === 'object') {
  67. // If WebCrypto is available, let's do signing from the client.
  68. return uppy.getPlugin('myAWSPlugin').createSignedURL(file, options)
  69. }
  70. // Send a request to our Express.js signing endpoint.
  71. const response = await fetch('/sign-s3', {
  72. method: 'POST',
  73. headers: {
  74. accept: 'application/json',
  75. },
  76. body: serialize({
  77. filename: file.name,
  78. contentType: file.type,
  79. }),
  80. signal: options.signal,
  81. })
  82. if (!response.ok) throw new Error('Unsuccessful request', { cause: response })
  83. // Parse the JSON response.
  84. const data = await response.json()
  85. // Return an object in the correct shape.
  86. return {
  87. method: data.method,
  88. url: data.url,
  89. fields: {}, // For presigned PUT uploads, this should be left empty.
  90. // Provide content type header required by S3
  91. headers: {
  92. 'Content-Type': file.type,
  93. },
  94. }
  95. },
  96. // ========== Multipart Uploads ==========
  97. // The following methods are only useful for multipart uploads:
  98. // If you are not interested in multipart uploads, you don't need to
  99. // implement them (you'd also need to set `shouldUseMultipart: false` though).
  100. async createMultipartUpload(file, signal) {
  101. if (signal?.aborted) {
  102. const err = new DOMException('The operation was aborted', 'AbortError')
  103. Object.defineProperty(err, 'cause', { __proto__: null, configurable: true, writable: true, value: signal.reason })
  104. throw err
  105. }
  106. const metadata = {}
  107. Object.keys(file.meta || {}).forEach(key => {
  108. if (file.meta[key] != null) {
  109. metadata[key] = file.meta[key].toString()
  110. }
  111. })
  112. const response = await fetch('/s3/multipart', {
  113. method: 'POST',
  114. // Send and receive JSON.
  115. headers: {
  116. accept: 'application/json',
  117. },
  118. body: serialize({
  119. filename: file.name,
  120. type: file.type,
  121. metadata,
  122. }),
  123. signal,
  124. })
  125. if (!response.ok) throw new Error('Unsuccessful request', { cause: response })
  126. // Parse the JSON response.
  127. const data = await response.json()
  128. return data
  129. },
  130. async abortMultipartUpload (file, { key, uploadId }, signal) {
  131. const filename = encodeURIComponent(key)
  132. const uploadIdEnc = encodeURIComponent(uploadId)
  133. const response = await fetch(`/s3/multipart/${uploadIdEnc}?key=${filename}`, {
  134. method: 'DELETE',
  135. signal,
  136. })
  137. if (!response.ok) throw new Error('Unsuccessful request', { cause: response })
  138. },
  139. async signPart (file, options) {
  140. if (typeof crypto?.subtle === 'object') {
  141. // If WebCrypto, let's do signing from the client.
  142. return uppy.getPlugin('myAWSPlugin').createSignedURL(file, options)
  143. }
  144. const { uploadId, key, partNumber, signal } = options
  145. if (signal?.aborted) {
  146. const err = new DOMException('The operation was aborted', 'AbortError')
  147. Object.defineProperty(err, 'cause', { __proto__: null, configurable: true, writable: true, value: signal.reason })
  148. throw err
  149. }
  150. if (uploadId == null || key == null || partNumber == null) {
  151. throw new Error('Cannot sign without a key, an uploadId, and a partNumber')
  152. }
  153. const filename = encodeURIComponent(key)
  154. const response = await fetch(`/s3/multipart/${uploadId}/${partNumber}?key=${filename}`, { signal })
  155. if (!response.ok) throw new Error('Unsuccessful request', { cause: response })
  156. const data = await response.json()
  157. return data
  158. },
  159. async listParts (file, { key, uploadId }, signal) {
  160. if (signal?.aborted) {
  161. const err = new DOMException('The operation was aborted', 'AbortError')
  162. Object.defineProperty(err, 'cause', { __proto__: null, configurable: true, writable: true, value: signal.reason })
  163. throw err
  164. }
  165. const filename = encodeURIComponent(key)
  166. const response = await fetch(`/s3/multipart/${uploadId}?key=${filename}`, { signal })
  167. if (!response.ok) throw new Error('Unsuccessful request', { cause: response })
  168. const data = await response.json()
  169. return data
  170. },
  171. async completeMultipartUpload (file, { key, uploadId, parts }, signal) {
  172. if (signal?.aborted) {
  173. const err = new DOMException('The operation was aborted', 'AbortError')
  174. Object.defineProperty(err, 'cause', { __proto__: null, configurable: true, writable: true, value: signal.reason })
  175. throw err
  176. }
  177. const filename = encodeURIComponent(key)
  178. const uploadIdEnc = encodeURIComponent(uploadId)
  179. const response = await fetch(`s3/multipart/${uploadIdEnc}/complete?key=${filename}`, {
  180. method: 'POST',
  181. headers: {
  182. accept: 'application/json',
  183. },
  184. body: serialize({ parts }),
  185. signal,
  186. })
  187. if (!response.ok) throw new Error('Unsuccessful request', { cause: response })
  188. const data = await response.json()
  189. return data
  190. },
  191. })
  192. uppy.on('complete', (result) => {
  193. console.log('Upload complete! We’ve uploaded these files:', result.successful)
  194. })
  195. uppy.on('upload-success', (file, data) => {
  196. console.log('Upload success! We’ve uploaded this file:', file.meta['name'])
  197. })
  198. }
  199. </script>
  200. </body>
  201. </html>