Skip to content

Media (Out-of-Band Bytes)

Images/videos never cross the wire protocol (the frame cap is 16 MiB). vendor.media demonstrates the intended pattern:

  1. The host picks/captures a file natively and returns its path to the worklet.
  2. The worklet mounts it on the loopback server under an unguessable id and returns only the URL. The native path never reaches the page.
  3. The web layer loads the URL directly — same-origin, cookie-authenticated.

One serving implementation for every runtime.

import type { PluginBus } from '@ekrooh/bare/core';
import { mediaEvents } from '@ekrooh/bare/plugins/media/events';
declare const bus: PluginBus; // createPluginBus(messenger), see Transports
declare const img: HTMLImageElement;
const [err, result] = await bus.invoke(mediaEvents.media.pick('image'));
if (!err) {
// Check the URL is a same-origin path before using it (the reference app's
// `safeMediaUrl` in examples/web/media-url.ts does this).
const url = new URL(result.url, location.href);
if (url.origin === location.origin) img.src = url.href;
}

The builders pass a 5-minute invoke timeout, because the native picker is user-mediated.

createMediaPlugin delegates the pick/capture to the host via HOST_INVOKE_REQUEST (with a host deadline of 4 min 55 s, just under the page’s timeout), then mounts the returned path under a CSPRNG id:

import crypto from 'bare-crypto';
const id = `${kind}-${crypto.randomBytes(16).toString('base64url')}`;
deps.staticServer.mount(`/media/${id}`, path);
const url = await deps.staticServer.url(`/media/${id}`);
return ok({ url }); // never the native path

Never derive a mount id from Date.now() or a counter: any holder of the session could then guess other mounted files. At most 4 media mounts stay live; the oldest is unmounted first.

The Android reference host wires a real native picker/camera; the iOS host uses PHPickerViewController + UIImagePickerController. The host returns { path } to the worklet — bytes stay in the sandbox, served by the worklet. A host error with a code outside the canonical ErrorCode set reaches the page as HOST_ERROR, and its message is capped at 500 characters.

media.pick and media.capture, each taking { kind?: 'image' | 'video' } (any other kind is refused with PLUGIN_ERROR before it reaches the host) and returning { url }.