Files
skills/docs/ui/react/react-audio-stream/src/session/interruption-classifier/interruption-classifier.md
2026-05-29 08:20:56 +01:00

3.8 KiB

interruption-classifier

(no submodules)

Purpose

InterruptionClassifier maps a structural disconnect cause to exactly one of network | media-permission | signalling | peer | unknown. Classification is deterministic — every rule is a typed equality or membership check, no probabilistic heuristics — so the same cause record always produces the same verdict regardless of timing or ordering. This is essential for the auto-reconnect policy that lands in chunk D: the policy decides whether to retry by inspecting the verdict label, and a flaky classifier would translate into flaky reconnect behaviour for users.

The classifier is intentionally state-free. Every consumer (the StreamSession, future React hooks, integration tests) constructs one instance and calls classify on every disconnect — there is no internal counter, ring buffer, or memo, so a single instance can be shared across sessions without contention.

Features

  • Five disjoint labels covering every observed interruption shape.
  • Priority-ordered rules with explicit precedence: peer beats media-permission beats network beats signalling.
  • WebRTC-friendly: keys directly off iceConnectionState and connectionState.
  • WebSocket-friendly: keys off RFC 6455 close codes, skipping the clean-close codes (1000, 1001) so a graceful server-side close does not trigger reconnect.
  • shouldAutoReconnect(class) helper exposes the chunk-D reconnect policy in a single place.
  • Zero allocations on the hot path beyond the verdict object.

Usage: Basic

import { InterruptionClassifier } from "@suaveplan/react-audio-stream/session";

const classifier = new InterruptionClassifier();
const verdict = classifier.classify({ iceConnectionState: "failed" });
// verdict.class === "network"

Usage: Advanced

import {
  InterruptionClassifier,
  type InterruptionCause,
} from "@suaveplan/react-audio-stream/session";

const classifier = new InterruptionClassifier();

function onDisconnect(rawCause: InterruptionCause) {
  const verdict = classifier.classify(rawCause);
  if (classifier.shouldAutoReconnect(verdict.class)) {
    scheduleReconnect(verdict);
  } else {
    surfaceToUi(verdict);
  }
}

// Hard browser-permission revoke
onDisconnect({
  mediaErrorName: "NotAllowedError",
  reason: "user revoked microphone permission",
});
// → verdict.class === "media-permission"; no reconnect.

// Server-initiated abnormal close
onDisconnect({ signallingCloseCode: 1006, reason: "abrupt server close" });
// → verdict.class === "signalling"; reconnect scheduled.

API Reference

  • class InterruptionClassifier — stateless classifier.
  • classify(cause: InterruptionCause): InterruptionVerdict — apply the rule mapping and return { class, cause }.
  • shouldAutoReconnect(class: InterruptionClass): boolean — true iff the class is network or signalling.
  • InterruptionClass, InterruptionCause, InterruptionVerdict — re-exported types.

Implementation Notes

The rule ordering is the only piece of business logic with subtle consequences. peer is evaluated first because an intentional remote leave should never be treated as a network glitch — even if the ICE state also happens to read disconnected during the closing handshake. Permission errors take precedence over ICE because a user-revoked microphone often surfaces as both a getUserMedia rejection and an ICE failure milliseconds later; the permission failure is the actionable cause. Close codes 1000 and 1001 are explicitly skipped so a clean server shutdown (going away) does not show up as a signalling interruption — those map to unknown instead, which the reconnect policy treats as non-retryable. The classifier deliberately holds no state so it composes cleanly into the FSM-driven session flow without spreading hidden coupling.