@remotedraw/receiver-web
v0.5.0
Published
Framework-independent RemoteDraw receiver and custom element.
Readme
@remotedraw/receiver-web
Framework-independent RemoteDraw receiver and custom element. Real-device acceptance is still pending. A phone browser or the existing iOS sender can test it; Android hardware is not required for those paths. Android sender compatibility remains unverified until separately tested.
Install and mount
npm install @remotedraw/receiver-web # npm
pnpm add @remotedraw/receiver-web # pnpm
yarn add @remotedraw/receiver-web # Yarn
bun add @remotedraw/receiver-web # bunTo use an unreleased build from a RemoteDraw checkout instead, see Working from a checkout.
import { mountReceiver, createHttpReceiverClient } from '@remotedraw/receiver-web';
const response = await fetch('/api/drawing-session', { method: 'POST' });
if (!response.ok) throw new Error('Could not create session');
// Your backend creates the session; never send an rd_sk_ API key to a browser.
const created = await response.json();
const session = {
...created.session,
receiverToken: created.receiverToken,
joinUrl: created.joinUrl,
joinUrls: created.joinUrls,
joinTokenExpiresAt: created.joinTokenExpiresAt,
};
const view = mountReceiver(document.querySelector<HTMLElement>('#board')!, {
receiver: createHttpReceiverClient('https://api.remotedraw.com'),
session,
// Board aspect ratio is derived from the session's coordinate space.
});
// On route exit or window teardown:
view.dispose();The view owns its store, starts it automatically, and stops it on disposal.
Pass store to share an existing createReceiverStore instead; then the host
owns its start/stop lifecycle. Use view.store.setSession(nextSession) to switch
boards and view.store.clear(), .undo(), .refreshJoinToken() for controls.
The shared client handles draft/commit handoff, ordering, stale draft cleanup,
presence aging, polling, push-source fallback and reconnect backfill.
Use the same source option as the headless client for realtime updates. HTTP
polling alone is functional but is not low-latency streaming. Supply scoped
receiver credentials returned by your backend. The pairing panel includes a QR,
link, expiry handling, and manual code refresh. It never puts receiver credentials
in attributes or events. onStateChange does receive privileged store state;
keep it within trusted application code.
Custom element
import { defineRemoteDrawReceiver } from '@remotedraw/receiver-web';
defineRemoteDrawReceiver();
const board = document.createElement('remote-draw-receiver');
board.options = { receiver, session }; // JavaScript property, not HTML attributes
container.append(board);
// Removing the element disposes its view. Reconnecting mounts a fresh view.The module can be imported during SSR; register/mount only in the browser.
Set options again to reconfigure. Parts: receiver, pairing, canvas.
Use remote-draw-receiver::part(canvas) to style the canvas. showPairing: false
hides the built-in panel for a host-owned pairing flow. The custom element must
be registered in the same window/realm as its HTMLElement base.
Framework recipes
Vue: mount in onMounted(() => { view = mountReceiver(board.value, options) })
and dispose in onBeforeUnmount(() => view?.dispose()). Use a <div ref="board"/>.
Angular: use @ViewChild('board') board: ElementRef, mount in ngAfterViewInit
and dispose in ngOnDestroy; template <div #board></div>. No Angular schema
change is needed when using the imperative mount.
Svelte: onMount(() => { const view = mountReceiver(board, options); return () =>
view.dispose(); }) with <div bind:this={board}></div>. These recipes use the
framework's normal lifecycle; the DOM implementation is shared. Vue, Angular,
and Svelte compiler/runtime integration has not been tested yet.
Rendering boundaries
Basic normalized freehand/point strokes, line/arrow/rectangle/ellipse outlines, plain text, pressure/tilt stroke geometry and simple layered ink are implemented using the shared client geometry. The SVG follows session target.coordinateSpace width/height (square fallback), or an explicit aspectRatio override. It fits that coordinate aspect ratio without stretching. Resize the host freely; server coordinate aspect changes update automatically unless an explicit override is supplied.
This preview does not provide parity with the React receiver: images,
annotation/document anchors, projection cameras/phone frames, textured substrate
masks, wet-edge effects, accumulated highlighter film, physical material
simulation, or WebRTC visual-context publication/consumption are not implemented.
Unknown types and unsupported features are listed in the SVG's
data-unsupported attribute, visible limitation notice, and onUnsupported callback; unsupported elements are omitted, while advanced
materials use basic vector fallback. Text is plain SVG text, with fixed font size
and no multiline layout. Large-board incremental rendering is not optimized.
Do not accept visual fidelity or streaming parity based on this preview.
Desktop recipes
Electron: install this package in the renderer app, then use the mount example.
Keep contextIsolation: true and nodeIntegration: false; session creation and
any secret key belong to a trusted backend, not renderer code or a distributed
binary. Dispose the receiver on SPA teardown. No Electron-specific transport is
needed. Test background/resume, offline/reconnect and window resize.
Tauri: use the same frontend mount example. Allow the API origin in your CSP
connect-src and data: QR images in img-src; use your backend for scoped
sessions. Do not embed developer keys in Rust or frontend bundles. Test window
close/reopen and suspend/resume. These are integration recipes, not independently
validated Electron/Tauri starter applications.
Local fixture demo and release gate
From repository root:
/Users/wout/.local/bin/dev-external bun build packages/web/examples/demo.ts --outdir packages/web/examples/dist --target browser
/Users/wout/.local/bin/dev-external bun packages/web/examples/serve.tsOpen http://127.0.0.1:4319. This demo uses a local store fixture with no network
credentials. It exercises draft/commit replacement, clear and session switching.
For live acceptance, replace the fixture store with the scoped-session mount
example above. Check pairing/QR from iPhone, draft ink, commit, undo, clear,
reconnect after airplane mode, join-code refresh/expiry, two senders, board
switching and resizing. Record browser, OS and device. Run bun run --cwd packages/web test and
bun run --cwd packages/web build through dev-external before packing.
