Skip to content

fix(replay): trust SDK-supplied session_id in handleReplay (follow-up to #318) - #319

Merged
ayushjhanwar-png merged 1 commit into
mainfrom
fix/replay-trust-sdk-session-id
Jun 15, 2026
Merged

fix(replay): trust SDK-supplied session_id in handleReplay (follow-up to #318)#319
ayushjhanwar-png merged 1 commit into
mainfrom
fix/replay-trust-sdk-session-id

Conversation

@ayushjhanwar-png

Copy link
Copy Markdown

Why this is needed

PR #318 made the SDK pass its localStorage / Firebase-UID-based deviceId to `/track/device-id?deviceId=`, and the server returns the matching session_id from the `sessions` table. The SDK then includes that session_id with every replay chunk.

But the replay handler (`handleReplay`) was still overriding `payload.session_id` by recomputing deviceId from IP + UA and looking up a session for that hash — re-introducing the NAT-collision behavior on the replay storage path:

```
SDK sends: session_id = "real-session-from-sessions-table"
Server overrides: session_id = lookup-by-IP+UA hash (could be a different session)
CH stores: session_replay_chunks.session_id = the-overridden-id
↑ doesn't match sessions.id → "Session not found"
```

Verified on dev: track flow correctly created a session under the SDK's localStorage UUID, but replay chunks landed under a session keyed by the IP+UA hash. Two separate sessions, same human.

The fix

Trust the SDK-supplied session_id when present. Fall back to the IP+UA derivation only when the SDK didn't send one (older SDKs that pre-date the deviceId override path).

```diff

  • let sessionId = payload.session_id;
  • if (ip && ua) {
  • let sessionId = payload.session_id;
  • if (!sessionId && ip && ua) {
    ```

Why this is safe now (unlike #315 which was reverted)

PR #315 had the SDK generate its own session_id (a random UUID with no row in `sessions`). Trusting it broke the dashboard's session lookup ("Session not found"). #317 reverted that.

This time the SDK's session_id is not self-generated — it comes from `/track/device-id`, which is a real row in `sessions` for the same deviceId the track flow used. So:

```
sessions.id ← server-issued, real row
sessionId on the wire ← echoed back to SDK, same value
chunks.session_id ← now also that value
```

All three match. Dashboard finds the session. Replay plays.

Backward compatibility

  • Modern SDKs (1.3.0+): `payload.session_id` is the server-issued id → trusted as-is. ✅
  • Legacy SDKs (1.1.x): `payload.session_id` may be empty/stale → falls through to the existing IP+UA derivation (unchanged behavior). ✅

Test plan

  • Typecheck — 18 pre-existing errors, no new
  • Deploy to dev, verify on local frameo:
    • Anonymous user: localStorage UUID gets a session in `sessions`; `session_replay_chunks.session_id` matches `sessions.id` ✅
    • Office colleague: gets their own UUID → different session → different replay timeline ✅
    • Dashboard "session details" page finds the session and plays the replay ✅

Companion to #318. The replay handler still derived a session by
recomputing deviceId from IP+UA, which overrode the SDK-provided
session_id (and re-introduced NAT-collision behavior on the replay
storage path even when the SDK was using the deviceId override
correctly on the track path).

With #318 shipped, the SDK's session_id is no longer self-generated:
it comes from /track/device-id?deviceId=<our localStorage UUID or
stable user id>, which is the same id the track flow uses to create
sessions. So the SDK-supplied session_id is now a real row in the
`sessions` table and trusting it is safe — which fixes the
"Session not found" dashboard error from the earlier attempt
(#315 / #317).

Behavior:
- payload.session_id present (modern SDKs): trust it. Chunks land
  under the same session row that the track flow created. Dashboard
  "session details" works.
- payload.session_id missing (legacy SDKs): fall back to the IP+UA
  derivation that's been there all along. No regression.
@ayushjhanwar-png
ayushjhanwar-png merged commit d3fbb24 into main Jun 15, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant