← Back to Frequently Asked Questions

How to set up rtcstats-js in a Single Page Application

Choosing between a per-page socket and a per-call socket in SPAs, and how to avoid the timing bug that causes parser errors when cycling connect/close.

If your app is a Single Page Application (SPA), you have two options for how to manage the rtcstats-js WebSocket connection: open it once on page load, or open and close it around each call. They're not equivalent - here's what each one does and which one to use.

Option 1: One socket for the whole page session

You call trace.connect() once when the app loads and leave it open for the lifetime of the page. Every WebRTC call the user makes ends up in the same dump.

This works, but it has a real drawback: if a user stays in your app for hours and makes several calls, the dump grows continuously. A full day of calls in one dump makes rtcstats.com analysis harder - the file is large, the timeline is long, and Observations get harder to attribute to the right call. It's also wasteful for users who open your app but never start a call.

Option 2: One socket per call (recommended)

Call trace.connect() just before you create your RTCPeerConnection, and call trace.close() after the call ends. Each call produces its own dump - a clean, bounded unit that maps to one conversation.

This is the recommended approach. Smaller dumps analyze faster, the Observations and Experience Score map to a single call, and you only send data for calls that actually happened.

// Generate a fresh token with identifiers for this call
const token = await fetchRtcstatsToken({ session: uuid(), conference: roomId, user: userId });

// Connect before creating any RTCPeerConnection
await trace.connect('wss://your-domain.example.com/?rtcstats-token=' + token);

// Now create your peer connection - rtcstats-js is already watching
const pc = new RTCPeerConnection(config);

// ... call runs ...

// After the call ends, close the peer connection first, then the socket
pc.close();
trace.close();

Two things matter here:

  1. trace.connect() must be awaited before you instantiate RTCPeerConnection. If the socket isn't open when getStats() events start firing, those early events get dropped.
  2. Close the RTCPeerConnection before calling trace.close(), so the final stats events are captured before the dump is finalized.

The timing bug: "Number of minutes must be positive or 0"

If rtcstats.com rejects your dump with this error, you have a timestamp ordering problem. It happens when metadata.startTime ends up later than some event timestamps already recorded in the dump.

The typical cause: calling connect() on one tick and immediately creating a RTCPeerConnection on the next, while some events fire before the socket handshake completes and sets startTime. The dump ends up with events timestamped before the declared start.

To avoid it:

  • Always await trace.connect(...) before creating RTCPeerConnection
  • Don't reuse a single trace instance across calls by calling connect() / close() on it in a tight loop - create a fresh connection for each call
  • If you are managing a wrapper around the trace instance, use a generation counter or similar guard to ensure a stale close() from a previous call can't race with the new connect()

Handling concurrent calls

If your app supports multiple simultaneous calls (rare, but possible), each call needs its own RTCPeerConnection and its own rtcstats-js socket. A single socket maps to a single dump - it's not designed to multiplex multiple peer connections into separate sessions.

See also

Was this page helpful?