diff --git a/examples/aws-nodejs/index.js b/examples/aws-nodejs/index.js index 6ece65bdf..9f704d433 100644 --- a/examples/aws-nodejs/index.js +++ b/examples/aws-nodejs/index.js @@ -11,19 +11,31 @@ const app = express() const port = process.env.PORT ?? 8080 const bodyParser = require('body-parser') -let s3Client -const aws = require('aws-sdk') +const { + S3Client, + AbortMultipartUploadCommand, + CompleteMultipartUploadCommand, + CreateMultipartUploadCommand, + ListPartsCommand, + PutObjectCommand, + UploadPartCommand, +} = require('@aws-sdk/client-s3') +const { getSignedUrl } = require('@aws-sdk/s3-request-presigner') -const expires = 800 // Define how long until a S3 signature expires. +/** + * @type {S3Client} + */ +let s3Client + +const expiresIn = 900 // Define how long until a S3 signature expires. function getS3Client () { - s3Client ??= new aws.S3({ - signatureVersion: 'v4', + s3Client ??= new S3Client({ region: process.env.COMPANION_AWS_REGION, - credentials : new aws.Credentials( - process.env.COMPANION_AWS_KEY, - process.env.COMPANION_AWS_SECRET, - ), + credentials : { + accessKeyId: process.env.COMPANION_AWS_KEY, + secretAccessKey: process.env.COMPANION_AWS_SECRET, + }, }) return s3Client } @@ -40,29 +52,22 @@ app.get('/drag', (req, res) => { res.sendFile(htmlPath) }) -app.post('/sign-s3', (req, res) => { - const s3 = getS3Client() +app.post('/sign-s3', (req, res, next) => { const Key = `${crypto.randomUUID()}-${req.body.filename}` const { contentType } = req.body - const s3Params = { + + getSignedUrl(getS3Client(), new PutObjectCommand({ Bucket: process.env.COMPANION_AWS_BUCKET, Key, - Expires: expires, ContentType: contentType, - } - - s3.getSignedUrl('putObject', s3Params, (err, data, next) => { - if (err) { - next(err) - return - } - const returnData = { - url: data, + }), { expiresIn }).then((url) => { + res.setHeader('Access-Control-Allow-Origin', '*') + res.json({ + url, method: 'PUT', - } - res.write(JSON.stringify(returnData)) + }) res.end() - }) + }, next) }) // === === @@ -86,11 +91,14 @@ app.post('/s3/multipart', (req, res, next) => { Metadata: metadata, } - return client.createMultipartUpload(params, (err, data) => { + const command = new CreateMultipartUploadCommand(params) + + return client.send(command, (err, data) => { if (err) { next(err) return } + res.setHeader('Access-Control-Allow-Origin', '*') res.json({ key: data.Key, uploadId: data.UploadId, @@ -104,7 +112,6 @@ function validatePartNumber (partNumber) { return Number.isInteger(partNumber) && partNumber >= 1 && partNumber <= 10_000 } app.get('/s3/multipart/:uploadId/:partNumber', (req, res, next) => { - const client = getS3Client() const { uploadId, partNumber } = req.params const { key } = req.query @@ -115,20 +122,16 @@ app.get('/s3/multipart/:uploadId/:partNumber', (req, res, next) => { return res.status(400).json({ error: 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"' }) } - return client.getSignedUrl('uploadPart', { + return getSignedUrl(getS3Client(), new UploadPartCommand({ Bucket: process.env.COMPANION_AWS_BUCKET, Key: key, UploadId: uploadId, PartNumber: partNumber, Body: '', - Expires: expires, - }, (err, url) => { - if (err) { - next(err) - return - } - res.json({ url, expires }) - }) + }), { expiresIn }).then((url) => { + res.setHeader('Access-Control-Allow-Origin', '*') + res.json({ url, expires: expiresIn }) + }, next) }) app.get('/s3/multipart/:uploadId', (req, res, next) => { @@ -144,12 +147,12 @@ app.get('/s3/multipart/:uploadId', (req, res, next) => { const parts = [] function listPartsPage (startAt) { - client.listParts({ + client.send(new ListPartsCommand({ Bucket: process.env.COMPANION_AWS_BUCKET, Key: key, UploadId: uploadId, PartNumberMarker: startAt, - }, (err, data) => { + }), (err, data) => { if (err) { next(err) return @@ -169,7 +172,7 @@ app.get('/s3/multipart/:uploadId', (req, res, next) => { }) function isValidPart (part) { - return part && typeof part === 'object' && typeof part.PartNumber === 'number' && typeof part.ETag === 'string' + return part && typeof part === 'object' && Number(part.PartNumber) && typeof part.ETag === 'string' } app.post('/s3/multipart/:uploadId/complete', (req, res, next) => { const client = getS3Client() @@ -184,18 +187,19 @@ app.post('/s3/multipart/:uploadId/complete', (req, res, next) => { return res.status(400).json({ error: 's3: `parts` must be an array of {ETag, PartNumber} objects.' }) } - return client.completeMultipartUpload({ + return client.send(new CompleteMultipartUploadCommand({ Bucket: process.env.COMPANION_AWS_BUCKET, Key: key, UploadId: uploadId, MultipartUpload: { Parts: parts, }, - }, (err, data) => { + }), (err, data) => { if (err) { next(err) return } + res.setHeader('Access-Control-Allow-Origin', '*') res.json({ location: data.Location, }) @@ -211,11 +215,11 @@ app.delete('/s3/multipart/:uploadId', (req, res, next) => { return res.status(400).json({ error: 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"' }) } - return client.abortMultipartUpload({ + return client.send(new AbortMultipartUploadCommand({ Bucket: process.env.COMPANION_AWS_BUCKET, Key: key, UploadId: uploadId, - }, (err) => { + }), (err) => { if (err) { next(err) return diff --git a/examples/aws-nodejs/package.json b/examples/aws-nodejs/package.json index c0d651624..5a7ad0c11 100644 --- a/examples/aws-nodejs/package.json +++ b/examples/aws-nodejs/package.json @@ -8,10 +8,10 @@ "start": "node index.js" }, "private": true, - "author": "", "license": "MIT", "dependencies": { - "aws-sdk": "^2.1038.0", + "@aws-sdk/client-s3": "^3.338.0", + "@aws-sdk/s3-request-presigner": "^3.338.0", "body-parser": "^1.20.0", "dotenv": "^16.0.0", "express": "^4.18.1" diff --git a/yarn.lock b/yarn.lock index 9d660f16c..9fa8ccff6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10100,7 +10100,8 @@ __metadata: version: 0.0.0-use.local resolution: "@uppy-example/aws-nodejs@workspace:examples/aws-nodejs" dependencies: - aws-sdk: ^2.1038.0 + "@aws-sdk/client-s3": ^3.338.0 + "@aws-sdk/s3-request-presigner": ^3.338.0 body-parser: ^1.20.0 dotenv: ^16.0.0 express: ^4.18.1