Import it from @grafloria/engine.
The presence store for one peer: our own state, and everyone else's.
Deliberately transport-free. SyncAdapter wires it to a channel; a test wires it to
nothing and drives it directly. It never sees an Op, a Replica or an OpLog, and that
is not an accident — it is the containment.
WHY lastSeen IS A WALL CLOCK WHEN THE LOG WENT TO SUCH LENGTHS TO AVOID ONE
Because the question is different. The log asks "did A happen before B?", which a wall
clock cannot answer across machines (they disagree, and they run backwards). Expiry
asks "has it been 15 seconds since I last heard from Bob?" — measured entirely on MY
clock, about MY receipts, comparing my own now() to my own earlier now(). Bob's
clock is never consulted, so its wrongness cannot infect anything. Using a Lamport
clock for a TIMEOUT would be the actual mistake: it has no relationship to seconds.
tsclass Awareness
Methods
constructor(private readonly options: AwarenessOptions)get actor(): ActorIdgetLocalState(): AwarenessState— Our own published state, and the sequence a peer will LWW it by.get localSeq(): numbersetLocalState(patch: Partial<AwarenessState>): boolean— Merge a patch into our own state and bump the sequence.
Returns false when nothing actually changed — a mouse that has not moved must not
burn a sequence number, wake the throttle, or send a message. (A mousemove fires
on sub-pixel jitter and on scroll; without this guard an idle user with a resting
hand is a 60Hz broadcaster.)
applyRemote(actor: ActorId, state: AwarenessState | null, seq: number): AwarenessChange | null— Take a peer's published state.
seq is the whole reason a reordered transport does not make a cursor jump
backwards: a sample older than the one we already hold is REFUSED, exactly as the LWW
gate refuses a superseded op. Same idea, one scope down, and no shared machinery
because the lifecycles have nothing in common.
remove(actor: ActorId): AwarenessChange | null— An explicitbye, or a transport that told us the peer is gone.prune(): AwarenessChange | null— Drop peers we have not heard from inside the timeout.
THE GUARANTEE, as opposed to bye, which is merely the optimisation. A crashed tab,
a closed laptop, a killed process and a severed cable all send no bye whatsoever,
and every one of them must eventually stop showing a cursor.
getPeers(): PeerPresence[]— Everyone but us.getPeer(actor: ActorId): PeerPresence | undefinedget peerCount(): numberclearPeers(): AwarenessChange | null— Everyone is gone (we disconnected — we can no longer vouch for anyone).onChange(listener: (change: AwarenessChange) => void): () => void
Was this page helpful?