Fix device takeover leaving both devices on the session with no working controls #23
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/device-takeover-stale-connection"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Two gaps around an already-correct takeover mechanism (
SocketGateway's "join" handler), not the mechanism itself.Player.tsandMusicBar.tswiredonClose: () => {}— a complete no-op — to the connection's "disconnect" event. So the moment a takeover force-disconnected the old device's socket, its video/hls.js instance (or the persistent music bar) just kept running forever on a dead connection — both devices looked fully active, since nothing ever told the old one to stop or navigate away.POST /api/parties/:partyId/leaveremoves a member byuserIdalone, with no equivalent of the socket handlers' own staleness guard. Calling it from the old, superseded device would have ended the new device's membership too, silently undoing the takeover it was supposed to be cleaning up after.Fixed both:
onClosenow tears down local playback and leaves whenever this route/module's own explicit teardown hasn't already run (leftIntentionallyin Player.ts,activePartyIdalready null in MusicBar.ts); the leave route (and the client, which now sends its owndeviceId) gained the same staleness guard the socket handlers already had, via a newPartyService.isStaleDevice(member, deviceId)— only ever blocks on positive evidence of a mismatch.Test plan
npm run typecheck/npm run build— clean on both projectsnpm test(server) — 153/153, including new coverage forisStaleDevice(matching deviceId, mismatched, unset member deviceId, unset caller deviceId, unknown member — each checked against why it should/shouldn't block)io()(real.on/.emit/a scriptable.disconnect()that fires real listeners) letjoinMusicParty/leaveMusicPartyrun for real. Firingdisconnectright after a realleaveMusicParty()produced no toast, bar stayed hidden (no spurious message on an intentional leave); firing it with no prior leave — the takeover-kick shape — produced the "Connexion à la party interrompue" toast and correctly tore the bar down.leftIntentionallyfix mirrors this exact shape (confirmed by reading, not re-verified with a second live harness — hls.js/a full player DOM would need mocking for no more signal than the MusicBar case already gives).🤖 Generated with Claude Code