Stabilize room host ownership across rejoins - #195
Merged
Conversation
Keep creator ownership separate from active admin handoffs and restore the creator deterministically on rejoin. Centralize role checks and document the authorization contract needed by room invitations.\n\nRefs #153
Preserve the non-null admin socket contract, always select and persist a successor on removal, and reject self-removal moderation requests. Make explicit two-tab leaves remove their own socket membership before checking for remaining tabs so the last departure cannot strand a room.\n\nRefs #153
Claim persisted room departures with a MongoDB compare-and-set on the active membership revision. This prevents competing Socket.IO processes from duplicating leave side effects or overwriting a rejoin, while retaining the reconnect grace behavior.\n\nRefs #153
Carry the membership revision captured when a socket leaves through reconnect cleanup and reject a departure when that generation no longer matches. This prevents a losing old finalizer from removing a later rejoin.\n\nRefs #153
Advance a durable per-user presence revision before acknowledging an already-active room membership. Departure claims now require that generation, so an old leave cannot remove a newly opened tab.\n\nRefs #153
Validate the current room state after a departure wins the duplicate-join race, preventing bans or changed access rules from being bypassed.
This was referenced Jul 13, 2026
coder13
marked this pull request as ready for review
July 14, 2026 01:06
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
UPDATE_ADMINSocket.IO contract while persistingadmin: nullfor empty roomsRoot cause
The ordinary creator leave/rejoin path already attempted to restore ownership. Its selector was brittle, though: owner restoration required a non-null current admin, while the fallback always chose the first stored active participant. That could select the wrong host for a null-admin room and erase a legitimate active handoff while the owner was absent. The original correction also made empty rooms invoke a callback with
admin: null, but established clients dereferenceadmin.id. Explicit leave checked for peer tabs before removing the current socket from the per-user Socket.IO room, allowing two simultaneous leaves to each see the other and strand membership. Process-local departure queues also could not serialize competing Socket.IO processes. Finally, a duplicate socket could arrive while persisted membership was still active, leaving an old departure generation valid unless the new socket durably fenced it.Authorization, protocol, and concurrency contract
Owner is permanent creator/deletion authority. Admin is the sole active control holder. The active owner reclaims admin; otherwise the active current admin remains, then the first active participant is promoted. Empty rooms persist no admin but never broadcast a null
UPDATE_ADMINpayload. Raw self-kick and self-ban are rejected; users leave through the ordinary departure path.Each actual join or removal advances the room membership revision. Each authenticated duplicate join atomically advances only that user's
presenceRevisionbefore acknowledgment; it does not alter membership or admin. Reconnect cleanup captures both revisions. Departure uses a MongoDB conditional update requiring the same active membership and both generations. Exactly one process can claim that departure and emit its metrics/events. A failed claim is terminal and cannot remove a later session. Reconnect grace remains unchanged.For #187, private-room invitation authority must combine canonical active membership with owner-or-current-admin role checks. Delete authorization alone is intentionally insufficient because an absent owner may still delete a room.
Verification
git diff --checkpassesserver/api.jsimport-extension violation, outside this PRGrand Prix is disabled in current runtime configuration; the rejection/no-mutation path is covered, while enabled Grand Prix uses the same model selector but was not browser-tested.
Fixes #153