mirror of
https://github.com/transloadit/uppy.git
synced 2026-07-17 16:50:22 +00:00
test(tus): add minimal local-upload example to reproduce #6287
@uppy/core + @uppy/tus (local files only, no Dashboard/providers/Companion) against a tus-node-server backend that rejects *.bin files with HTTP 403 + a JSON body. One running server reproduces both the error path (#6287) and the success path.
This commit is contained in:
parent
e311bb9371
commit
031bfcaf1d
8 changed files with 319 additions and 0 deletions
1
examples/tus-min/.gitignore
vendored
Normal file
1
examples/tus-min/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
uploads/
|
||||
59
examples/tus-min/README.md
Normal file
59
examples/tus-min/README.md
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# example-tus-min
|
||||
|
||||
Minimal reproduction for [issue #6287](https://github.com/transloadit/uppy/issues/6287):
|
||||
`@uppy/core` + `@uppy/tus` (local file uploads only — no Dashboard, no
|
||||
providers, no Companion) talking to a [`tus-node-server`](https://github.com/tus/tus-node-server)
|
||||
backend (`@tus/server` + `@tus/file-store`).
|
||||
|
||||
The server rejects any file whose name ends in `.bin` with `HTTP 403` and a JSON
|
||||
body, mimicking the "BIN content type is disallowed" server in the bug report.
|
||||
So one running server reproduces both paths:
|
||||
|
||||
- a **normal file** → success path
|
||||
- a **`*.bin` file** → error path (#6287)
|
||||
|
||||
## Run
|
||||
|
||||
From the repo root, make sure the workspace packages are built so the example
|
||||
resolves the local `@uppy/tus`:
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
yarn workspace @uppy/tus build # or `yarn build` for everything
|
||||
```
|
||||
|
||||
Then start the client + server together:
|
||||
|
||||
```bash
|
||||
yarn workspace example-tus-min start
|
||||
```
|
||||
|
||||
- client: <http://localhost:5173>
|
||||
- tus server: <http://127.0.0.1:1080/files>
|
||||
|
||||
Uploaded files land in `examples/tus-min/uploads/` (gitignored).
|
||||
|
||||
## What to look for
|
||||
|
||||
Open <http://localhost:5173> and watch the on-page log (and the devtools
|
||||
console). `window.uppy` is exposed for poking around.
|
||||
|
||||
### Error path — pick a `*.bin` file (e.g. rename anything to `test.bin`)
|
||||
|
||||
| | without the fix (`main`) | with the fix |
|
||||
| --- | --- | --- |
|
||||
| `upload-error` `response` | `undefined` | `{ status: 403, body: { xhr } }` |
|
||||
| `response.body.xhr.responseText` | n/a | the server's JSON message |
|
||||
|
||||
### Success path — pick any normal file
|
||||
|
||||
| | without the fix (`main`) | with the fix |
|
||||
| --- | --- | --- |
|
||||
| `upload-success` `response.body.xhr.status` | `0` (xhr was reset) | `204` |
|
||||
|
||||
> `response.status` is always `200` on success because the Tus plugin hardcodes
|
||||
> it — the tell for the success-path bug is specifically
|
||||
> `response.body.xhr.status === 0`.
|
||||
|
||||
To compare buggy vs. fixed behavior, rebuild `@uppy/tus` on each branch
|
||||
(`yarn workspace @uppy/tus build`) and restart.
|
||||
46
examples/tus-min/index.html
Normal file
46
examples/tus-min/index.html
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Uppy + Tus — minimal local-upload repro (#6287)</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, sans-serif;
|
||||
max-width: 720px;
|
||||
margin: 2rem auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
pre {
|
||||
background: #f4f4f4;
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
margin: 0;
|
||||
}
|
||||
#log {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
code {
|
||||
background: #eee;
|
||||
padding: 0 0.25rem;
|
||||
border-radius: 3px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Uppy + Tus — minimal local-upload repro</h1>
|
||||
<p>
|
||||
Pick a normal file → <strong>success</strong> path.<br />
|
||||
Pick a file whose name ends in <code>.bin</code> → server returns
|
||||
<strong>HTTP 403</strong> with a JSON body (error path, issue #6287).
|
||||
</p>
|
||||
<input id="file" type="file" multiple />
|
||||
<h2>Log (newest first)</h2>
|
||||
<div id="log"></div>
|
||||
<script type="module" src="./main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
63
examples/tus-min/main.js
Normal file
63
examples/tus-min/main.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import Uppy from '@uppy/core'
|
||||
import Tus from '@uppy/tus'
|
||||
|
||||
const TUS_ENDPOINT = 'http://localhost:1080/files/'
|
||||
|
||||
const logEl = document.querySelector('#log')
|
||||
function log(label, data) {
|
||||
console.log(label, data)
|
||||
const pre = document.createElement('pre')
|
||||
pre.textContent =
|
||||
data === undefined ? label : `${label}\n${JSON.stringify(data, null, 2)}`
|
||||
logEl.prepend(pre)
|
||||
}
|
||||
|
||||
// Inspect the parts of `response` that the bug affects. `response.body.xhr` is
|
||||
// an XMLHttpRequest, so we read its `status`/`responseText` rather than dumping
|
||||
// the object.
|
||||
function describeResponse(response) {
|
||||
if (response === undefined) return '⚠️ response is undefined'
|
||||
const xhr = response.body?.xhr
|
||||
return {
|
||||
status: response.status,
|
||||
'body.xhr.status': xhr?.status,
|
||||
'body.xhr.responseText': xhr?.responseText,
|
||||
}
|
||||
}
|
||||
|
||||
// Only the Tus uploader — no Dashboard, no providers, no Companion.
|
||||
const uppy = new Uppy({ autoProceed: true, debug: true }).use(Tus, {
|
||||
endpoint: TUS_ENDPOINT,
|
||||
})
|
||||
|
||||
uppy.on('upload-success', (file, response) => {
|
||||
// BUG (success path): on a build without the fix, `body.xhr.status` is 0
|
||||
// because the completed request was aborted during cleanup.
|
||||
log('✅ upload-success', describeResponse(response))
|
||||
})
|
||||
|
||||
uppy.on('upload-error', (file, error, response) => {
|
||||
// BUG (#6287): on a build without the fix, `response` is `undefined`.
|
||||
log('❌ upload-error', describeResponse(response))
|
||||
log(' error.message', error.message)
|
||||
})
|
||||
|
||||
uppy.on('complete', (result) => {
|
||||
const failed = result.failed?.[0]
|
||||
log('🏁 complete — failed[0].response', failed ? describeResponse(failed.response) : '(none failed)')
|
||||
})
|
||||
|
||||
document.querySelector('#file').addEventListener('change', (event) => {
|
||||
const input = event.target
|
||||
for (const file of input.files) {
|
||||
try {
|
||||
uppy.addFile({ name: file.name, type: file.type, data: file })
|
||||
} catch (err) {
|
||||
log('addFile error', err.message)
|
||||
}
|
||||
}
|
||||
input.value = ''
|
||||
})
|
||||
|
||||
// Handy for poking at state from the devtools console.
|
||||
window.uppy = uppy
|
||||
21
examples/tus-min/package.json
Normal file
21
examples/tus-min/package.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "example-tus-min",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "run-p start:server start:client",
|
||||
"start:client": "vite",
|
||||
"start:server": "node server.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tus/file-store": "^2.1.0",
|
||||
"@tus/server": "^2.4.1",
|
||||
"@uppy/core": "workspace:*",
|
||||
"@uppy/tus": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"npm-run-all": "^4.1.5",
|
||||
"vite": "^7.1.11"
|
||||
}
|
||||
}
|
||||
43
examples/tus-min/server.mjs
Normal file
43
examples/tus-min/server.mjs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { FileStore } from '@tus/file-store'
|
||||
import { Server } from '@tus/server'
|
||||
|
||||
const host = '127.0.0.1'
|
||||
const port = 1080
|
||||
|
||||
// The Vite dev client (start:client) runs on this origin. The tus server only
|
||||
// sets CORS headers for origins listed here.
|
||||
const clientOrigin = 'http://localhost:5173'
|
||||
|
||||
const server = new Server({
|
||||
path: '/files',
|
||||
datastore: new FileStore({ directory: './uploads' }),
|
||||
allowedOrigins: [clientOrigin],
|
||||
|
||||
// Reproduce issue #6287: reject some uploads with a non-2xx + JSON body,
|
||||
// exactly like the server in the bug report ("BIN content type is disallowed").
|
||||
//
|
||||
// Any file whose name ends in `.bin` is rejected at creation, so you can test
|
||||
// BOTH paths against the same running server:
|
||||
// - a normal file -> success path
|
||||
// - a `*.bin` file -> error path (HTTP 403 with a JSON body)
|
||||
async onUploadCreate(req, upload) {
|
||||
const filename = upload.metadata?.filename ?? ''
|
||||
if (filename.toLowerCase().endsWith('.bin')) {
|
||||
throw {
|
||||
status_code: 403,
|
||||
body: JSON.stringify({
|
||||
message:
|
||||
'File cannot be uploaded as the BIN content type is disallowed!',
|
||||
status_code: 403,
|
||||
}),
|
||||
}
|
||||
}
|
||||
// Accept the upload unchanged. (The hook must resolve to an object.)
|
||||
return { metadata: upload.metadata }
|
||||
},
|
||||
})
|
||||
|
||||
server.listen({ host, port }, () => {
|
||||
console.log(`tus server listening on http://${host}:${port}/files`)
|
||||
console.log(`CORS allowed for: ${clientOrigin}`)
|
||||
})
|
||||
6
examples/tus-min/vite.config.js
Normal file
6
examples/tus-min/vite.config.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { defineConfig } from 'vite'
|
||||
|
||||
// Fixed port so the server's CORS allow-list (http://localhost:5173) matches.
|
||||
export default defineConfig({
|
||||
server: { port: 5173, strictPort: true },
|
||||
})
|
||||
80
yarn.lock
80
yarn.lock
|
|
@ -5681,6 +5681,23 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@redis/client@npm:^5.0.0":
|
||||
version: 5.12.1
|
||||
resolution: "@redis/client@npm:5.12.1"
|
||||
dependencies:
|
||||
cluster-key-slot: "npm:1.1.2"
|
||||
peerDependencies:
|
||||
"@node-rs/xxhash": ^1.1.0
|
||||
"@opentelemetry/api": ">=1 <2"
|
||||
peerDependenciesMeta:
|
||||
"@node-rs/xxhash":
|
||||
optional: true
|
||||
"@opentelemetry/api":
|
||||
optional: true
|
||||
checksum: 10/6e7ac59761201ca0cb5eeff78e18b67eb0db2071b6bf3306d08697999a244329dd01a4d521963eb134da87c8ef6689ed52648f5fc15d6d2cd84a77cd5f3351c4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@remix-run/node-fetch-server@npm:^0.9.0":
|
||||
version: 0.9.0
|
||||
resolution: "@remix-run/node-fetch-server@npm:0.9.0"
|
||||
|
|
@ -7659,6 +7676,20 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tus/file-store@npm:^2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "@tus/file-store@npm:2.1.0"
|
||||
dependencies:
|
||||
"@redis/client": "npm:^5.0.0"
|
||||
"@tus/utils": "npm:^0.7.0"
|
||||
debug: "npm:^4.3.4"
|
||||
dependenciesMeta:
|
||||
"@redis/client":
|
||||
optional: true
|
||||
checksum: 10/b057d9fffbb596213f83a9daf468d1bf0e25c1cb9a1eba517392d30ece00024ff9a225247225becec4fe7278738998ad19d3d94c2a975a4d465473f941dc66f6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tus/file-store@npm:latest":
|
||||
version: 2.0.0
|
||||
resolution: "@tus/file-store@npm:2.0.0"
|
||||
|
|
@ -7673,6 +7704,26 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tus/server@npm:^2.4.1":
|
||||
version: 2.4.1
|
||||
resolution: "@tus/server@npm:2.4.1"
|
||||
dependencies:
|
||||
"@redis/client": "npm:^5.0.0"
|
||||
"@tus/utils": "npm:^0.7.0"
|
||||
debug: "npm:^4.3.4"
|
||||
ioredis: "npm:^5.4.1"
|
||||
lodash.throttle: "npm:^4.1.1"
|
||||
set-cookie-parser: "npm:^2.7.1"
|
||||
srvx: "npm:~0.11.15"
|
||||
dependenciesMeta:
|
||||
"@redis/client":
|
||||
optional: true
|
||||
ioredis:
|
||||
optional: true
|
||||
checksum: 10/d1ac62b025bff046737482559d8a0ad7711cc53e70b339581cd698216bef1e124c26da45e79871b74b00767b486d8d43ca83d397e25ced29526e8d00c71a977b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tus/server@npm:latest":
|
||||
version: 2.3.0
|
||||
resolution: "@tus/server@npm:2.3.0"
|
||||
|
|
@ -7700,6 +7751,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tus/utils@npm:^0.7.0":
|
||||
version: 0.7.0
|
||||
resolution: "@tus/utils@npm:0.7.0"
|
||||
checksum: 10/2c5235e8cbdc0c795f3db36261317244e27b2ca1c210c4f2a37cf36eea7d5f2f75f555ee9b629231c89a78844539dbc905802a1769ea5229d25ec8f405dec17c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tybys/wasm-util@npm:^0.10.0":
|
||||
version: 0.10.0
|
||||
resolution: "@tybys/wasm-util@npm:0.10.0"
|
||||
|
|
@ -13135,6 +13193,19 @@ __metadata:
|
|||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"example-tus-min@workspace:examples/tus-min":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "example-tus-min@workspace:examples/tus-min"
|
||||
dependencies:
|
||||
"@tus/file-store": "npm:^2.1.0"
|
||||
"@tus/server": "npm:^2.4.1"
|
||||
"@uppy/core": "workspace:*"
|
||||
"@uppy/tus": "workspace:*"
|
||||
npm-run-all: "npm:^4.1.5"
|
||||
vite: "npm:^7.1.11"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"example-vue@workspace:examples/vue":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "example-vue@workspace:examples/vue"
|
||||
|
|
@ -21303,6 +21374,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"srvx@npm:~0.11.15":
|
||||
version: 0.11.16
|
||||
resolution: "srvx@npm:0.11.16"
|
||||
bin:
|
||||
srvx: bin/srvx.mjs
|
||||
checksum: 10/5ec582e92d618220e24bbe9efe4900215081b1949327ee165f3cf4b01e766c458a8aaba50347e8fa1b536ac8b854e0e6d8cd970c62c64278487867699243124d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"srvx@npm:~0.8.2":
|
||||
version: 0.8.7
|
||||
resolution: "srvx@npm:0.8.7"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue