mirror of
https://github.com/transloadit/uppy.git
synced 2026-07-17 16:50:22 +00:00
parent
489d76717c
commit
d9742ba4e4
27 changed files with 2332 additions and 439 deletions
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"@uppy/aws-s3": patch
|
||||
---
|
||||
|
||||
Improve type
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"@uppy/companion": major
|
||||
---
|
||||
|
||||
Upgrade to express 5 - Companion no longer works when used as a middleware with Express 4.
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.0.1",
|
||||
"express": "^5.2.1",
|
||||
"express": "^4.22.0",
|
||||
"express-session": "^1.17.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"vite": "^7.1.11"
|
||||
|
|
|
|||
|
|
@ -15,6 +15,6 @@
|
|||
"@aws-sdk/s3-request-presigner": "^3.338.0",
|
||||
"body-parser": "^1.20.4",
|
||||
"dotenv": "^16.0.0",
|
||||
"express": "^5.2.1"
|
||||
"express": "^4.22.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
"@uppy/companion": "workspace:*",
|
||||
"body-parser": "^1.20.4",
|
||||
"dotenv": "^16.0.1",
|
||||
"express": "^5.2.1",
|
||||
"express": "^4.22.0",
|
||||
"express-session": "^1.15.6",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"vite": "^7.1.11"
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"dotenv": "^16.0.1",
|
||||
"express": "^5.2.1",
|
||||
"express": "^4.22.0",
|
||||
"vite": "^7.1.11"
|
||||
},
|
||||
"private": true,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"dependencies": {
|
||||
"@uppy/companion": "workspace:*",
|
||||
"body-parser": "^1.20.4",
|
||||
"express": "^5.2.1",
|
||||
"express": "^4.22.0",
|
||||
"express-session": "^1.15.6",
|
||||
"light-server": "^2.4.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -20,14 +20,14 @@
|
|||
"@uppy/transloadit": "workspace:*",
|
||||
"@uppy/tus": "workspace:*",
|
||||
"@uppy/xhr-upload": "workspace:*",
|
||||
"express": "^5.2.1",
|
||||
"express": "^4.22.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router": "^7.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-router/dev": "^7.12.0",
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/express": "^4.17.20",
|
||||
"@types/react": "^18.2.20",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"tsx": "^4.0.0",
|
||||
|
|
|
|||
|
|
@ -41,9 +41,7 @@ async function startServer() {
|
|||
|
||||
// TUS upload endpoints (before React Router)
|
||||
app.all('/api/upload/tus', (req, res) => tusServer.handle(req, res))
|
||||
app.all('/api/upload/tus/{*splat}', (req, res) =>
|
||||
tusServer.handle(req, res),
|
||||
)
|
||||
app.all('/api/upload/tus/*', (req, res) => tusServer.handle(req, res))
|
||||
|
||||
// Handle Chrome DevTools requests silently
|
||||
app.get('/.well-known/appspecific/com.chrome.devtools.json', (req, res) => {
|
||||
|
|
@ -51,7 +49,7 @@ async function startServer() {
|
|||
})
|
||||
|
||||
// React Router handles all other routes
|
||||
app.all('/{*splat}', reactRouterHandler)
|
||||
app.all('*', reactRouterHandler)
|
||||
|
||||
const port = process.env.PORT || 3000
|
||||
const server = app.listen(port, () => {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
"@uppy/remote-sources": "workspace:*",
|
||||
"@uppy/transloadit": "workspace:*",
|
||||
"@uppy/webcam": "workspace:*",
|
||||
"express": "^5.2.1",
|
||||
"express": "^4.22.0",
|
||||
"he": "^1.2.0"
|
||||
},
|
||||
"private": true,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
"@uppy/dashboard": "workspace:*",
|
||||
"@uppy/xhr-upload": "workspace:*",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^5.2.1",
|
||||
"express": "^4.22.0",
|
||||
"multer": "^2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -662,11 +662,10 @@ export default class AwsS3Multipart<
|
|||
querify: true,
|
||||
})
|
||||
|
||||
const query = new URLSearchParams({
|
||||
filename,
|
||||
...(type != null && { type }),
|
||||
...metadata,
|
||||
})
|
||||
const query = new URLSearchParams({ filename, type, ...metadata } as Record<
|
||||
string,
|
||||
string
|
||||
>)
|
||||
|
||||
return this.#client.get(`s3/params?${query}`, options)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,9 +47,9 @@
|
|||
"cors": "^2.8.5",
|
||||
"escape-goat": "3.0.0",
|
||||
"escape-string-regexp": "4.0.0",
|
||||
"express": "^5.2.1",
|
||||
"express": "4.21.2",
|
||||
"express-interceptor": "1.2.0",
|
||||
"express-prom-bundle": "^8.0.0",
|
||||
"express-prom-bundle": "7",
|
||||
"express-session": "1.18.1",
|
||||
"fast-safe-stringify": "^2.1.1",
|
||||
"formdata-node": "^6.0.3",
|
||||
|
|
|
|||
|
|
@ -125,9 +125,6 @@ export function app(optionsArg: CompanionInitOptions) {
|
|||
|
||||
const app = express()
|
||||
|
||||
// Needed for e.g. s3 `/params` endpoint metadata, like `metadata[key]=value` to be parsed into a metadata object
|
||||
app.set('query parser', 'extended')
|
||||
|
||||
if (options.metrics) {
|
||||
app.use(middlewares.metrics({ path: options.server.path }))
|
||||
}
|
||||
|
|
@ -139,10 +136,10 @@ export function app(optionsArg: CompanionInitOptions) {
|
|||
app.use(interceptGrantErrorResponse)
|
||||
|
||||
// override provider credentials at request time
|
||||
// Making `POST` request to the `/connect/:provider{/:override}` route requires a form body parser middleware:
|
||||
// Making `POST` request to the `/connect/:provider/:override?` route requires a form body parser middleware:
|
||||
// See https://github.com/simov/grant#dynamic-http
|
||||
app.use(
|
||||
'/connect/:oauthProvider{/:override}',
|
||||
'/connect/:oauthProvider/:override?',
|
||||
express.urlencoded({ extended: false }),
|
||||
getCredentialsOverrideMiddleware(
|
||||
providers as Record<string, typeof Provider>,
|
||||
|
|
@ -162,7 +159,7 @@ export function app(optionsArg: CompanionInitOptions) {
|
|||
app.use(middlewares.cors(options))
|
||||
|
||||
// add uppy options to the request object so it can be accessed by subsequent handlers.
|
||||
app.use(middlewares.getCompanionMiddleware(options))
|
||||
app.use('*', middlewares.getCompanionMiddleware(options))
|
||||
app.use('/s3', s3(options.s3))
|
||||
if (options.enableUrlEndpoint) app.use('/url', url())
|
||||
if (options.enableGooglePickerEndpoint)
|
||||
|
|
@ -235,7 +232,7 @@ export function app(optionsArg: CompanionInitOptions) {
|
|||
)
|
||||
|
||||
app.get(
|
||||
'/:providerName/list{/:id}',
|
||||
'/:providerName/list/:id?',
|
||||
middlewares.hasSessionAndProvider,
|
||||
middlewares.verifyToken,
|
||||
controllers.list,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export default function callback(
|
|||
const providerName = req.params['providerName']
|
||||
const { companion } = req
|
||||
|
||||
if (typeof providerName !== 'string' || providerName.length === 0) {
|
||||
if (providerName == null || providerName.length === 0) {
|
||||
res.sendStatus(400)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,8 @@ export default async function list(
|
|||
next: NextFunction,
|
||||
): Promise<void> {
|
||||
const { query, params, companion } = req
|
||||
const id = params['id']
|
||||
const { providerUserSession, provider } = companion
|
||||
if (!provider || (typeof id !== 'string' && id != null)) {
|
||||
if (!provider) {
|
||||
res.sendStatus(400)
|
||||
return
|
||||
}
|
||||
|
|
@ -18,7 +17,7 @@ export default async function list(
|
|||
const data = await provider.list({
|
||||
companion,
|
||||
providerUserSession,
|
||||
directory: id,
|
||||
directory: params['id'],
|
||||
query,
|
||||
})
|
||||
res.json(data)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export default function preauth(req: Request, res: Response): void {
|
|||
}
|
||||
|
||||
const providerName = req.params['providerName']
|
||||
if (typeof providerName !== 'string' || providerName.length === 0) {
|
||||
if (providerName == null || providerName.length === 0) {
|
||||
res.sendStatus(400)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export default async function refreshToken(
|
|||
next: NextFunction,
|
||||
): Promise<void> {
|
||||
const providerName = req.params['providerName']
|
||||
if (typeof providerName !== 'string' || providerName.length === 0) {
|
||||
if (providerName == null) {
|
||||
res.sendStatus(400)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import assert from 'node:assert'
|
||||
import type { Part, S3Client } from '@aws-sdk/client-s3'
|
||||
import {
|
||||
AbortMultipartUploadCommand,
|
||||
|
|
@ -248,11 +247,6 @@ export default function s3(
|
|||
const { uploadId } = req.params
|
||||
const { key } = req.query
|
||||
|
||||
assert(
|
||||
typeof uploadId === 'string' && uploadId.length > 0,
|
||||
's3: uploadId must be provided.',
|
||||
)
|
||||
|
||||
if (typeof key !== 'string') {
|
||||
res.status(400).json({
|
||||
error:
|
||||
|
|
@ -266,7 +260,7 @@ export default function s3(
|
|||
|
||||
const parts: Part[] = []
|
||||
|
||||
const listPartsPage = (startAt?: string) => {
|
||||
function listPartsPage(startAt?: string) {
|
||||
s3Client
|
||||
.send(
|
||||
new ListPartsCommand({
|
||||
|
|
@ -309,7 +303,7 @@ export default function s3(
|
|||
const partNumber = req.params['partNumber']
|
||||
const key = req.query['key']
|
||||
|
||||
if (typeof uploadId !== 'string' || uploadId.length === 0) {
|
||||
if (uploadId == null || uploadId.length === 0) {
|
||||
res.status(400).json({ error: 's3: uploadId must be provided.' })
|
||||
return
|
||||
}
|
||||
|
|
@ -320,7 +314,7 @@ export default function s3(
|
|||
})
|
||||
return
|
||||
}
|
||||
if (typeof partNumber !== 'string' || !parseInt(partNumber, 10)) {
|
||||
if (partNumber == null || !parseInt(partNumber, 10)) {
|
||||
res.status(400).json({
|
||||
error: 's3: the part number must be a number between 1 and 10000.',
|
||||
})
|
||||
|
|
@ -369,7 +363,7 @@ export default function s3(
|
|||
const key = req.query['key']
|
||||
const partNumbers = req.query['partNumbers']
|
||||
|
||||
if (typeof uploadId !== 'string' || uploadId.length === 0) {
|
||||
if (uploadId == null || uploadId.length === 0) {
|
||||
res.status(400).json({ error: 's3: uploadId must be provided.' })
|
||||
return
|
||||
}
|
||||
|
|
@ -449,11 +443,6 @@ export default function s3(
|
|||
const { uploadId } = req.params
|
||||
const { key } = req.query
|
||||
|
||||
assert(
|
||||
typeof uploadId === 'string' && uploadId.length > 0,
|
||||
's3: uploadId must be provided.',
|
||||
)
|
||||
|
||||
if (typeof key !== 'string') {
|
||||
res.status(400).json({
|
||||
error:
|
||||
|
|
@ -499,11 +488,6 @@ export default function s3(
|
|||
const { key } = req.query
|
||||
const { parts }: { parts: unknown } = req.body
|
||||
|
||||
assert(
|
||||
typeof uploadId === 'string' && uploadId.length > 0,
|
||||
's3: uploadId must be provided.',
|
||||
)
|
||||
|
||||
if (typeof key !== 'string') {
|
||||
res.status(400).json({
|
||||
error:
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export default async function simpleAuth(
|
|||
next: NextFunction,
|
||||
): Promise<void> {
|
||||
const providerName = req.params['providerName']
|
||||
if (typeof providerName !== 'string' || providerName.length === 0) {
|
||||
if (providerName == null || providerName.length === 0) {
|
||||
res.sendStatus(400)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ async function thumbnail(
|
|||
next: NextFunction,
|
||||
): Promise<void> {
|
||||
const id = req.params['id']
|
||||
if (typeof id !== 'string' || id.length === 0) {
|
||||
if (id == null) {
|
||||
res.sendStatus(400)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ export const verifyToken: RequestHandler = (req, res, next) => {
|
|||
}
|
||||
const { secret } = req.companion.options
|
||||
const providerName = req.params['providerName']
|
||||
if (typeof providerName !== 'string' || providerName.length === 0) {
|
||||
if (providerName == null || providerName.length === 0) {
|
||||
res.sendStatus(400)
|
||||
return
|
||||
}
|
||||
|
|
@ -124,9 +124,7 @@ export const verifyToken: RequestHandler = (req, res, next) => {
|
|||
const { providerOptions } = req.companion.options
|
||||
const providerName = req.params['providerName']
|
||||
const providerOption =
|
||||
typeof providerName === 'string'
|
||||
? providerOptions?.[providerName]
|
||||
: undefined
|
||||
providerName != null ? providerOptions?.[providerName] : undefined
|
||||
const key = providerOption?.key
|
||||
if (!key) {
|
||||
logger.info(
|
||||
|
|
@ -148,7 +146,7 @@ export const verifyToken: RequestHandler = (req, res, next) => {
|
|||
// does not fail if token is invalid
|
||||
export const gentleVerifyToken: RequestHandler = (req, res, next) => {
|
||||
const providerName = req.params['providerName']
|
||||
if (typeof providerName !== 'string' || providerName.length === 0) {
|
||||
if (providerName == null || providerName.length === 0) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,7 @@ const port: string | number =
|
|||
|
||||
const { app, companionOptions } = standalone()
|
||||
|
||||
const server = app.listen(port, () => {
|
||||
logger.info(`Welcome to Companion! v${packageJson.version}`)
|
||||
logger.info(`Listening on http://localhost:${port}`)
|
||||
companion.socket(app.listen(port), companionOptions)
|
||||
|
||||
companion.socket(server, companionOptions)
|
||||
})
|
||||
logger.info(`Welcome to Companion! v${packageJson.version}`)
|
||||
logger.info(`Listening on http://localhost:${port}`)
|
||||
|
|
|
|||
|
|
@ -426,26 +426,4 @@ describe('S3 controller', () => {
|
|||
),
|
||||
)
|
||||
})
|
||||
|
||||
test('getUploadParameters supports bracket metadata query format', async () => {
|
||||
const server = await getServer({
|
||||
COMPANION_AWS_KEY: 'test_key',
|
||||
COMPANION_AWS_SECRET: 'test_secret',
|
||||
COMPANION_AWS_BUCKET: 'test-bucket',
|
||||
COMPANION_AWS_REGION: 'us-east-1',
|
||||
})
|
||||
|
||||
return request(server)
|
||||
.get('/s3/params')
|
||||
.query({
|
||||
filename: 'test.txt',
|
||||
type: 'text/plain',
|
||||
'metadata[name]': 'demo-file',
|
||||
})
|
||||
.expect(200)
|
||||
.then((res) => {
|
||||
const fields = res.body.fields as Record<string, string>
|
||||
expect(fields['x-amz-meta-name']).toBe('demo-file')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ async function run({
|
|||
next()
|
||||
})
|
||||
app.use(cors(corsOptions))
|
||||
app.options('/{*splat}', (_req, res) => {
|
||||
app.options('*', (_req, res) => {
|
||||
res.status(204).end()
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ export const getServerWithEmitter = async (
|
|||
authServer.use(
|
||||
session({ secret: 'grant', resave: true, saveUninitialized: true }),
|
||||
)
|
||||
authServer.all('{*splat}/callback', (req, res, next) => {
|
||||
authServer.all('*/callback', (req, res, next) => {
|
||||
if (req.session) {
|
||||
req.session['grant'] = {
|
||||
response: { access_token: grantToken },
|
||||
|
|
@ -109,19 +109,16 @@ export const getServerWithEmitter = async (
|
|||
}
|
||||
next()
|
||||
})
|
||||
authServer.all(
|
||||
['{*splat}/send-token', '{*splat}/redirect'],
|
||||
(req, res, next) => {
|
||||
const state =
|
||||
typeof req.query['state'] === 'string' ? req.query['state'] : null
|
||||
if (req.session) {
|
||||
req.session['grant'] = {
|
||||
dynamic: { state: state ?? 'non-empty-value' },
|
||||
}
|
||||
authServer.all(['*/send-token', '*/redirect'], (req, res, next) => {
|
||||
const state =
|
||||
typeof req.query['state'] === 'string' ? req.query['state'] : null
|
||||
if (req.session) {
|
||||
req.session['grant'] = {
|
||||
dynamic: { state: state ?? 'non-empty-value' },
|
||||
}
|
||||
next()
|
||||
},
|
||||
)
|
||||
}
|
||||
next()
|
||||
})
|
||||
|
||||
const { app, emitter } = standalone()
|
||||
authServer.use(app)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue