diff --git a/docs/guides/migration-guides.md b/docs/guides/migration-guides.md
index 4222cc9803..ba4dcfabc7 100644
--- a/docs/guides/migration-guides.md
+++ b/docs/guides/migration-guides.md
@@ -102,6 +102,22 @@ const { someThingMyBackendReturns } = uppy.getFile(id).response;
about this in the
[plugin docs](https://uppy.io/docs/aws-s3-multipart/#when-should-i-use-it).
- Remove deprecated `prepareUploadParts` option.
+- Companion’s options (`companionUrl`, `companionHeaders`, and
+ `companionCookieRules`) are renamed to more generic names (`endpoint`,
+ `headers`, and `cookieRules`).
+
+ Using Companion with the `@uppy/aws-s3` plugin only makes sense if you already
+ need Companion for remote providers (such as Google Drive). When using your
+ own backend, you can let Uppy do all the heavy lifting on the client which it
+ would normally do for Companion, so you don’t have to implement that yourself.
+
+ As long as you return the JSON for the expected endpoints (see our
+ [server example](https://github.com/transloadit/uppy/blob/main/examples/aws-nodejs/index.js)),
+ you only need to set `endpoint`.
+
+ If you are using Companion, rename the options. If you have a lot of
+ client-side complexity (`createMultipartUpload`, `signPart`, etc), consider
+ letting Uppy do this for you.
### `@uppy/core`
diff --git a/docs/uploader/aws-s3-multipart.mdx b/docs/uploader/aws-s3-multipart.mdx
index 629838edc0..a21183ac72 100644
--- a/docs/uploader/aws-s3-multipart.mdx
+++ b/docs/uploader/aws-s3-multipart.mdx
@@ -166,7 +166,7 @@ import '@uppy/dashboard/dist/style.min.css';
const uppy = new Uppy()
.use(Dashboard, { inline: true, target: 'body' })
.use(AwsS3, {
- companionUrl: 'https://companion.uppy.io',
+ endpoint: 'https://companion.uppy.io',
});
```
@@ -215,22 +215,23 @@ uploaded.
:::
-#### `companionUrl`
+#### `endpoint`
-URL to a [Companion](/docs/companion) instance (`string`, default: `null`).
+URL to your backend or to [Companion](/docs/companion) (`string`, default:
+`null`).
-#### `companionHeaders`
+#### `headers`
-Custom headers that should be sent along to [Companion](/docs/companion) on
-every request (`Object`, default: `{}`).
+Custom headers that should be sent along to the [`endpoint`](#endpoint) on every
+request (`Object`, default: `{}`).
-#### `companionCookiesRule`
+#### `cookiesRule`
This option correlates to the
[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials)
(`string`, default: `'same-origin'`).
-This tells the plugin whether to send cookies to [Companion](/docs/companion).
+This tells the plugin whether to send cookies to the [`endpoint`](#endpoint).
#### `retryDelays`
@@ -432,7 +433,7 @@ upload sources), you can pass a boolean:
```js
uppy.use(AwsS3, {
// This is an example using Companion:
- companionUrl: 'http://companion.uppy.io',
+ endpoint: 'http://companion.uppy.io',
getTemporarySecurityCredentials: true,
shouldUseMultipart: (file) => file.size > 100 * 2 ** 20,
});
diff --git a/e2e/clients/dashboard-aws-multipart/app.js b/e2e/clients/dashboard-aws-multipart/app.js
index d4a3f9f015..8abb12ff92 100644
--- a/e2e/clients/dashboard-aws-multipart/app.js
+++ b/e2e/clients/dashboard-aws-multipart/app.js
@@ -9,7 +9,7 @@ const uppy = new Uppy()
.use(Dashboard, { target: '#app', inline: true })
.use(AwsS3Multipart, {
limit: 2,
- companionUrl: process.env.VITE_COMPANION_URL,
+ endpoint: process.env.VITE_COMPANION_URL,
shouldUseMultipart: true,
})
diff --git a/e2e/clients/dashboard-aws/app.js b/e2e/clients/dashboard-aws/app.js
index eeaa7effe6..2c23b9205b 100644
--- a/e2e/clients/dashboard-aws/app.js
+++ b/e2e/clients/dashboard-aws/app.js
@@ -9,7 +9,7 @@ const uppy = new Uppy()
.use(Dashboard, { target: '#app', inline: true })
.use(AwsS3, {
limit: 2,
- companionUrl: process.env.VITE_COMPANION_URL,
+ endpoint: process.env.VITE_COMPANION_URL,
shouldUseMultipart: false,
})
diff --git a/examples/aws-nodejs/index.js b/examples/aws-nodejs/index.js
index 2f13cc770d..20f8a003b4 100644
--- a/examples/aws-nodejs/index.js
+++ b/examples/aws-nodejs/index.js
@@ -2,6 +2,7 @@
const path = require('node:path')
const crypto = require('node:crypto')
+const { existsSync } = require('node:fs')
require('dotenv').config({ path: path.join(__dirname, '..', '..', '.env') })
const express = require('express')
@@ -9,6 +10,7 @@ const express = require('express')
const app = express()
const port = process.env.PORT ?? 8080
+const accessControlAllowOrigin = '*' // You should define the actual domain(s) that are allowed to make requests.
const bodyParser = require('body-parser')
const {
@@ -21,19 +23,14 @@ const {
UploadPartCommand,
} = require('@aws-sdk/client-s3')
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner')
-const {
- STSClient,
- GetFederationTokenCommand,
-} = require('@aws-sdk/client-sts')
+const { STSClient, GetFederationTokenCommand } = require('@aws-sdk/client-sts')
const policy = {
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
- Action: [
- 's3:PutObject',
- ],
+ Action: ['s3:PutObject'],
Resource: [
`arn:aws:s3:::${process.env.COMPANION_AWS_BUCKET}/*`,
`arn:aws:s3:::${process.env.COMPANION_AWS_BUCKET}`,
@@ -54,10 +51,10 @@ let stsClient
const expiresIn = 900 // Define how long until a S3 signature expires.
-function getS3Client () {
+function getS3Client() {
s3Client ??= new S3Client({
region: process.env.COMPANION_AWS_REGION,
- credentials : {
+ credentials: {
accessKeyId: process.env.COMPANION_AWS_KEY,
secretAccessKey: process.env.COMPANION_AWS_SECRET,
},
@@ -65,10 +62,10 @@ function getS3Client () {
return s3Client
}
-function getSTSClient () {
+function getSTSClient() {
stsClient ??= new STSClient({
region: process.env.COMPANION_AWS_REGION,
- credentials : {
+ credentials: {
accessKeyId: process.env.COMPANION_AWS_KEY,
secretAccessKey: process.env.COMPANION_AWS_SECRET,
},
@@ -78,53 +75,61 @@ function getSTSClient () {
app.use(bodyParser.urlencoded({ extended: true }), bodyParser.json())
-app.get('/', (req, res) => {
- const htmlPath = path.join(__dirname, 'public', 'index.html')
- res.sendFile(htmlPath)
-})
-
-app.get('/drag', (req, res) => {
- const htmlPath = path.join(__dirname, 'public', 'drag.html')
- res.sendFile(htmlPath)
+app.get('/s3/sts', (req, res, next) => {
+ // Before giving the STS token to the client, you should first check is they
+ // are authorized to perform that operation, and if the request is legit.
+ // For the sake of simplification, we skip that check in this example.
+
+ getSTSClient()
+ .send(
+ new GetFederationTokenCommand({
+ Name: '123user',
+ // The duration, in seconds, of the role session. The value specified
+ // can range from 900 seconds (15 minutes) up to the maximum session
+ // duration set for the role.
+ DurationSeconds: expiresIn,
+ Policy: JSON.stringify(policy),
+ }),
+ )
+ .then((response) => {
+ // Test creating multipart upload from the server — it works
+ // createMultipartUploadYo(response)
+ res.setHeader('Access-Control-Allow-Origin', accessControlAllowOrigin)
+ res.setHeader('Cache-Control', `public,max-age=${expiresIn}`)
+ res.json({
+ credentials: response.Credentials,
+ bucket: process.env.COMPANION_AWS_BUCKET,
+ region: process.env.COMPANION_AWS_REGION,
+ })
+ }, next)
})
+const signOnServer = (req, res, next) => {
+ // Before giving the signature to the user, you should first check is they
+ // are authorized to perform that operation, and if the request is legit.
+ // For the sake of simplification, we skip that check in this example.
-app.get('/sts', (req, res, next) => {
- getSTSClient().send(new GetFederationTokenCommand({
- Name: '123user',
- // The duration, in seconds, of the role session. The value specified
- // can range from 900 seconds (15 minutes) up to the maximum session
- // duration set for the role.
- DurationSeconds: expiresIn,
- Policy: JSON.stringify(policy),
- })).then(response => {
- // Test creating multipart upload from the server — it works
- // createMultipartUploadYo(response)
- res.setHeader('Access-Control-Allow-Origin', '*')
- res.setHeader('Cache-Control', `public,max-age=${expiresIn}`)
- res.json({
- credentials: response.Credentials,
- bucket: process.env.COMPANION_AWS_BUCKET,
- region: process.env.COMPANION_AWS_REGION,
- })
- }, next)
-})
-app.post('/sign-s3', (req, res, next) => {
const Key = `${crypto.randomUUID()}-${req.body.filename}`
const { contentType } = req.body
- getSignedUrl(getS3Client(), new PutObjectCommand({
- Bucket: process.env.COMPANION_AWS_BUCKET,
- Key,
- ContentType: contentType,
- }), { expiresIn }).then((url) => {
- res.setHeader('Access-Control-Allow-Origin', '*')
+ getSignedUrl(
+ getS3Client(),
+ new PutObjectCommand({
+ Bucket: process.env.COMPANION_AWS_BUCKET,
+ Key,
+ ContentType: contentType,
+ }),
+ { expiresIn },
+ ).then((url) => {
+ res.setHeader('Access-Control-Allow-Origin', accessControlAllowOrigin)
res.json({
url,
method: 'PUT',
})
res.end()
}, next)
-})
+}
+app.get('/s3/params', signOnServer)
+app.post('/s3/sign', signOnServer)
// === ===
// You can remove those endpoints if you only want to support the non-multipart uploads.
@@ -133,7 +138,9 @@ app.post('/s3/multipart', (req, res, next) => {
const client = getS3Client()
const { type, metadata, filename } = req.body
if (typeof filename !== 'string') {
- return res.status(400).json({ error: 's3: content filename must be a string' })
+ return res
+ .status(400)
+ .json({ error: 's3: content filename must be a string' })
}
if (typeof type !== 'string') {
return res.status(400).json({ error: 's3: content type must be a string' })
@@ -154,7 +161,7 @@ app.post('/s3/multipart', (req, res, next) => {
next(err)
return
}
- res.setHeader('Access-Control-Allow-Origin', '*')
+ res.setHeader('Access-Control-Allow-Origin', accessControlAllowOrigin)
res.json({
key: data.Key,
uploadId: data.UploadId,
@@ -162,7 +169,7 @@ app.post('/s3/multipart', (req, res, next) => {
})
})
-function validatePartNumber (partNumber) {
+function validatePartNumber(partNumber) {
// eslint-disable-next-line no-param-reassign
partNumber = Number(partNumber)
return Number.isInteger(partNumber) && partNumber >= 1 && partNumber <= 10_000
@@ -172,20 +179,33 @@ app.get('/s3/multipart/:uploadId/:partNumber', (req, res, next) => {
const { key } = req.query
if (!validatePartNumber(partNumber)) {
- return res.status(400).json({ error: 's3: the part number must be an integer between 1 and 10000.' })
+ return res
+ .status(400)
+ .json({
+ error: 's3: the part number must be an integer between 1 and 10000.',
+ })
}
if (typeof key !== 'string') {
- return res.status(400).json({ error: 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"' })
+ return res
+ .status(400)
+ .json({
+ error:
+ 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"',
+ })
}
- return getSignedUrl(getS3Client(), new UploadPartCommand({
- Bucket: process.env.COMPANION_AWS_BUCKET,
- Key: key,
- UploadId: uploadId,
- PartNumber: partNumber,
- Body: '',
- }), { expiresIn }).then((url) => {
- res.setHeader('Access-Control-Allow-Origin', '*')
+ return getSignedUrl(
+ getS3Client(),
+ new UploadPartCommand({
+ Bucket: process.env.COMPANION_AWS_BUCKET,
+ Key: key,
+ UploadId: uploadId,
+ PartNumber: partNumber,
+ Body: '',
+ }),
+ { expiresIn },
+ ).then((url) => {
+ res.setHeader('Access-Control-Allow-Origin', accessControlAllowOrigin)
res.json({ url, expires: expiresIn })
}, next)
})
@@ -196,39 +216,52 @@ app.get('/s3/multipart/:uploadId', (req, res, next) => {
const { key } = req.query
if (typeof key !== 'string') {
- res.status(400).json({ error: 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"' })
+ res
+ .status(400)
+ .json({
+ error:
+ 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"',
+ })
return
}
const parts = []
- function listPartsPage (startAt) {
- client.send(new ListPartsCommand({
- Bucket: process.env.COMPANION_AWS_BUCKET,
- Key: key,
- UploadId: uploadId,
- PartNumberMarker: startAt,
- }), (err, data) => {
- if (err) {
- next(err)
- return
- }
-
- parts.push(...data.Parts)
-
- if (data.IsTruncated) {
- // Get the next page.
- listPartsPage(data.NextPartNumberMarker)
- } else {
- res.json(parts)
- }
- })
+ function listPartsPage(startAt) {
+ client.send(
+ new ListPartsCommand({
+ Bucket: process.env.COMPANION_AWS_BUCKET,
+ Key: key,
+ UploadId: uploadId,
+ PartNumberMarker: startAt,
+ }),
+ (err, data) => {
+ if (err) {
+ next(err)
+ return
+ }
+
+ parts.push(...data.Parts)
+
+ if (data.IsTruncated) {
+ // Get the next page.
+ listPartsPage(data.NextPartNumberMarker)
+ } else {
+ res.json(parts)
+ }
+ },
+ )
}
listPartsPage(0)
})
-function isValidPart (part) {
- return part && typeof part === 'object' && Number(part.PartNumber) && typeof part.ETag === 'string'
+function isValidPart(part) {
+ return (
+ part &&
+ typeof part === 'object' &&
+ Number(part.PartNumber) &&
+ typeof part.ETag === 'string'
+ )
}
app.post('/s3/multipart/:uploadId/complete', (req, res, next) => {
const client = getS3Client()
@@ -237,29 +270,41 @@ app.post('/s3/multipart/:uploadId/complete', (req, res, next) => {
const { parts } = req.body
if (typeof key !== 'string') {
- return res.status(400).json({ error: 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"' })
+ return res
+ .status(400)
+ .json({
+ error:
+ 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"',
+ })
}
if (!Array.isArray(parts) || !parts.every(isValidPart)) {
- return res.status(400).json({ error: 's3: `parts` must be an array of {ETag, PartNumber} objects.' })
+ return res
+ .status(400)
+ .json({
+ error: 's3: `parts` must be an array of {ETag, PartNumber} objects.',
+ })
}
- return client.send(new CompleteMultipartUploadCommand({
- Bucket: process.env.COMPANION_AWS_BUCKET,
- Key: key,
- UploadId: uploadId,
- MultipartUpload: {
- Parts: parts,
+ return client.send(
+ new CompleteMultipartUploadCommand({
+ Bucket: process.env.COMPANION_AWS_BUCKET,
+ Key: key,
+ UploadId: uploadId,
+ MultipartUpload: {
+ Parts: parts,
+ },
+ }),
+ (err, data) => {
+ if (err) {
+ next(err)
+ return
+ }
+ res.setHeader('Access-Control-Allow-Origin', accessControlAllowOrigin)
+ res.json({
+ location: data.Location,
+ })
},
- }), (err, data) => {
- if (err) {
- next(err)
- return
- }
- res.setHeader('Access-Control-Allow-Origin', '*')
- res.json({
- location: data.Location,
- })
- })
+ )
})
app.delete('/s3/multipart/:uploadId', (req, res, next) => {
@@ -268,24 +313,89 @@ app.delete('/s3/multipart/:uploadId', (req, res, next) => {
const { key } = req.query
if (typeof key !== 'string') {
- return res.status(400).json({ error: 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"' })
+ return res
+ .status(400)
+ .json({
+ error:
+ 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"',
+ })
}
- return client.send(new AbortMultipartUploadCommand({
- Bucket: process.env.COMPANION_AWS_BUCKET,
- Key: key,
- UploadId: uploadId,
- }), (err) => {
- if (err) {
- next(err)
- return
- }
- res.json({})
- })
+ return client.send(
+ new AbortMultipartUploadCommand({
+ Bucket: process.env.COMPANION_AWS_BUCKET,
+ Key: key,
+ UploadId: uploadId,
+ }),
+ (err) => {
+ if (err) {
+ next(err)
+ return
+ }
+ res.json({})
+ },
+ )
})
// === ===
+// === ===
+
+app.get('/', (req, res) => {
+ res.setHeader('Content-Type', 'text/html')
+ const htmlPath = path.join(__dirname, 'public', 'index.html')
+ res.sendFile(htmlPath)
+})
+app.get('/index.html', (req, res) => {
+ res.setHeader('Location', '/').sendStatus(308).end()
+})
+app.get('/withCustomEndpoints.html', (req, res) => {
+ res.setHeader('Content-Type', 'text/html')
+ const htmlPath = path.join(__dirname, 'public', 'withCustomEndpoints.html')
+ res.sendFile(htmlPath)
+})
+
+app.get('/uppy.min.mjs', (req, res) => {
+ res.setHeader('Content-Type', 'text/javascript')
+ const bundlePath = path.join(
+ __dirname,
+ '../..',
+ 'packages/uppy/dist',
+ 'uppy.min.mjs',
+ )
+ if (existsSync(bundlePath)) {
+ res.sendFile(bundlePath)
+ } else {
+ console.warn(
+ 'No local JS bundle found, using the CDN as a fallback. Run `corepack yarn build` to make this warning disappear.',
+ )
+ res.end(
+ 'export * from "https://releases.transloadit.com/uppy/v4.0.0-beta.11/uppy.min.mjs";\n',
+ )
+ }
+})
+app.get('/uppy.min.css', (req, res) => {
+ res.setHeader('Content-Type', 'text/css')
+ const bundlePath = path.join(
+ __dirname,
+ '../..',
+ 'packages/uppy/dist',
+ 'uppy.min.css',
+ )
+ if (existsSync(bundlePath)) {
+ res.sendFile(bundlePath)
+ } else {
+ console.warn(
+ 'No local CSS bundle found, using the CDN as a fallback. Run `corepack yarn build` to make this warning disappear.',
+ )
+ res.end(
+ '@import "https://releases.transloadit.com/uppy/v4.0.0-beta.11/uppy.min.css";\n',
+ )
+ }
+})
+
app.listen(port, () => {
- console.log(`Example app listening on port ${port}`)
+ console.log(`Example app listening on port ${port}.`)
+ console.log(`Visit http://localhost:${port}/ on your browser to try it.`)
})
+// === ===
diff --git a/examples/aws-nodejs/public/drag.html b/examples/aws-nodejs/public/drag.html
deleted file mode 100644
index 4fb3b96804..0000000000
--- a/examples/aws-nodejs/public/drag.html
+++ /dev/null
@@ -1,104 +0,0 @@
-
-
-
-
- Uppy
-
-
-
-
-
-
diff --git a/examples/aws-nodejs/public/index.html b/examples/aws-nodejs/public/index.html
index 3a5996ebb8..881a07567a 100644
--- a/examples/aws-nodejs/public/index.html
+++ b/examples/aws-nodejs/public/index.html
@@ -3,264 +3,68 @@
Uppy – AWS upload example
-
+
AWS upload example
-
+
+ Sign on the server
+
+
+
+ Sign on the client (if WebCrypto is available)
+
+
+
+
diff --git a/examples/aws-nodejs/public/withCustomEndpoints.html b/examples/aws-nodejs/public/withCustomEndpoints.html
new file mode 100644
index 0000000000..2265a7bb70
--- /dev/null
+++ b/examples/aws-nodejs/public/withCustomEndpoints.html
@@ -0,0 +1,267 @@
+
+
+
+
+ Uppy – AWS upload example
+
+
+
+ AWS upload example
+
+
+
+
+
+
diff --git a/packages/@uppy/aws-s3/src/index.test.ts b/packages/@uppy/aws-s3/src/index.test.ts
index 90c0ae8933..cce88b432f 100644
--- a/packages/@uppy/aws-s3/src/index.test.ts
+++ b/packages/@uppy/aws-s3/src/index.test.ts
@@ -29,7 +29,7 @@ describe('AwsS3Multipart', () => {
core.use(AwsS3Multipart)
const awsS3Multipart = core.getPlugin('AwsS3Multipart')!
- const err = 'Expected a `companionUrl` option'
+ const err = 'Expected a `endpoint` option'
const file = {}
const opts = {}
@@ -330,8 +330,8 @@ describe('AwsS3Multipart', () => {
beforeEach(() => {
core = new Core()
core.use(AwsS3Multipart, {
- companionUrl: '',
- companionHeaders: {
+ endpoint: '',
+ headers: {
authorization: oldToken,
},
})
@@ -340,7 +340,8 @@ describe('AwsS3Multipart', () => {
it('companionHeader is updated before uploading file', async () => {
awsS3Multipart.setOptions({
- companionHeaders: {
+ endpoint: 'http://localhost',
+ headers: {
authorization: newToken,
},
})
@@ -371,7 +372,8 @@ describe('AwsS3Multipart', () => {
Body
>
awsS3Multipart.setOptions({
- companionHeaders: {
+ endpoint: 'http://localhost',
+ headers: {
authorization: newToken,
},
})
diff --git a/packages/@uppy/aws-s3/src/index.ts b/packages/@uppy/aws-s3/src/index.ts
index e642ed1f8c..3ae8c8bc40 100644
--- a/packages/@uppy/aws-s3/src/index.ts
+++ b/packages/@uppy/aws-s3/src/index.ts
@@ -143,9 +143,15 @@ export interface AwsS3Part {
}
type AWSS3WithCompanion = {
- companionUrl: string
- companionHeaders?: Record
- companionCookiesRule?: string
+ endpoint: ConstructorParameters<
+ typeof RequestClient
+ >[1]['companionUrl']
+ headers?: ConstructorParameters<
+ typeof RequestClient
+ >[1]['companionHeaders']
+ cookiesRule?: ConstructorParameters<
+ typeof RequestClient
+ >[1]['companionCookiesRule']
getTemporarySecurityCredentials?: true
}
type AWSS3WithoutCompanion = {
@@ -253,11 +259,7 @@ type AWSS3MaybeMultipartWithoutCompanion<
shouldUseMultipart: (file: UppyFile) => boolean
}
-type RequestClientOptions = Partial<
- ConstructorParameters>[1]
->
-
-interface _AwsS3MultipartOptions extends PluginOpts, RequestClientOptions {
+interface _AwsS3MultipartOptions extends PluginOpts {
allowedMetaFields?: string[] | boolean
limit?: number
retryDelays?: number[] | null
@@ -285,7 +287,6 @@ const defaultOptions = {
// eslint-disable-next-line no-bitwise
(file.size! >> 10) >> 10 > 100) as any as true,
retryDelays: [0, 1000, 3000, 5000],
- companionHeaders: {},
} satisfies Partial>
export default class AwsS3Multipart<
@@ -303,6 +304,7 @@ export default class AwsS3Multipart<
| 'completeMultipartUpload'
> &
Required> &
+ Partial &
AWSS3MultipartWithoutCompanionMandatorySignPart &
AWSS3NonMultipartWithoutCompanionMandatory,
M,
@@ -335,8 +337,7 @@ export default class AwsS3Multipart<
// We need the `as any` here because of the dynamic default options.
this.type = 'uploader'
this.id = this.opts.id || 'AwsS3Multipart'
- // TODO: only initiate `RequestClient` is `companionUrl` is defined.
- this.#client = new RequestClient(uppy, (opts as any) ?? {})
+ this.#setClient(opts)
const dynamicDefaultOptions = {
createMultipartUpload: this.createMultipartUpload,
@@ -385,10 +386,59 @@ export default class AwsS3Multipart<
return this.#client
}
+ #setClient(opts?: Partial>) {
+ if (
+ opts == null ||
+ !(
+ 'endpoint' in opts ||
+ 'companionUrl' in opts ||
+ 'headers' in opts ||
+ 'companionHeaders' in opts ||
+ 'cookiesRule' in opts ||
+ 'companionCookiesRule' in opts
+ )
+ )
+ return
+ if ('companionUrl' in opts && !('endpoint' in opts)) {
+ this.uppy.log(
+ '`companionUrl` option has been removed in @uppy/aws-s3, use `endpoint` instead.',
+ 'warning',
+ )
+ }
+ if ('companionHeaders' in opts && !('headers' in opts)) {
+ this.uppy.log(
+ '`companionHeaders` option has been removed in @uppy/aws-s3, use `headers` instead.',
+ 'warning',
+ )
+ }
+ if ('companionCookiesRule' in opts && !('cookiesRule' in opts)) {
+ this.uppy.log(
+ '`companionCookiesRule` option has been removed in @uppy/aws-s3, use `cookiesRule` instead.',
+ 'warning',
+ )
+ }
+ if ('endpoint' in opts) {
+ this.#client = new RequestClient(this.uppy, {
+ pluginId: this.id,
+ provider: 'AWS',
+ companionUrl: this.opts.endpoint!,
+ companionHeaders: this.opts.headers,
+ companionCookiesRule: this.opts.cookiesRule,
+ })
+ } else {
+ if ('headers' in opts) {
+ this.#setCompanionHeaders()
+ }
+ if ('cookiesRule' in opts) {
+ this.#client.opts.companionCookiesRule = opts.cookiesRule
+ }
+ }
+ }
+
setOptions(newOptions: Partial>): void {
this.#companionCommunicationQueue.setOptions(newOptions)
- super.setOptions(newOptions)
- this.#setCompanionHeaders()
+ super.setOptions(newOptions as any)
+ this.#setClient(newOptions)
}
/**
@@ -410,9 +460,9 @@ export default class AwsS3Multipart<
}
#assertHost(method: string): void {
- if (!this.opts.companionUrl) {
+ if (!this.#client) {
throw new Error(
- `Expected a \`companionUrl\` option containing a Companion address, or if you are not using Companion, a custom \`${method}\` implementation.`,
+ `Expected a \`endpoint\` option containing a URL, or if you are not using Companion, a custom \`${method}\` implementation.`,
)
}
}
@@ -486,15 +536,18 @@ export default class AwsS3Multipart<
throwIfAborted(options?.signal)
if (this.#cachedTemporaryCredentials == null) {
+ const { getTemporarySecurityCredentials } = this.opts
// We do not await it just yet, so concurrent calls do not try to override it:
- if (this.opts.getTemporarySecurityCredentials === true) {
+ if (getTemporarySecurityCredentials === true) {
this.#assertHost('getTemporarySecurityCredentials')
this.#cachedTemporaryCredentials = this.#client
.get('s3/sts', options)
.then(assertServerError)
} else {
this.#cachedTemporaryCredentials =
- this.opts.getTemporarySecurityCredentials(options)
+ (getTemporarySecurityCredentials as AWSS3WithoutCompanion['getTemporarySecurityCredentials'])!(
+ options,
+ )
}
this.#cachedTemporaryCredentials = await this.#cachedTemporaryCredentials
setTimeout(
@@ -572,7 +625,6 @@ export default class AwsS3Multipart<
abortMultipartUpload(
file: UppyFile,
{ key, uploadId, signal }: UploadResultWithSignal,
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
): Promise {
this.#assertHost('abortMultipartUpload')
@@ -920,7 +972,7 @@ export default class AwsS3Multipart<
}
#setCompanionHeaders = () => {
- this.#client.setCompanionHeaders(this.opts.companionHeaders)
+ this.#client?.setCompanionHeaders(this.opts.headers!)
}
#setResumableUploadsCapability = (boolean: boolean) => {