🎞️

Gif Stitch

postal MessagePort transport demo

Initializing camera…

Waiting for camera…

How postal makes this work

Gif Stitch runs in two JavaScript execution contexts — the main thread and a Web Worker. postal bridges them with its MessagePort transport, turning what would be raw postMessage plumbing into clean pub/sub and RPC.

Transport bridging

The main thread calls connectToWorker(worker) and the worker calls connectToHost(). They perform a SYN/ACK handshake over a MessageChannel, then addTransport() wires the result into postal's outbound hook — from that point on, every publish and request flows across the boundary automatically.

Request / Handle RPC

When you hit Start, the main thread calls channel.request("encode.start", payload) for each GIF slot. The worker's channel.handle("encode.start", ...) does the encoding and returns the finished GIF bytes. The main thread gets a resolved Promise — no manual correlation IDs, no reply listeners, no ceremony.

Pub/sub for progress

While the worker encodes each frame, it calls channel.publish("encode.progress", {"{ index, percent }"}). The main thread subscribed to that topic before sending the request — so the progress bar updates live, mid-RPC, as a fire-and-forget stream alongside the pending Promise. That's two postal patterns running in parallel on the same channel.

Zero-copy transferables

Webcam frames are RGBA data — roughly 1.2 MB per frame at 640×480. Ten frames per GIF means ~12 MB per encoding request. Instead of structured-cloning all that memory, we call markTransferable(payload, buffers) before the request. The transport passes the ArrayBuffers directly to postMessage's transfer list — zero-copy, instant, no serialization cost.