withCustomEndpoints.html 8.8 KB

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