poka-web HTTP API
poka-web is the local web server in web/ (cd web && cargo run). It is a small
standard-library HTTP/1.1 server that serves the web app and the same preview
engine the desktop host uses. The deployed site at poka.sh is a static
Cloudflare Worker with no server compute, so only the local server has
POST /api/preview.
The server listens on 127.0.0.1:21220 unless POKA_WEB_ADDR says otherwise (see
Environment variables and file locations). On start
it prints the bound address and how many extensions it loaded.
Limits
| Limit | Value |
|---|---|
Upload body (POST /api/preview) |
256 MB; larger returns 413 |
| Request line or one header | 16 KiB; larger returns 431 |
| Number of headers | 100; more returns 431 |
| Idle read or write on a connection | 60 seconds |
| Result cache | the last 64 results, in memory, keyed by file name and content hash |
An unknown path returns 404 with the body not found.
Preview and catalogue
| Method | Path | Returns |
|---|---|---|
POST |
/api/preview?name=<filename> |
The preview of the bytes in the request body, as a PreviewResult (JSON). The main endpoint |
POST |
/api/preview?src=<path> |
The preview of a file under the examples directory, in place. Local server only |
GET |
/api/formats |
Every routed extension and file name, with its viewer and module id |
GET |
/api/formats.json |
The full route table the engine uses: hashes, sizes and limits |
GET |
/wasm/{id} |
The module's .wasm (application/wasm) |
GET |
/wasm/{id}/{file} |
A named file from that module's directory: its .wasm or its plugin.json |
GET |
/examples/{name} |
A file from the examples directory, for "try a sample" links |
POST /api/preview
The body is the raw file bytes. The name query parameter (percent-encoded,
default file.bin) decides which parser the file routes to, so it must include the
extension. Identical name and bytes return the cached result without running a module.
src=<path> previews a file where it lies instead of from a temporary copy, so the
module's /input directory is that file's real folder. Formats whose scene sits
beside the file (a project file with an asset/ folder) need this. The path must stay
inside the examples directory; every refusal gives the same message so a probe cannot
tell "outside" from "missing". Results from src= are not cached.
The response is always 200 with JSON. Its type field is the variant of
PreviewResult, one of:
type |
Meaning |
|---|---|
Image |
Decoded raster bytes, with format, width and height |
Svg |
Inline SVG markup in content |
Pdf |
PDF bytes, drawn by the frontend |
Html |
Viewer HTML in content, with optional facts and integrity notes |
Text |
Plain text in content |
Model |
A mesh to load: path, format, optional mtl |
Dwg |
Structured CAD entities with bounds |
Scene3D |
Meshes, materials, textures, cameras and lights with bounds |
MultiView |
Several tabs, each a titled PreviewResult |
NoExtension |
No module handles the file: file_ext, extension_id, size_mb, supported |
AudioTranscode, VideoTranscode |
A transcoded temporary file to play, with a hint |
NoAudioPlayback, NoVideoPlayback |
The media cannot be played, with a hint |
ExtensionError |
The module failed: code, message, recoverable. A recoverable error is the parser's own warning rather than a verdict on the file: both frontends render it as a warning card (file-warning), not a failure |
ServerError |
The host failed: message and kind |
The type is defined once in core/poka-core/src/preview_types.rs, the shared engine
crate both the desktop app and the local server use, so a field cannot be added to one
host only.
In the browser engine a ServerError also says why. Its kind is load (a module
would not download or is not a valid module), run (it ran and failed), output (it
answered with something unreadable), timeout, too-large, routes (the format list
would not load) or engine. Where they apply it adds stage (parser or viewer),
extension (the module id), status (the HTTP status), offline, damaged, sizeMB
and capMB, and limitMs. The server path sends only message.
GET /api/formats
A JSON array, sorted with extension rows first and then file-name rows:
[{"ext": "dwg", "viewer": "", "id": "poka-dwg"}, {"filename": "makefile", "viewer": "poka-code-viewer", "id": "poka-code"}]
extis the extension without the dot;filenameis an exact file name (a row has one or the other)vieweris the composed viewer id, empty for a monolithic moduleidis the module id; use it with/wasm/{id}
GET /api/formats.json
The route table both hosts install from. Each element describes one route:
| Field | Meaning |
|---|---|
id |
The parser's module id |
ext or filename |
What the route matches: an extension, or an exact lowercased file name |
wasm, manifest |
Paths of the parser's .wasm and plugin.json under /wasm/ |
sha256, bytes |
Hash and size of the parser wasm, checked before it runs or is installed |
memory_mb, max_time_ms |
The parser's declared limits |
viewer |
The composed viewer id, empty for a monolithic module |
viewer_wasm, viewer_manifest, viewer_sha256, viewer_bytes |
The same facts for the viewer; present only when there is one |
A route's wasm may point into another module's directory when several routes share
one binary. Every route is listed in the generated route table reference.
GET /wasm/{id} and GET /wasm/{id}/{file}
Serve a module binary or manifest. {file} is one path segment. A shared binary is
found in the directory of the module that owns it, so /wasm/poka-m3u8/poka-code.wasm
is served from poka-code/. A manifest is served only from its own module's directory.
Unknown ids, unsafe names (anything with .. or a separator) and unregistered files
return 404.
Pages and assets
The server also serves the web app itself, so cargo run matches the deployed site:
| Path | Content |
|---|---|
/ |
The app |
/app.js, /home.js, /version.js |
The page scripts |
/version.json |
Which build is running |
/preview-frame |
The sandboxed document that hosts viewer HTML |
/embed |
The app with only the viewer, which the format pages under /docs frame (#503). The one page another page may frame, and only one of this origin. A built bundle has it; the local server does not |
/docs, /50-50, /50-50.js, /50-50.json |
The help page and the 50+50 formats page |
/engine.js, /engine-worker.js, /engine-core.js, /proto.js, /wasi.js, /routing.js, /content-sniff.js, /content-signatures.json |
The client-side preview engine |
/js/* |
The desktop frontend scripts, served in place from desktop/src/js/ |
/favicon.ico |
The favicon |
/50-50.json has a second reader: the start screen's surprise me button
(web/src/home.js) fetches it when the button is reached, keeps only the samples
marked cleared and opens one of them through /examples/<file> like a pasted
link. It adds no route and nothing to the bundle.
The account page and its API passthrough exist on the local server too; they are not part of the preview API and are not described here.
The browser engine
The browser does not need POST /api/preview. It fetches the route table, downloads the
parser and viewer wasm by id (with the first 16 hex digits of the route's SHA-256 in the
URL as a cache key) and runs them in a Web Worker. The file never leaves the device.
Appending ?server=1 to the app address uses POST /api/preview instead, so the two
engines can be compared; node scripts/test-parity.mjs automates that comparison over
the whole corpus.
URL preview
Pasting a public file URL into the app, or opening https://poka.sh/?url=<address>,
fetches the file in the browser, so the server never fetches it. An address opened as
?url= in the page address is sent to the server with the page request. The address must end
in a recognisable extension, the site must allow cross-origin requests, the file is
capped at 256 MB, and only public addresses work. If the site
blocks cross-origin requests, download the file and drop it on the page.