@tus/server
Advanced tools
Comparing version 1.0.0-beta.7 to 1.0.0
@@ -50,2 +50,3 @@ /// <reference types="node" /> | ||
handle(req: http.IncomingMessage, res: http.ServerResponse): Promise<http.ServerResponse | stream.Writable | void>; | ||
write(res: http.ServerResponse, status: number, body?: string, headers?: {}): http.ServerResponse<http.IncomingMessage>; | ||
listen(...args: any[]): http.Server; | ||
@@ -52,0 +53,0 @@ cleanUpExpiredUploads(): Promise<number>; |
@@ -82,10 +82,16 @@ "use strict"; | ||
} | ||
const onError = (error) => { | ||
const status_code = error.status_code || constants_1.ERRORS.UNKNOWN_ERROR.status_code; | ||
const body = error.body || `${constants_1.ERRORS.UNKNOWN_ERROR.body}${error.message || ''}\n`; | ||
return this.write(res, status_code, body); | ||
}; | ||
try { | ||
await this.options.onIncomingRequest?.(req, res); | ||
} | ||
catch (err) { | ||
return onError(err); | ||
} | ||
if (req.method === 'GET') { | ||
const handler = this.handlers.GET; | ||
return handler.send(req, res).catch((error) => { | ||
log(`[${handler.constructor.name}]`, error); | ||
const status_code = error.status_code || constants_1.ERRORS.UNKNOWN_ERROR.status_code; | ||
const body = error.body || `${constants_1.ERRORS.UNKNOWN_ERROR.body}${error.message || ''}\n`; | ||
return handler.write(res, status_code, {}, body); | ||
}); | ||
return handler.send(req, res).catch(onError); | ||
} | ||
@@ -97,4 +103,3 @@ // The Tus-Resumable header MUST be included in every request and | ||
if (req.method !== 'OPTIONS' && req.headers['tus-resumable'] === undefined) { | ||
res.writeHead(412, 'Precondition Failed'); | ||
return res.end('Tus-Resumable Required\n'); | ||
return this.write(res, 412, 'Tus-Resumable Required\n'); | ||
} | ||
@@ -121,5 +126,3 @@ // Validate all required headers to adhere to the tus protocol | ||
if (invalid_headers.length > 0) { | ||
// The request was not configured to the tus protocol | ||
res.writeHead(400, 'Bad Request'); | ||
return res.end(`Invalid ${invalid_headers.join(' ')}\n`); | ||
return this.write(res, 400, `Invalid ${invalid_headers.join(' ')}\n`); | ||
} | ||
@@ -134,12 +137,13 @@ // Enable CORS | ||
if (handler) { | ||
return handler.send(req, res).catch((error) => { | ||
log(`[${handler.constructor.name}]`, error); | ||
const status_code = error.status_code || constants_1.ERRORS.UNKNOWN_ERROR.status_code; | ||
const body = error.body || `${constants_1.ERRORS.UNKNOWN_ERROR.body}${error.message || ''}\n`; | ||
return handler.write(res, status_code, {}, body); | ||
}); | ||
return handler.send(req, res).catch(onError); | ||
} | ||
// 404 Anything else | ||
res.writeHead(404, {}); | ||
res.write('Not found\n'); | ||
return this.write(res, 404, 'Not found\n'); | ||
} | ||
write(res, status, body = '', headers = {}) { | ||
if (status !== 204) { | ||
// @ts-expect-error not explicitly typed but possible | ||
headers['Content-Length'] = Buffer.byteLength(body, 'utf8'); | ||
} | ||
res.writeHead(status, headers); | ||
res.write(body); | ||
return res.end(); | ||
@@ -146,0 +150,0 @@ } |
@@ -11,3 +11,4 @@ /// <reference types="node" /> | ||
onUploadFinish?: (req: http.IncomingMessage, res: http.ServerResponse, upload: Upload) => Promise<http.ServerResponse>; | ||
onIncomingRequest?: (req: http.IncomingMessage, res: http.ServerResponse) => Promise<void>; | ||
}; | ||
export type RouteHandler = (req: http.IncomingMessage, res: http.ServerResponse) => void; |
{ | ||
"$schema": "https://json.schemastore.org/package.json", | ||
"name": "@tus/server", | ||
"version": "1.0.0-beta.7", | ||
"version": "1.0.0", | ||
"description": "Tus resumable upload protocol in Node.js", | ||
@@ -24,20 +24,20 @@ "main": "dist/index.js", | ||
"dependencies": { | ||
"debug": "^4.3.3" | ||
"debug": "^4.3.4" | ||
}, | ||
"devDependencies": { | ||
"@types/debug": "^4.1.7", | ||
"@types/debug": "^4.1.8", | ||
"@types/mocha": "^10.0.1", | ||
"@types/node": "latest", | ||
"@types/sinon": "^10.0.13", | ||
"@types/node": "^20.5.7", | ||
"@types/sinon": "^10.0.16", | ||
"@types/supertest": "^2.0.12", | ||
"eslint": "^8.29.0", | ||
"eslint": "^8.48.0", | ||
"eslint-config-custom": "0.0.0", | ||
"mocha": "^10.1.0", | ||
"node-mocks-http": "^1.12.1", | ||
"mocha": "^10.2.0", | ||
"node-mocks-http": "^1.13.0", | ||
"should": "^13.2.3", | ||
"sinon": "^15.0.4", | ||
"supertest": "^6.3.2", | ||
"sinon": "^15.2.0", | ||
"supertest": "^6.3.3", | ||
"ts-node": "^10.9.1", | ||
"tsconfig": "*", | ||
"typescript": "latest" | ||
"typescript": "^5.2.2" | ||
}, | ||
@@ -44,0 +44,0 @@ "engines": { |
178
README.md
@@ -5,3 +5,3 @@ # `@tus/server` | ||
> The old package, `tus-node-server`, is considered unstable and will only receive security fixes. | ||
> Make sure to use the new package, currently in beta at `1.0.0-beta.7`. | ||
> Make sure to use the new package. | ||
@@ -39,12 +39,12 @@ ## Contents | ||
```js | ||
const { Server } = require("@tus/server"); | ||
const { FileStore } = require("@tus/file-store"); | ||
const host = "127.0.0.1"; | ||
const port = 1080; | ||
const {Server} = require('@tus/server') | ||
const {FileStore} = require('@tus/file-store') | ||
const host = '127.0.0.1' | ||
const port = 1080 | ||
const server = new Server({ | ||
path: "/files", | ||
datastore: new FileStore({ directory: "./files" }), | ||
}); | ||
server.listen({ host, port }); | ||
path: '/files', | ||
datastore: new FileStore({directory: './files'}), | ||
}) | ||
server.listen({host, port}) | ||
``` | ||
@@ -85,3 +85,3 @@ | ||
If the function returns the (modified) response, the upload will be created. | ||
If an error is thrown, the HTTP request will be aborted and the provided `body` and `status_code` (or their fallbacks) will be sent to the client. | ||
You can `throw` an Object and the HTTP request will be aborted with the provided `body` and `status_code` (or their fallbacks). | ||
@@ -95,6 +95,13 @@ This can be used to implement validation of upload metadata or add headers. | ||
If the function returns the (modified) response, the upload will finish. | ||
If an error is thrown, the HTTP request will be aborted and the provided `body` and `status_code` (or their fallbacks) will be sent to the client. | ||
You can `throw` an Object and the HTTP request will be aborted with the provided `body` and `status_code` (or their fallbacks). | ||
This can be used to implement post-processing validation. | ||
#### `options.onIncomingRequest` | ||
`onIncomingRequest` is a middleware function invoked before all handlers (`(req, res) => Promise<void>`) | ||
This can be used for things like access control. | ||
You can `throw` an Object and the HTTP request will be aborted with the provided `body` and `status_code` (or their fallbacks). | ||
#### `server.handle(req, res)` | ||
@@ -184,17 +191,17 @@ | ||
```js | ||
const { Server } = require("@tus/server"); | ||
const { FileStore } = require("@tus/file-store"); | ||
const express = require("express"); | ||
const {Server} = require('@tus/server') | ||
const {FileStore} = require('@tus/file-store') | ||
const express = require('express') | ||
const host = "127.0.0.1"; | ||
const port = 1080; | ||
const app = express(); | ||
const uploadApp = express(); | ||
const host = '127.0.0.1' | ||
const port = 1080 | ||
const app = express() | ||
const uploadApp = express() | ||
const server = new Server({ | ||
datastore: new FileStore({ directory: "/files" }), | ||
}); | ||
datastore: new FileStore({directory: '/files'}), | ||
}) | ||
uploadApp.all("*", server.handle.bind(server)); | ||
app.use("/uploads", uploadApp); | ||
app.listen(port, host); | ||
uploadApp.all('*', server.handle.bind(server)) | ||
app.use('/uploads', uploadApp) | ||
app.listen(port, host) | ||
``` | ||
@@ -205,28 +212,28 @@ | ||
```js | ||
const http = require("node:http"); | ||
const url = require("node:url"); | ||
const Koa = require("koa"); | ||
const { Server } = require("@tus/server"); | ||
const { FileStore } = require("@tus/file-store"); | ||
const http = require('node:http') | ||
const url = require('node:url') | ||
const Koa = require('koa') | ||
const {Server} = require('@tus/server') | ||
const {FileStore} = require('@tus/file-store') | ||
const app = new Koa(); | ||
const appCallback = app.callback(); | ||
const port = 1080; | ||
const app = new Koa() | ||
const appCallback = app.callback() | ||
const port = 1080 | ||
const tusServer = new Server({ | ||
path: "/files", | ||
datastore: new FileStore({ directory: "/files" }), | ||
}); | ||
path: '/files', | ||
datastore: new FileStore({directory: '/files'}), | ||
}) | ||
const server = http.createServer((req, res) => { | ||
const urlPath = url.parse(req.url).pathname; | ||
const urlPath = url.parse(req.url).pathname | ||
// handle any requests with the `/files/*` pattern | ||
if (/^\/files\/.+/.test(urlPath.toLowerCase())) { | ||
return tusServer.handle(req, res); | ||
return tusServer.handle(req, res) | ||
} | ||
appCallback(req, res); | ||
}); | ||
appCallback(req, res) | ||
}) | ||
server.listen(port); | ||
server.listen(port) | ||
``` | ||
@@ -237,10 +244,10 @@ | ||
```js | ||
const fastify = require("fastify")({ logger: true }); | ||
const { Server } = require("@tus/server"); | ||
const { FileStore } = require("@tus/file-store"); | ||
const fastify = require('fastify')({logger: true}) | ||
const {Server} = require('@tus/server') | ||
const {FileStore} = require('@tus/file-store') | ||
const tusServer = new Server({ | ||
path: "/files", | ||
datastore: new FileStore({ directory: "./files" }), | ||
}); | ||
path: '/files', | ||
datastore: new FileStore({directory: './files'}), | ||
}) | ||
@@ -253,5 +260,5 @@ /** | ||
fastify.addContentTypeParser( | ||
"application/offset+octet-stream", | ||
'application/offset+octet-stream', | ||
(request, payload, done) => done(null) | ||
); | ||
) | ||
@@ -264,14 +271,14 @@ /** | ||
*/ | ||
fastify.all("/files", (req, res) => { | ||
tusServer.handle(req.raw, res.raw); | ||
}); | ||
fastify.all("/files/*", (req, res) => { | ||
tusServer.handle(req.raw, res.raw); | ||
}); | ||
fastify.all('/files', (req, res) => { | ||
tusServer.handle(req.raw, res.raw) | ||
}) | ||
fastify.all('/files/*', (req, res) => { | ||
tusServer.handle(req.raw, res.raw) | ||
}) | ||
fastify.listen(3000, (err) => { | ||
if (err) { | ||
fastify.log.error(err); | ||
process.exit(1); | ||
fastify.log.error(err) | ||
process.exit(1) | ||
} | ||
}); | ||
}) | ||
``` | ||
@@ -286,5 +293,5 @@ | ||
```ts | ||
import type { NextApiRequest, NextApiResponse } from "next"; | ||
import { Server, Upload } from "@tus/server"; | ||
import { FileStore } from "@tus/file-store"; | ||
import type {NextApiRequest, NextApiResponse} from 'next' | ||
import {Server, Upload} from '@tus/server' | ||
import {FileStore} from '@tus/file-store' | ||
@@ -299,3 +306,3 @@ /** | ||
}, | ||
}; | ||
} | ||
@@ -305,8 +312,8 @@ const tusServer = new Server({ | ||
// ie /api/upload | ||
path: "/api/upload", | ||
datastore: new FileStore({ directory: "./files" }), | ||
}); | ||
path: '/api/upload', | ||
datastore: new FileStore({directory: './files'}), | ||
}) | ||
export default function handler(req: NextApiRequest, res: NextApiResponse) { | ||
return tusServer.handle(req, res); | ||
return tusServer.handle(req, res) | ||
} | ||
@@ -318,3 +325,3 @@ ``` | ||
```js | ||
const { Server } = require("@tus/server"); | ||
const {Server} = require('@tus/server') | ||
// ... | ||
@@ -325,13 +332,44 @@ | ||
async onUploadCreate(req, res, upload) { | ||
const { ok, expected, received } = validateMetadata(upload); | ||
const {ok, expected, received} = validateMetadata(upload) | ||
if (!ok) { | ||
const body = `Expected "${expected}" in "Upload-Metadata" but received "${received}"`; | ||
throw { status_code: 500, body }; // if undefined, falls back to 500 with "Internal server error". | ||
const body = `Expected "${expected}" in "Upload-Metadata" but received "${received}"` | ||
throw {status_code: 500, body} // if undefined, falls back to 500 with "Internal server error". | ||
} | ||
// We have to return the (modified) response. | ||
return res; | ||
return res | ||
}, | ||
}) | ||
``` | ||
### Example: access control | ||
Access control is opinionated and can be done in different ways. | ||
This example is psuedo-code for what it could look like with JSON Web Tokens. | ||
```js | ||
const { Server } = require("@tus/server"); | ||
// ... | ||
const server = new Server({ | ||
// .. | ||
async onIncomingRequest(req, res) { | ||
const token = req.headers.authorization; | ||
if (!token) { | ||
throw { status_code: 401, body: 'Unauthorized' }) | ||
} | ||
try { | ||
const decodedToken = await jwt.verify(token, 'your_secret_key') | ||
req.user = decodedToken | ||
} catch (error) { | ||
throw { status_code: 401, body: 'Invalid token' }) | ||
} | ||
if (req.user.role !== 'admin') { | ||
throw { status_code: 403, body: 'Access denied' }) | ||
} | ||
}, | ||
}); | ||
server.listen({ host, port }); | ||
``` | ||
@@ -338,0 +376,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
72683
1448
0
386
Updateddebug@^4.3.4