Skip to content

feat: session resumption and connection migration with encrypted state transfer - #266

Merged
levibliz merged 3 commits into
RiftCore00:devfrom
RajeshRk18:feat/session-resumption-228
Aug 21, 2026
Merged

feat: session resumption and connection migration with encrypted state transfer#266
levibliz merged 3 commits into
RiftCore00:devfrom
RajeshRk18:feat/session-resumption-228

Conversation

@RajeshRk18

Copy link
Copy Markdown
Contributor

Closes #228

What this adds

Session resumption with zero-downtime client handoff between gateway instances.

  • src/session-manager.jsSessionManager: session state is JSON → deflate → AES-256-GCM (HKDF-derived key, 12-byte IV), blob format <keyId>.<iv>.<ct>.<tag>. Key rotation via a JSON key map ({"v1":…,"v2":…}): every listed key opens old blobs. Storage is an injected node-redis-v4-style client (SET … PX, sliding TTL, session:<clientId> keys) with an in-memory TTL store fallback for single-instance mode. Debounced saves (500 ms coalescing per client), flush/flushAll for shutdown, 16 KB blob cap, session_resumption_total{success|decrypt_failed|expired|mismatch|new_session} and session_state_size_bytes metrics.
  • Handshake (src/server.js) — ?session_id=<blob> (or JWT sid claim) after auth: decrypt, verify liveness/TTL, bind state.clientId to the authenticated JWT sub (mismatch → counted, treated as new session), restore room memberships + acked seqs + geofence set, conservatively re-consume the saved rate-limit window (no reset-burst), reply session_resumed with per-room highestAckedSeq/highestReceivedSeq and live currentSeqPerRoom; gap replay uses the existing reconnect flow. Inbound frames wait on the handshake so restored state always lands first.
  • Sticky-cookie fallbackGW_AFFINITY=<instanceId> cookie on the upgrade response; a client returning to the same instance without a blob restores from the local in-memory cache, no Redis round-trip.
  • Graceful shutdown (src/index.js) — persists every live session and delivers each client its fresh blob inside server_shutting_down.
  • Admin migrationPOST /admin/v1/clients/{clientId}/migrate: immediate save, then close 4100 with the blob (close reason when it fits, else a preceding migrate frame). GET /metrics exposes the counters.
  • Resumption is strictly opt-in: without SESSION_ENCRYPTION_KEY (or an injected manager) the gateway behaves byte-identically to before.

Everything is Node built-ins (node:crypto, node:zlib) — no new dependencies. New env vars documented in .env.example and README.

Repairs the feature required

The dev branch does not currently build; three files needed repair before any acceptance criterion (lint passing, existing tests passing) was reachable. Same situation previously handled by #209/#218.

  • src/rate-limiter.js — file was truncated: the returned object literal and factory were never closed (2 lines appended, nothing else).
  • src/server.js — corrupted merge: duplicate createServer declaration, an import mid-function, await in non-async callbacks, undefined safeSend. Reconstructed as one coherent handler preserving all tested behavior.
  • src/room-manager.js — methods and the merged DoS test suite referenced constructor state that was never initialized (_roomSeq, ring buffers, dedup cache, _totalMembers, circuit breaker). Initialized and implemented to the existing tests' spec.
  • src/validator.js — the token_refresh message type was missing from the schema union, so refresh frames never reached their handler and tests/refresh.test.js could not pass.

I know the issue lists some of these as do-not-modify; with the parse errors on dev there is no state in which npm run lint or the suite passes without them. No existing test file was modified.

Verification

  • Baseline on dev: lint fails (2 parse errors), 26 tests failing in 14 files.
  • This branch: npm run lint clean; npx vitest run26 files passed, 280 tests passed, 15 skipped (the skips are the pre-existing self-skipping postgres suite), stable across repeated runs.
  • New coverage: tests/session-manager.test.js (47 unit tests: round-trip, tamper/expiry/rotation, debounce, TTL sliding, fallback store, metrics, 50-room blob = 1801 bytes < 16 KB) and tests/session-resumption-integration.test.js (15 end-to-end tests over real sockets: resume/expired/corrupted/mismatch, debounced save visible in the store within 1 s, sticky-cookie restore without a store read, migrate round-trip, cross-key rotation resume, shutdown save-all, metrics).

@levibliz

Copy link
Copy Markdown
Contributor

@RajeshRk18 Please resolve your conflict

…nto feat/session-resumption-228

Resolves conflicts with the independently merged session-resumption
implementation (RiftCore00#267) and the co-located HTTP server / backpressure
work (RiftCore00#203, RiftCore00#217, RiftCore00#218):

- src/session-manager.js: keep this branch's encrypted manager
  (AES-256-GCM + HKDF, Redis/in-memory store, debounced saves, metrics)
  over the add/add conflicting upstream variant.
- src/server.js: keep this branch's session wiring and admin migration
  endpoint; integrate upstream's /healthz, /readyz and Prometheus
  /metrics endpoints, gateway metrics counters, event-loop lag sampling
  and markShuttingDown. Session resumption counters are now exported in
  the Prometheus exposition instead of a JSON /metrics body.
- src/index.js: keep session-aware graceful shutdown; call
  markShuttingDown() on SIGTERM/SIGINT so health probes flip to 503.
- src/room-manager.js: keep ring-buffer replay/dedup/limits; restore the
  backpressure-aware broadcast wiring (constructor options, client
  state tracking and cleanup) that upstream's copy referenced but never
  initialized.
- tests: keep this branch's session-manager and resumption integration
  suites; update /metrics assertions to the Prometheus format.
@RajeshRk18
RajeshRk18 force-pushed the feat/session-resumption-228 branch from e6487ec to 4908396 Compare August 18, 2026 00:18
@RajeshRk18

Copy link
Copy Markdown
Contributor Author

@RajeshRk18 Please resolve your conflict

Resolved

@RajeshRk18

RajeshRk18 commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

@levibliz will you merge it after i resolve conflicts???

you closed the issue i worked as completed via another PR?? #228

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants