Whiteboard
Unlike the other packages on this site, this one has exactly one external
dependency, and it’s solved by injection rather than baked in. Deciding
whether a submitted drawing’s imageUrl is trustworthy means knowing your
own storage’s bucket, domain, or signing scheme — something this SDK can’t
know in advance. So createWhiteboardState takes an isOwnImageUrl
predicate and builds the rest of the API (reduce, timerDelay, onTimer,
restore) around it. There’s no “accept everything” default: a host that
forgets to pass a predicate would otherwise silently let arbitrary external
URLs into shared state.
The demo below wires up that predicate itself, using a fake storage prefix
(https://cdn.example.com/uploads/) that only this page knows about — the
package never sees it as anything but “the predicate said yes.”
What to try
Section titled “What to try”- In draw phase, submit a drawing with own URL — it’s accepted,
because
isOwnImageUrlrecognizes the prefix. Then submit with other URL —reducereturnsnull, dropping the action entirely rather than accepting an arbitrary external image. - In free mode, press add stroke and add shape a few times, then press clear with strokes and shapes on the board. Both counts go to zero together — a “clear the board” that only cleared strokes would leave shapes behind, which would violate what “clear” means.
- Press erase with no strokes on the board. It returns
null— there’s nothing to remove, so it’s a no-op rather than a broadcast-worthy change. - Switch between free and relay. Each switch also calls
reducewithset-mode, and it’s alwaysnull: tab switching is a client-side display concern now, and acceptingset-modeserver-side would let anyone silently discard an in-progress relay game by what looks like a harmless toggle. - Join at least two players and press start-game. Watch the phase
(
prompt→draw→guess→ …) and the countdown advance on their own oncetimerDelayelapses —stateitself only changes when a player acts or a phase’sonTimerfires.
The code behind it
Section titled “The code behind it”The demo imports the published package and nothing else:
import { createWhiteboardState } from '@insession/extension-whiteboard';
const whiteboard = createWhiteboardState({ isOwnImageUrl: (url) => url.startsWith('https://cdn.example.com/uploads/'),});
let state = whiteboard.defaultState();
// A client actionconst next = whiteboard.reduce(state, 'submit-drawing', { by: 'Alice', imageUrl: 'https://cdn.example.com/uploads/1.png',});if (next) state = next;
// Relay phase transitions are driven by your own timerconst delay = whiteboard.timerDelay(state); // ms until the phase ends, or nullif (delay !== null) { setTimeout(() => { state = whiteboard.onTimer(state) ?? state; }, delay);}reduce is a pure function — no I/O, no timers started inside it, and the
only “impure” thing anywhere in the package is Date.now(). Scheduling is
yours, which is what lets the same state machine run on a server, in a test,
or in this page.
See the package reference for the full
action list and the persistence helpers (restore).