@krizaka/video
Video behind one port: server-signed resumable uploads, playback tokens and signed HLS links, signed webhook statuses.
Install
npm install @krizaka/videoAlso published as subpath exports: @krizaka/video/testkit.
Video behind one port: server-signed resumable uploads, playback tokens and signed links, webhook statuses. Bunny Stream is the first adapter (streamVideo); the port is open to others. The client never holds a library key. No vendor SDK, no environment variable, no wall clock.
- Uploads.
createVideothenuploadSession(videoId, ttl): a TUS endpoint and the four headers (AuthorizationSignature,AuthorizationExpire,VideoId,LibraryId), signed on the server for this video only. - Playback.
viewToken(videoId, ttl, "embed" | "mobile")(the embedded web player's token, or the mobile SDKs'; the lifetime is passed in, 1 to 5 minutes is the norm) andplaybackUrls(videoId, ttl): the HLS playlist signed for the whole video directory (the token sits in the path, so every rendition and segment is covered) and a signed poster. - Webhooks.
parseWebhook(rawBody, headers)verifies the HMAC-SHA256 of the raw body with the library's read-only key, in constant time, and answers{ videoId, status }(ready,playable,failed,upload_failed…); a status it does not track answersnull. - Proofs. The formulas come from Bunny's documentation (TUS, embedded view token, mobile SDK token, webhooks) and the CDN forms are the ones run in production. The documentation publishes no expected outputs, so the tests compute them independently from the documented formulas and never use an invented vector.
- Not here yet. Captions, collections and listing are follow-ups.
import { streamVideo } from "@krizaka/video";
const video = streamVideo({ libraryId, libraryKey, tokenKey, readOnlyKey, cdnHostname, fetch, clock });
const { videoId } = await video.createVideo({ title: "Moment" });
const session = video.uploadSession(videoId, 3600); // hand it to the client's TUS uploader
const { token, expires } = video.viewToken(videoId, 120, "embed");videoProviderContract (@krizaka/video/testkit) is the contract every adapter runs.
Apache-2.0.
Exports
The 19 names the package exports, read from its types.
| Name | Kind | What it is |
|---|---|---|
Clock | type | |
embedToken | function | The token of the embedded web player (Stream docs, « Embedded view token authentication »): hex SHA-256 of tokenKey + videoId + expires. |
FetchLike | type | The part of fetch the adapters use: inject the global one (fetch), or a fake in a test. |
mobileViewToken | function | The token of the mobile SDKs (Stream docs, « Token authentication with Mobile SDKs »): HMAC-SHA256, keyed by the library API key, over `libraryKey + videoId… |
PlaybackUrls | type | |
signedDirectoryUrl | function | CDN token authentication for a DIRECTORY (a video's playlist, renditions and segments): SHA-256 of key + path + expires + [ip] + "token_path=" + path, URL-… |
signedFileUrl | function | CDN token authentication for ONE FILE (a poster, a preview): SHA-256 of key + path + expires, URL-safe base64, sent as ?token=…&expires=…. |
streamVideo | function | Bunny Stream, behind the VideoProvider port. |
StreamVideoOptions | type | |
tusSignature | function | The signature of a TUS upload (Stream docs, « TUS resumable uploads »): hex SHA-256 of libraryId + libraryKey + expires + videoId, concatenated with no sep… |
UploadSession | type | Where a client uploads, resumably (TUS), and the headers it must send: short-lived, for this video only. |
VIDEO_CODE | constant | |
VIDEO_STATUS | constant | |
VideoCode | type | |
VideoError | class | |
VideoEvent | type | What a provider's webhook says happened to one video. |
VideoProvider | type | One video provider. |
VideoStatus | type | |
ViewToken | type | A token the player is given for one video. |
@krizaka/video/testkit
| Name | Kind | What it is |
|---|---|---|
videoProviderContract | function | What every VideoProvider must do: the same behaviour behind the same port. |
Package
| Version on npm | 0.2.2 (npm) |
| Licence | Apache-2.0 |
| Node.js | >=22 |
| Dependencies | @krizaka/db workspace:^ |
| Peer dependencies | vitest >=4 |
| Source | video |
Changelog
0.2.2
Patch changes
- The READMEs, comments and published declarations no longer name the products that left the organisation (krizaka/.github#413, rule
no-departed-published). The sources were cleaned in #262 with an empty changeset, so the published tarballs still carried the old text; this release replaces them. No behaviour change.
0.2.1
Patch changes
- Updated dependencies [0d19b64]
- @krizaka/[email protected]
0.2.0
Minor changes
- New packages (#44).
@krizaka/storage: theObjectStoreport (put,get,head,delete,list,signedUrl,publicUrl),remoteStore(HTTP zone, token-signed links),bucketStore(any S3-compatible service, Signature V4 checked against AWS's vectors),folderStore,memoryObjectStore,createStorage(size limit passed in, declared layout, magic-number check, content-hashed names),checkEndpoint,@krizaka/storage/settings(storage.providerlocal / http / s3, limits and coordinates;storeFromSettings) andobjectStoreContract.@krizaka/video: theVideoProviderport,streamVideo(Bunny Stream: create, delete, server-signed TUS sessions, embed and mobile view tokens, signed HLS and poster links, signed webhooks), the signing helpers andvideoProviderContract.
@krizaka/coupons
Discount and credit coupons the admin manages: reserved under a lock, confirmed once, released when unpaid, refunded; partners for attribution.
@krizaka/storage
Files behind one port: content-hashed names, signed and public links; an HTTP zone, any S3-compatible bucket or a folder, chosen by the admin; bytes checked by magic number.