@mieweb/pulsevault 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +45 -0
- package/README.md +386 -0
- package/dist/app.d.ts +114 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +54 -0
- package/dist/app.js.map +1 -0
- package/dist/augment.d.ts +45 -0
- package/dist/augment.d.ts.map +1 -0
- package/dist/augment.js +2 -0
- package/dist/augment.js.map +1 -0
- package/dist/lib/deeplinks.d.ts +32 -0
- package/dist/lib/deeplinks.d.ts.map +1 -0
- package/dist/lib/deeplinks.js +24 -0
- package/dist/lib/deeplinks.js.map +1 -0
- package/dist/lib/errors.d.ts +6 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +4 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/magic.d.ts +50 -0
- package/dist/lib/magic.d.ts.map +1 -0
- package/dist/lib/magic.js +66 -0
- package/dist/lib/magic.js.map +1 -0
- package/dist/lib/pulsevaultTus.d.ts +56 -0
- package/dist/lib/pulsevaultTus.d.ts.map +1 -0
- package/dist/lib/pulsevaultTus.js +146 -0
- package/dist/lib/pulsevaultTus.js.map +1 -0
- package/dist/lib/uuid.d.ts +2 -0
- package/dist/lib/uuid.d.ts.map +1 -0
- package/dist/lib/uuid.js +5 -0
- package/dist/lib/uuid.js.map +1 -0
- package/dist/routes/pulsevault.d.ts +30 -0
- package/dist/routes/pulsevault.d.ts.map +1 -0
- package/dist/routes/pulsevault.js +315 -0
- package/dist/routes/pulsevault.js.map +1 -0
- package/dist/storage/local.d.ts +34 -0
- package/dist/storage/local.d.ts.map +1 -0
- package/dist/storage/local.js +167 -0
- package/dist/storage/local.js.map +1 -0
- package/dist/storage/types.d.ts +72 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +2 -0
- package/dist/storage/types.js.map +1 -0
- package/package.json +75 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type ConfigureDestinationLinkOptions = {
|
|
2
|
+
/** Full origin of the PulseVault server, e.g. `https://example.com` or `http://192.168.1.10:3030`. */
|
|
3
|
+
server: string;
|
|
4
|
+
/** Opaque token forwarded to the server's `authorize` hook. Omit for unauthenticated servers. */
|
|
5
|
+
token?: string;
|
|
6
|
+
/** Human-readable name shown in the Pulse app's destination list. Defaults to the server hostname. */
|
|
7
|
+
name?: string;
|
|
8
|
+
};
|
|
9
|
+
export type UploadLinkOptions = {
|
|
10
|
+
/** Full origin of the PulseVault server. */
|
|
11
|
+
server: string;
|
|
12
|
+
/** Opaque token forwarded to the server's `authorize` hook. Omit for unauthenticated servers. */
|
|
13
|
+
token?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Server-side video UUID. Used as both the app's local draft key and the
|
|
16
|
+
* `videoid` in `Upload-Metadata`, so the app skips `POST /reserve`.
|
|
17
|
+
* Generate with `crypto.randomUUID()`.
|
|
18
|
+
*/
|
|
19
|
+
videoid: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Build a `pulsecam://` deep link that adds this server as a saved upload
|
|
23
|
+
* destination in the Pulse app. No server request is made when the link is
|
|
24
|
+
* opened — the app stores the config locally.
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildConfigureDestinationLink(opts: ConfigureDestinationLinkOptions): string;
|
|
27
|
+
/**
|
|
28
|
+
* Build a `pulsecam://` deep link that opens the Pulse app directly on the
|
|
29
|
+
* upload screen for a specific draft, pointed at this server.
|
|
30
|
+
*/
|
|
31
|
+
export declare function buildUploadLink(opts: UploadLinkOptions): string;
|
|
32
|
+
//# sourceMappingURL=deeplinks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deeplinks.d.ts","sourceRoot":"","sources":["../../src/lib/deeplinks.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,+BAA+B,GAAG;IAC5C,sGAAsG;IACtG,MAAM,EAAE,MAAM,CAAC;IACf,iGAAiG;IACjG,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sGAAsG;IACtG,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,iGAAiG;IACjG,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,+BAA+B,GACpC,MAAM,CAKR;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAI/D"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a `pulsecam://` deep link that adds this server as a saved upload
|
|
3
|
+
* destination in the Pulse app. No server request is made when the link is
|
|
4
|
+
* opened — the app stores the config locally.
|
|
5
|
+
*/
|
|
6
|
+
export function buildConfigureDestinationLink(opts) {
|
|
7
|
+
const params = new URLSearchParams({ mode: "configure_destination", server: opts.server });
|
|
8
|
+
if (opts.token)
|
|
9
|
+
params.set("token", opts.token);
|
|
10
|
+
if (opts.name)
|
|
11
|
+
params.set("name", opts.name);
|
|
12
|
+
return `pulsecam://?${params.toString()}`;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Build a `pulsecam://` deep link that opens the Pulse app directly on the
|
|
16
|
+
* upload screen for a specific draft, pointed at this server.
|
|
17
|
+
*/
|
|
18
|
+
export function buildUploadLink(opts) {
|
|
19
|
+
const params = new URLSearchParams({ mode: "upload", videoid: opts.videoid, server: opts.server });
|
|
20
|
+
if (opts.token)
|
|
21
|
+
params.set("token", opts.token);
|
|
22
|
+
return `pulsecam://?${params.toString()}`;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=deeplinks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deeplinks.js","sourceRoot":"","sources":["../../src/lib/deeplinks.ts"],"names":[],"mappings":"AAsBA;;;;GAIG;AACH,MAAM,UAAU,6BAA6B,CAC3C,IAAqC;IAErC,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,EAAE,IAAI,EAAE,uBAAuB,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IAC3F,IAAI,IAAI,CAAC,KAAK;QAAE,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAChD,IAAI,IAAI,CAAC,IAAI;QAAE,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7C,OAAO,eAAe,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;AAC5C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,IAAuB;IACrD,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACnG,IAAI,IAAI,CAAC,KAAK;QAAE,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAChD,OAAO,eAAe,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;AAC5C,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG;IAChC,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,mBAAmB,CAElE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { FastifyRequest } from "fastify";
|
|
2
|
+
import type { LocalStorage } from "../storage/local.js";
|
|
3
|
+
/**
|
|
4
|
+
* Optional plugin-level hook: after TUS writes the final byte but before the
|
|
5
|
+
* upload is marked ready (or the consumer's `onUploadComplete` runs),
|
|
6
|
+
* validate the payload bytes. Throw to reject — the plugin translates the
|
|
7
|
+
* throw into a 422 response, calls `storage.remove?.(videoid)` to free the
|
|
8
|
+
* disk, and never flips the sidecar to `"ready"`.
|
|
9
|
+
*
|
|
10
|
+
* The `localPath` field is populated for adapters that expose
|
|
11
|
+
* `getLocalPath` (the built-in local adapter does). For adapters whose
|
|
12
|
+
* bytes don't live on local disk (S3, etc.), `localPath` is `null` and the
|
|
13
|
+
* validator has to fetch bytes through whatever API the adapter provides.
|
|
14
|
+
*/
|
|
15
|
+
export type PulseVaultValidatePayload = (request: FastifyRequest, ctx: {
|
|
16
|
+
videoid: string;
|
|
17
|
+
size: number;
|
|
18
|
+
uploadId: string;
|
|
19
|
+
/** Absolute local path to the finalized bytes, or `null` if unavailable. */
|
|
20
|
+
localPath: string | null;
|
|
21
|
+
}) => void | Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Check whether a file's first bytes match the ISO base media file format
|
|
24
|
+
* (`ftyp` box at offset 4), which covers MP4, MOV, M4V, 3GP, and related
|
|
25
|
+
* containers. This is the same check tools like `file(1)` and ffprobe use
|
|
26
|
+
* to identify MP4-family videos.
|
|
27
|
+
*
|
|
28
|
+
* Not a full MP4 parse — just a ~12-byte sniff. Enough to reject uploads
|
|
29
|
+
* that are obviously not video (PDFs, HTML, random bytes) before the
|
|
30
|
+
* server ever serves them back as `video/mp4`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function sniffMp4(filePath: string): Promise<boolean>;
|
|
33
|
+
/**
|
|
34
|
+
* Build a `validatePayload` hook that enforces every uploaded file is a
|
|
35
|
+
* valid MP4-family container. Only works with `LocalStorage` (or any
|
|
36
|
+
* adapter that exposes `getLocalPath`) — pulls the path from the storage
|
|
37
|
+
* and runs `sniffMp4` against it.
|
|
38
|
+
*
|
|
39
|
+
* Usage:
|
|
40
|
+
* ```ts
|
|
41
|
+
* const storage = createLocalStorage({ workspaceDir: "./data" });
|
|
42
|
+
* await app.register(pulseVault, {
|
|
43
|
+
* storage,
|
|
44
|
+
* validatePayload: createMp4Sniffer(storage),
|
|
45
|
+
* // ...
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function createMp4Sniffer(storage: LocalStorage): PulseVaultValidatePayload;
|
|
50
|
+
//# sourceMappingURL=magic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"magic.d.ts","sourceRoot":"","sources":["../../src/lib/magic.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,yBAAyB,GAAG,CACtC,OAAO,EAAE,cAAc,EACvB,GAAG,EAAE;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,KACE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B;;;;;;;;;GASG;AACH,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAwBjE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,YAAY,GACpB,yBAAyB,CAmB3B"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
/**
|
|
3
|
+
* Check whether a file's first bytes match the ISO base media file format
|
|
4
|
+
* (`ftyp` box at offset 4), which covers MP4, MOV, M4V, 3GP, and related
|
|
5
|
+
* containers. This is the same check tools like `file(1)` and ffprobe use
|
|
6
|
+
* to identify MP4-family videos.
|
|
7
|
+
*
|
|
8
|
+
* Not a full MP4 parse — just a ~12-byte sniff. Enough to reject uploads
|
|
9
|
+
* that are obviously not video (PDFs, HTML, random bytes) before the
|
|
10
|
+
* server ever serves them back as `video/mp4`.
|
|
11
|
+
*/
|
|
12
|
+
export async function sniffMp4(filePath) {
|
|
13
|
+
let fd;
|
|
14
|
+
try {
|
|
15
|
+
fd = await fs.open(filePath, "r");
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const buf = Buffer.alloc(12);
|
|
22
|
+
const { bytesRead } = await fd.read(buf, 0, 12, 0);
|
|
23
|
+
if (bytesRead < 12)
|
|
24
|
+
return false;
|
|
25
|
+
// Bytes 4..7 must spell "ftyp" (ASCII 0x66 0x74 0x79 0x70). The first
|
|
26
|
+
// four bytes are the box size and the remaining four after "ftyp" are
|
|
27
|
+
// the brand (e.g. "isom", "mp42", "qt ") — brand validation is left
|
|
28
|
+
// to downstream tools.
|
|
29
|
+
return (buf[4] === 0x66 &&
|
|
30
|
+
buf[5] === 0x74 &&
|
|
31
|
+
buf[6] === 0x79 &&
|
|
32
|
+
buf[7] === 0x70);
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
await fd.close();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build a `validatePayload` hook that enforces every uploaded file is a
|
|
40
|
+
* valid MP4-family container. Only works with `LocalStorage` (or any
|
|
41
|
+
* adapter that exposes `getLocalPath`) — pulls the path from the storage
|
|
42
|
+
* and runs `sniffMp4` against it.
|
|
43
|
+
*
|
|
44
|
+
* Usage:
|
|
45
|
+
* ```ts
|
|
46
|
+
* const storage = createLocalStorage({ workspaceDir: "./data" });
|
|
47
|
+
* await app.register(pulseVault, {
|
|
48
|
+
* storage,
|
|
49
|
+
* validatePayload: createMp4Sniffer(storage),
|
|
50
|
+
* // ...
|
|
51
|
+
* });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export function createMp4Sniffer(storage) {
|
|
55
|
+
return async (_request, { videoid }) => {
|
|
56
|
+
const localPath = await storage.getLocalPath(videoid);
|
|
57
|
+
if (!localPath) {
|
|
58
|
+
throw Object.assign(new Error(`Cannot validate upload ${videoid}: no local path available`), { statusCode: 500 });
|
|
59
|
+
}
|
|
60
|
+
const ok = await sniffMp4(localPath);
|
|
61
|
+
if (!ok) {
|
|
62
|
+
throw Object.assign(new Error("Uploaded bytes are not a valid MP4 (missing ftyp header)"), { statusCode: 422 });
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=magic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"magic.js","sourceRoot":"","sources":["../../src/lib/magic.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AA2BlC;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,QAAgB;IAC7C,IAAI,EAAiB,CAAC;IACtB,IAAI,CAAC;QACH,EAAE,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC7B,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QACnD,IAAI,SAAS,GAAG,EAAE;YAAE,OAAO,KAAK,CAAC;QACjC,sEAAsE;QACtE,sEAAsE;QACtE,qEAAqE;QACrE,uBAAuB;QACvB,OAAO,CACL,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI;YACf,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI;YACf,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI;YACf,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,CAChB,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAqB;IAErB,OAAO,KAAK,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;QACrC,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QACtD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,MAAM,CAAC,MAAM,CACjB,IAAI,KAAK,CACP,0BAA0B,OAAO,2BAA2B,CAC7D,EACD,EAAE,UAAU,EAAE,GAAG,EAAE,CACpB,CAAC;QACJ,CAAC;QACD,MAAM,EAAE,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC,CAAC;QACrC,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,MAAM,MAAM,CAAC,MAAM,CACjB,IAAI,KAAK,CAAC,0DAA0D,CAAC,EACrE,EAAE,UAAU,EAAE,GAAG,EAAE,CACpB,CAAC;QACJ,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Server } from "@tus/server";
|
|
2
|
+
import type { FastifyRequest } from "fastify";
|
|
3
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
|
+
import type { PulseVaultValidatePayload } from "./magic.js";
|
|
5
|
+
import type { PulseVaultStorage } from "../storage/types.js";
|
|
6
|
+
/**
|
|
7
|
+
* Context the plugin stashes on each incoming Fastify request for the lifetime
|
|
8
|
+
* of a TUS call. Shared with the tus hooks via `AsyncLocalStorage` because
|
|
9
|
+
* `@tus/server` v2 hooks receive a web `Request`, not the FastifyRequest.
|
|
10
|
+
*/
|
|
11
|
+
export type PulseVaultTusContext = {
|
|
12
|
+
request: FastifyRequest;
|
|
13
|
+
videoid?: string;
|
|
14
|
+
};
|
|
15
|
+
export declare const pulseVaultTusContext: AsyncLocalStorage<PulseVaultTusContext>;
|
|
16
|
+
export type PulseVaultOnUploadComplete = (request: FastifyRequest, ctx: {
|
|
17
|
+
videoid: string;
|
|
18
|
+
size: number;
|
|
19
|
+
uploadId: string;
|
|
20
|
+
}) => void | Promise<void>;
|
|
21
|
+
export type PulsevaultTusOptions = {
|
|
22
|
+
storage: PulseVaultStorage;
|
|
23
|
+
/** Absolute URL path where TUS is mounted, e.g. `/pulsevault/upload`. */
|
|
24
|
+
tusPath: string;
|
|
25
|
+
/** Max total upload size in bytes. Use `Infinity` for no cap. */
|
|
26
|
+
maxSize: number;
|
|
27
|
+
/**
|
|
28
|
+
* File extensions allowed in the upload `filename` metadata. Must be
|
|
29
|
+
* pre-normalized to lowercase and include the leading dot.
|
|
30
|
+
*/
|
|
31
|
+
allowedExtensions: readonly string[];
|
|
32
|
+
/**
|
|
33
|
+
* Optional payload-validation hook. Runs after TUS writes the final byte
|
|
34
|
+
* but before `markReady` and `onUploadComplete`. Throwing from this hook
|
|
35
|
+
* causes the plugin to `storage.remove?.(videoid)` and return a 4xx
|
|
36
|
+
* (default 422) to the client. Use for magic-byte sniffing, virus
|
|
37
|
+
* scanning, size re-checks — anything that needs the final bytes.
|
|
38
|
+
*/
|
|
39
|
+
validatePayload?: PulseVaultValidatePayload;
|
|
40
|
+
/**
|
|
41
|
+
* Fired once the final byte of an upload has been written *and* any
|
|
42
|
+
* `validatePayload` has passed. Use this to flip consumer state (DB row,
|
|
43
|
+
* queue job, audit log). Throwing here returns a 500 — bytes are on disk
|
|
44
|
+
* and marked ready, but consumer-side work failed.
|
|
45
|
+
*/
|
|
46
|
+
onUploadComplete?: PulseVaultOnUploadComplete;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Shape `@tus/server` recognizes for sending an error response. We tag both
|
|
50
|
+
* `statusCode` and `status_code` so throws originating from either Fastify
|
|
51
|
+
* conventions (camelCase) or the tus convention (snake_case) surface with the
|
|
52
|
+
* right HTTP status.
|
|
53
|
+
*/
|
|
54
|
+
export declare function tusError(status: number, body: string): Error;
|
|
55
|
+
export declare function createPulsevaultTusServer(options: PulsevaultTusOptions): Server;
|
|
56
|
+
//# sourceMappingURL=pulsevaultTus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pulsevaultTus.d.ts","sourceRoot":"","sources":["../../src/lib/pulsevaultTus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAGrD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAE7D;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,cAAc,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,eAAO,MAAM,oBAAoB,yCAAgD,CAAC;AAElF,MAAM,MAAM,0BAA0B,GAAG,CACvC,OAAO,EAAE,cAAc,EACvB,GAAG,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,KACrD,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,iBAAiB,CAAC;IAC3B,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,yBAAyB,CAAC;IAC5C;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,0BAA0B,CAAC;CAC/C,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,CAM5D;AAmBD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,oBAAoB,UAyHtE"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { Server } from "@tus/server";
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { isUuid } from "./uuid.js";
|
|
5
|
+
export const pulseVaultTusContext = new AsyncLocalStorage();
|
|
6
|
+
/**
|
|
7
|
+
* Shape `@tus/server` recognizes for sending an error response. We tag both
|
|
8
|
+
* `statusCode` and `status_code` so throws originating from either Fastify
|
|
9
|
+
* conventions (camelCase) or the tus convention (snake_case) surface with the
|
|
10
|
+
* right HTTP status.
|
|
11
|
+
*/
|
|
12
|
+
export function tusError(status, body) {
|
|
13
|
+
return Object.assign(new Error(body), {
|
|
14
|
+
statusCode: status,
|
|
15
|
+
status_code: status,
|
|
16
|
+
body,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/** Parse the first path segment of a tus upload id as the videoid. */
|
|
20
|
+
function videoidFromUploadId(id) {
|
|
21
|
+
const first = id.split("/", 1)[0];
|
|
22
|
+
return isUuid(first) ? first : undefined;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Extract a numeric HTTP status from a thrown error, honoring both
|
|
26
|
+
* `statusCode` (Fastify) and `status_code` (tus).
|
|
27
|
+
*/
|
|
28
|
+
function statusCodeOf(err, fallback) {
|
|
29
|
+
const e = err;
|
|
30
|
+
if (typeof e?.statusCode === "number")
|
|
31
|
+
return e.statusCode;
|
|
32
|
+
if (typeof e?.status_code === "number")
|
|
33
|
+
return e.status_code;
|
|
34
|
+
return fallback;
|
|
35
|
+
}
|
|
36
|
+
export function createPulsevaultTusServer(options) {
|
|
37
|
+
const { storage, tusPath, maxSize, allowedExtensions, validatePayload, onUploadComplete, } = options;
|
|
38
|
+
return new Server({
|
|
39
|
+
path: tusPath,
|
|
40
|
+
datastore: storage.datastore,
|
|
41
|
+
maxSize,
|
|
42
|
+
namingFunction: async (_req, metadata) => {
|
|
43
|
+
const videoid = metadata?.videoid ?? "";
|
|
44
|
+
const filename = (metadata?.filename ?? "").trim();
|
|
45
|
+
if (!isUuid(videoid)) {
|
|
46
|
+
throw tusError(400, "Upload-Metadata must include a valid `videoid` (UUID).\n");
|
|
47
|
+
}
|
|
48
|
+
const ext = path.extname(filename).toLowerCase();
|
|
49
|
+
if (!ext || !allowedExtensions.includes(ext)) {
|
|
50
|
+
throw tusError(400, `Upload-Metadata \`filename\` must end with one of: ${allowedExtensions.join(", ")}\n`);
|
|
51
|
+
}
|
|
52
|
+
return storage.reserveUpload({ videoid, filename, ext });
|
|
53
|
+
},
|
|
54
|
+
generateUrl(_req, { proto, host, path: tusBasePath, id }) {
|
|
55
|
+
const encoded = Buffer.from(id, "utf8").toString("base64url");
|
|
56
|
+
return `${proto}://${host}${tusBasePath}/${encoded}`;
|
|
57
|
+
},
|
|
58
|
+
getFileIdFromRequest(_req, lastPath) {
|
|
59
|
+
if (!lastPath) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
return Buffer.from(lastPath, "base64url").toString("utf8");
|
|
63
|
+
},
|
|
64
|
+
onUploadFinish: async (_req, upload) => {
|
|
65
|
+
// Completion sequence: validate → markReady → consumer hook. Each step
|
|
66
|
+
// gates the next; failure anywhere short-circuits with a tus error
|
|
67
|
+
// (and cleans up disk state for validation failures specifically).
|
|
68
|
+
const store = pulseVaultTusContext.getStore();
|
|
69
|
+
if (!store) {
|
|
70
|
+
// Should not happen — the Fastify layer always establishes a store
|
|
71
|
+
// before calling into tus. Bail quietly rather than crash.
|
|
72
|
+
return {};
|
|
73
|
+
}
|
|
74
|
+
const videoid = videoidFromUploadId(upload.id);
|
|
75
|
+
if (!videoid) {
|
|
76
|
+
return {};
|
|
77
|
+
}
|
|
78
|
+
const size = upload.size ?? 0;
|
|
79
|
+
const uploadId = upload.id;
|
|
80
|
+
// 1. Validate payload (magic bytes, virus scan, etc.). If this throws
|
|
81
|
+
// we wipe the bytes from storage — the client gets a 4xx, the
|
|
82
|
+
// sidecar is gone, and they can safely retry with a corrected file.
|
|
83
|
+
if (validatePayload) {
|
|
84
|
+
try {
|
|
85
|
+
await validatePayload(store.request, {
|
|
86
|
+
videoid,
|
|
87
|
+
size,
|
|
88
|
+
uploadId,
|
|
89
|
+
localPath: await resolveLocalPath(storage, videoid),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
const status = statusCodeOf(err, 422);
|
|
94
|
+
const message = err instanceof Error ? err.message : "Payload validation failed";
|
|
95
|
+
try {
|
|
96
|
+
await storage.remove?.(videoid);
|
|
97
|
+
}
|
|
98
|
+
catch (rmErr) {
|
|
99
|
+
store.request.log.error({ err: rmErr, videoid }, "pulsevault failed to remove rejected upload");
|
|
100
|
+
}
|
|
101
|
+
throw tusError(status, `${message}\n`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// 2. Flip the sidecar to "ready" so `resolve` will serve the bytes.
|
|
105
|
+
// Done *before* the consumer hook so a downstream service that
|
|
106
|
+
// reacts to `onUploadComplete` can immediately GET the video.
|
|
107
|
+
try {
|
|
108
|
+
await storage.markReady?.(videoid);
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
const message = err instanceof Error ? err.message : "markReady failed";
|
|
112
|
+
throw tusError(500, `${message}\n`);
|
|
113
|
+
}
|
|
114
|
+
// 3. Consumer hook — business logic (DB writes, queue jobs).
|
|
115
|
+
if (onUploadComplete) {
|
|
116
|
+
try {
|
|
117
|
+
await onUploadComplete(store.request, { videoid, size, uploadId });
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
// Propagate as a tus error so the client sees a non-2xx and can
|
|
121
|
+
// distinguish "bytes stored but completion hook failed" from
|
|
122
|
+
// success. The video is marked ready at this point — consumers
|
|
123
|
+
// who want "all-or-nothing" should `storage.remove` before
|
|
124
|
+
// throwing.
|
|
125
|
+
const message = err instanceof Error ? err.message : "onUploadComplete failed";
|
|
126
|
+
throw tusError(500, `${message}\n`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return {};
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* If the adapter exposes `getLocalPath` (the built-in local adapter does),
|
|
135
|
+
* resolve the videoid to an absolute disk path for `validatePayload`. For
|
|
136
|
+
* other adapters, returns `null` and the validator is expected to fetch
|
|
137
|
+
* bytes through whatever API it knows about.
|
|
138
|
+
*/
|
|
139
|
+
async function resolveLocalPath(storage, videoid) {
|
|
140
|
+
const candidate = storage.getLocalPath;
|
|
141
|
+
if (typeof candidate !== "function")
|
|
142
|
+
return null;
|
|
143
|
+
const result = await candidate(videoid);
|
|
144
|
+
return typeof result === "string" ? result : null;
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=pulsevaultTus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pulsevaultTus.js","sourceRoot":"","sources":["../../src/lib/pulsevaultTus.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uuid.d.ts","sourceRoot":"","sources":["../../src/lib/uuid.ts"],"names":[],"mappings":"AAGA,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEtD"}
|
package/dist/lib/uuid.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uuid.js","sourceRoot":"","sources":["../../src/lib/uuid.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,GACd,iEAAiE,CAAC;AAEpE,MAAM,UAAU,MAAM,CAAC,KAAc;IACnC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC7D,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { FastifyPluginAsync, FastifyPluginOptions, FastifyRequest } from "fastify";
|
|
2
|
+
import type { PulseVaultCacheOptions } from "../app.js";
|
|
3
|
+
import { type PulseVaultOnUploadComplete } from "../lib/pulsevaultTus.js";
|
|
4
|
+
import type { PulseVaultValidatePayload } from "../lib/magic.js";
|
|
5
|
+
import type { PulseVaultStorage } from "../storage/types.js";
|
|
6
|
+
declare module "fastify" {
|
|
7
|
+
interface FastifyRequest {
|
|
8
|
+
pulseVault?: {
|
|
9
|
+
videoid: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export type PulseVaultAuthorizePhase = "create" | "patch" | "resolve" | "delete";
|
|
14
|
+
export type PulseVaultAuthorizeContext = {
|
|
15
|
+
phase: PulseVaultAuthorizePhase;
|
|
16
|
+
videoid: string;
|
|
17
|
+
};
|
|
18
|
+
export type PulseVaultAuthorize = (request: FastifyRequest, ctx: PulseVaultAuthorizeContext) => void | Promise<void>;
|
|
19
|
+
export type PulseVaultRoutesOptions = {
|
|
20
|
+
storage: PulseVaultStorage;
|
|
21
|
+
maxUploadSize: number;
|
|
22
|
+
allowedExtensions: readonly string[];
|
|
23
|
+
cache?: PulseVaultCacheOptions;
|
|
24
|
+
authorize?: PulseVaultAuthorize;
|
|
25
|
+
validatePayload?: PulseVaultValidatePayload;
|
|
26
|
+
onUploadComplete?: PulseVaultOnUploadComplete;
|
|
27
|
+
} & FastifyPluginOptions;
|
|
28
|
+
declare const pulseVaultRoutes: FastifyPluginAsync<PulseVaultRoutesOptions>;
|
|
29
|
+
export default pulseVaultRoutes;
|
|
30
|
+
//# sourceMappingURL=pulsevault.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pulsevault.d.ts","sourceRoot":"","sources":["../../src/routes/pulsevault.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EAEpB,cAAc,EAEf,MAAM,SAAS,CAAC;AAEjB,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACxD,OAAO,EAGL,KAAK,0BAA0B,EAChC,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAGjE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAM7D,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,cAAc;QACtB,UAAU,CAAC,EAAE;YAAE,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC;KAClC;CACF;AAED,MAAM,MAAM,wBAAwB,GAChC,QAAQ,GACR,OAAO,GACP,SAAS,GACT,QAAQ,CAAC;AAEb,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,EAAE,wBAAwB,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,CAChC,OAAO,EAAE,cAAc,EACvB,GAAG,EAAE,0BAA0B,KAC5B,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B,MAAM,MAAM,uBAAuB,GAAG;IACpC,OAAO,EAAE,iBAAiB,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,KAAK,CAAC,EAAE,sBAAsB,CAAC;IAC/B,SAAS,CAAC,EAAE,mBAAmB,CAAC;IAChC,eAAe,CAAC,EAAE,yBAAyB,CAAC;IAC5C,gBAAgB,CAAC,EAAE,0BAA0B,CAAC;CAC/C,GAAG,oBAAoB,CAAC;AA8JzB,QAAA,MAAM,gBAAgB,EAAE,kBAAkB,CAAC,uBAAuB,CA4NjE,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
|