Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@uppy/companion: switch from aws-sdk v2 to @aws-sdk/client-s3 (v3) #4285

Merged
merged 10 commits into from
Jun 19, 2023
Merged
5 changes: 4 additions & 1 deletion packages/@uppy/companion/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@
],
"bin": "./bin/companion",
"dependencies": {
"@aws-sdk/client-s3": "^3.338.0",
"@aws-sdk/lib-storage": "^3.338.0",
"@aws-sdk/s3-presigned-post": "^3.338.0",
"@aws-sdk/s3-request-presigner": "^3.338.0",
"atob": "2.1.2",
"aws-sdk": "^2.1038.0",
"body-parser": "1.20.0",
"chalk": "4.1.2",
"common-tags": "1.8.2",
Expand Down
44 changes: 23 additions & 21 deletions packages/@uppy/companion/src/server/Uploader.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const { promisify } = require('node:util')
const FormData = require('form-data')
const throttle = require('lodash/throttle')

const { Upload } = require('@aws-sdk/lib-storage')

// TODO move to `require('streams/promises').pipeline` when dropping support for Node.js 14.x.
const pipeline = promisify(pipelineCb)

Expand Down Expand Up @@ -642,7 +644,11 @@ class Uploader {
}

const filename = this.uploadFileName
const { client, options } = this.options.s3
/**
* @type {{client: import('@aws-sdk/client-s3').S3Client, options: Record<string, any>}}
*/
const s3Options = this.options.s3
const { client, options } = s3Options

const params = {
Bucket: options.bucket,
Expand All @@ -654,35 +660,31 @@ class Uploader {

if (options.acl != null) params.ACL = options.acl

const upload = client.upload(params, {
const upload = new Upload({
client,
params,
// using chunkSize as partSize too, see https://github.com/transloadit/uppy/pull/3511
partSize: this.options.chunkSize,
mifi marked this conversation as resolved.
Show resolved Hide resolved
leavePartsOnError: true, // https://github.com/aws/aws-sdk-js-v3/issues/2311
})

upload.on('httpUploadProgress', ({ loaded, total }) => {
this.onProgress(loaded, total)
})

return new Promise((resolve, reject) => {
upload.send((error, data) => {
if (error) {
reject(error)
return
}

resolve({
url: data && data.Location ? data.Location : null,
extraData: {
response: {
responseText: JSON.stringify(data),
headers: {
'content-type': 'application/json',
},
},
const data = await upload.done()
return {
// @ts-expect-error For some reason `|| null` is not enough for TS
url: data?.Location || null,
extraData: {
response: {
responseText: JSON.stringify(data),
headers: {
'content-type': 'application/json',
},
})
})
})
},
},
}
}
}

Expand Down
111 changes: 52 additions & 59 deletions packages/@uppy/companion/src/server/controllers/s3.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
const express = require('express')
const {
CreateMultipartUploadCommand,
ListPartsCommand,
UploadPartCommand,
AbortMultipartUploadCommand,
CompleteMultipartUploadCommand,
} = require('@aws-sdk/client-s3')

const { createPresignedPost } = require('@aws-sdk/s3-presigned-post')
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner')

function rfc2047Encode (data) {
// eslint-disable-next-line no-param-reassign
Expand Down Expand Up @@ -32,7 +42,9 @@ module.exports = function s3 (config) {
* - fields - Form fields to send along.
*/
function getUploadParameters (req, res, next) {
// @ts-ignore The `companion` property is added by middleware before reaching here.
/**
* @type {import('@aws-sdk/client-s3').S3Client}
*/
const client = req.companion.s3Client

if (!client || typeof config.bucket !== 'string') {
Expand All @@ -48,7 +60,6 @@ module.exports = function s3 (config) {
}

const fields = {
key,
success_action_status: '201',
'content-type': req.query.type,
}
Expand All @@ -59,23 +70,20 @@ module.exports = function s3 (config) {
fields[`x-amz-meta-${metadataKey}`] = metadata[metadataKey]
})

client.createPresignedPost({
createPresignedPost(client, {
Bucket: config.bucket,
Expires: config.expires,
Fields: fields,
Conditions: config.conditions,
}, (err, data) => {
if (err) {
next(err)
return
}
Key: key,
}).then(data => {
res.json({
method: 'post',
url: data.url,
fields: data.fields,
expires: config.expires,
})
})
}, next)
}

/**
Expand All @@ -93,7 +101,9 @@ module.exports = function s3 (config) {
* - uploadId - The ID of this multipart upload, to be used in later requests.
*/
function createMultipartUpload (req, res, next) {
// @ts-ignore The `companion` property is added by middleware before reaching here.
/**
* @type {import('@aws-sdk/client-s3').S3Client}
*/
const client = req.companion.s3Client
const key = config.getKey(req, req.body.filename, req.body.metadata || {})
const { type, metadata } = req.body
Expand All @@ -115,16 +125,12 @@ module.exports = function s3 (config) {

if (config.acl != null) params.ACL = config.acl

client.createMultipartUpload(params, (err, data) => {
if (err) {
next(err)
return
}
client.send(new CreateMultipartUploadCommand(params)).then((data) => {
res.json({
key: data.Key,
uploadId: data.UploadId,
})
})
}, next)
}

/**
Expand All @@ -141,7 +147,9 @@ module.exports = function s3 (config) {
* - Size - size of this part.
*/
function getUploadedParts (req, res, next) {
// @ts-ignore The `companion` property is added by middleware before reaching here.
/**
* @type {import('@aws-sdk/client-s3').S3Client}
*/
const client = req.companion.s3Client
const { uploadId } = req.params
const { key } = req.query
Expand All @@ -154,17 +162,12 @@ module.exports = function s3 (config) {
let parts = []

function listPartsPage (startAt) {
client.listParts({
client.send(new ListPartsCommand({
Bucket: config.bucket,
Key: key,
UploadId: uploadId,
PartNumberMarker: startAt,
}, (err, data) => {
if (err) {
next(err)
return
}

})).then(data => {
parts = parts.concat(data.Parts)

if (data.IsTruncated) {
Expand All @@ -173,7 +176,7 @@ module.exports = function s3 (config) {
} else {
res.json(parts)
}
})
}, next)
}
listPartsPage(0)
}
Expand All @@ -190,7 +193,9 @@ module.exports = function s3 (config) {
* - url - The URL to upload to, including signed query parameters.
*/
function signPartUpload (req, res, next) {
// @ts-ignore The `companion` property is added by middleware before reaching here.
/**
* @type {import('@aws-sdk/client-s3').S3Client}
*/
const client = req.companion.s3Client
const { uploadId, partNumber } = req.params
const { key } = req.query
Expand All @@ -204,20 +209,15 @@ module.exports = function s3 (config) {
return
}

client.getSignedUrl('uploadPart', {
getSignedUrl(client, new UploadPartCommand({
Bucket: config.bucket,
Key: key,
UploadId: uploadId,
PartNumber: partNumber,
Body: '',
Expires: config.expires,
}, (err, url) => {
if (err) {
next(err)
return
}
}), { expiresIn: config.expires }).then(url => {
res.json({ url, expires: config.expires })
})
}, next)
}

/**
Expand All @@ -234,7 +234,9 @@ module.exports = function s3 (config) {
* in an object mapped to part numbers.
*/
function batchSignPartsUpload (req, res, next) {
// @ts-ignore The `companion` property is added by middleware before reaching here.
/**
* @type {import('@aws-sdk/client-s3').S3Client}
*/
const client = req.companion.s3Client
const { uploadId } = req.params
const { key, partNumbers } = req.query
Expand All @@ -257,24 +259,21 @@ module.exports = function s3 (config) {

Promise.all(
partNumbersArray.map((partNumber) => {
return client.getSignedUrlPromise('uploadPart', {
return getSignedUrl(client, new UploadPartCommand({
Bucket: config.bucket,
Key: key,
UploadId: uploadId,
PartNumber: partNumber,
PartNumber: Number(partNumber),
Body: '',
Expires: config.expires,
})
}), { expiresIn: config.expires })
}),
).then((urls) => {
const presignedUrls = Object.create(null)
for (let index = 0; index < partNumbersArray.length; index++) {
presignedUrls[partNumbersArray[index]] = urls[index]
}
res.json({ presignedUrls })
}).catch((err) => {
next(err)
})
}).catch(next)
}

/**
Expand All @@ -288,7 +287,9 @@ module.exports = function s3 (config) {
* Empty.
*/
function abortMultipartUpload (req, res, next) {
// @ts-ignore The `companion` property is added by middleware before reaching here.
/**
* @type {import('@aws-sdk/client-s3').S3Client}
*/
const client = req.companion.s3Client
const { uploadId } = req.params
const { key } = req.query
Expand All @@ -298,17 +299,11 @@ module.exports = function s3 (config) {
return
}

client.abortMultipartUpload({
client.send(new AbortMultipartUploadCommand({
Bucket: config.bucket,
Key: key,
UploadId: uploadId,
}, (err) => {
if (err) {
next(err)
return
}
res.json({})
})
})).then(() => res.json({}), next)
}

/**
Expand All @@ -324,7 +319,9 @@ module.exports = function s3 (config) {
* - location - The full URL to the object in the S3 bucket.
*/
function completeMultipartUpload (req, res, next) {
// @ts-ignore The `companion` property is added by middleware before reaching here.
/**
* @type {import('@aws-sdk/client-s3').S3Client}
*/
const client = req.companion.s3Client
const { uploadId } = req.params
const { key } = req.query
Expand All @@ -342,22 +339,18 @@ module.exports = function s3 (config) {
return
}

client.completeMultipartUpload({
client.send(new CompleteMultipartUploadCommand({
Bucket: config.bucket,
Key: key,
UploadId: uploadId,
MultipartUpload: {
Parts: parts,
},
}, (err, data) => {
if (err) {
next(err)
return
}
})).then(data => {
res.json({
location: data.Location,
})
})
}, next)
}

return express.Router()
Expand Down
Loading