Files
Toju/toju-app/src/app/infrastructure/realtime/peer-role.rules.ts
T
myxelium a83f5aa750 fix(realtime): survive signal outages and per-server identities
Peer recovery burned its whole retry budget while signaling was down, then
dropped the tracker with no re-arm, so a peer stayed dead until an unrelated
roster event healed it. Recovery now waits for a usable transport before
spending an attempt.

Initiator election also compared a home actor id against foreign roster ids,
which is not antisymmetric across signal servers - both sides offered, or
neither did. Election moves into `peer-role.rules` and compares ids only
within one signal server's identity space.
2026-08-14 03:19:29 +02:00

38 lines
1.3 KiB
TypeScript

/**
* Deterministic WebRTC role election.
*
* Both ids must come from the **same signal server's identity space**. One human has a
* separate account - and therefore a separate actor id - on every signal server they are
* provisioned on, so comparing a home id against a foreign actor id is not antisymmetric:
* each side compares its own home id against the other's foreign id, and both can elect
* themselves initiator (glare) or neither can (no offer ever sent).
*/
export function shouldInitiatePeerConnection(
localActorId: string | null | undefined,
remoteActorId: string | null | undefined
): boolean {
const local = localActorId?.trim();
const remote = remoteActorId?.trim();
if (!local || !remote)
return false;
if (local === remote)
return false;
return local < remote;
}
/**
* Perfect-negotiation politeness, derived from the same election so the two can never
* disagree: the elected initiator is the impolite side and keeps its offer, the other
* side rolls back. Falls back to polite when the local actor id is unknown, so an
* unidentified client yields instead of deadlocking the negotiation.
*/
export function isPoliteOnOfferCollision(
localActorId: string | null | undefined,
remoteActorId: string | null | undefined
): boolean {
return !shouldInitiatePeerConnection(localActorId, remoteActorId);
}