mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
fix: batch of 22 correctness fixes across server and client (#1371)
* fix(voice): 4 defect(s) (OC-0008, OC-0009, OC-0042, OC-0080) Guard LiveKit session state against supersession: bump the camera/screen generation in leaveVoice and teardownForReconnect so an in-flight enable discards its track, bail out of restoreLocalVoiceState when a newer room claimed _room mid-await, and recheck isStateConnected in the auto-reconnect tail. * fix(ws): 1 defect(s) (OC-0019) * fix(db): 1 defect(s) (OC-0023) * fix(ws): 1 defect(s) (OC-0029) * fix(ws): 1 defect(s) (OC-0032) * fix(voice): 1 defect(s) (OC-0034) * fix(admin): 1 defect(s) (OC-0035) * fix(service): 2 defect(s) (OC-0036, OC-0128) * fix(voice): 2 defect(s) (OC-0038, OC-0065) OC-0038: the LiveKit participant_left webhook cleared the leaver's own client voice state before broadcasting voice_leave, so the broadcast audience (READ_MESSAGES holders union still-in-the-room participants) could no longer see them. Voice membership is gated on CONNECT_VOICE alone, so a participant without READ_MESSAGES never learned the server had torn down their call. Extracted finishVoiceLeave's audience logic into broadcastVoiceEventWithLeaver and used it on the webhook path. OC-0065: handleWebhookParticipantJoined OR'd a GetVoiceState read error into the same branch as "no matching row", so a transient DB failure ejected a legitimate participant from the SFU mid-call. Now the read error is logged and the check skipped, matching sweepStaleVoiceStates. * fix(client): 1 defect(s) (OC-0041) * fix(client): 1 defect(s) (OC-0043) * fix(client): 1 defect(s) (OC-0046) * fix(client): 1 defect(s) (OC-0047) * fix(client): 1 defect(s) (OC-0049) * fix(client): 1 defect(s) (OC-0108) * fix(client): 2 defect(s) (OC-0111, OC-0143) OC-0111: retry a presence_update dropped by the 1-per-10s limiter once the window reopens, so auto-idle's return-to-online does not leave the server and every other client stuck on idle. OC-0143: pass apiConfig.host to the DM profile sidebar so per-user notes are scoped per server, matching channel mutes, the NSFW gate and volume. * test(ws): align aborted-switch test with OC-0034 no-resurrect behavior The fix agent rewrote this pre-existing test (it locked the buggy restore path) but the prove agent left it out of c67d25ed; committed state alone failed go test ./ws/ without it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -709,6 +709,43 @@ func TestAdminAPI_DeleteChannel_CleansVoiceBeforeDBDelete(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// A voice_join racing the delete window must be refused, not silently create
|
||||
// a voice_states row the FK cascade then wipes out from under it (OC-0035):
|
||||
// CleanupVoiceForChannel snapshots participants ONCE, up front, so a join
|
||||
// that lands after that snapshot but before AdminDeleteChannel's cascade
|
||||
// leaves the joiner's hub-side voice state and LiveKit session orphaned with
|
||||
// nothing left to clean it up. handleDeleteChannel must close that window the
|
||||
// same way the archive path does (handlePatchChannel): persist archived=true
|
||||
// BEFORE evicting current participants, so voice_join's archived gate
|
||||
// (ws/voice_join.go) refuses any concurrent join that reads the channel row
|
||||
// during cleanup.
|
||||
func TestAdminAPI_DeleteChannel_ArchivesBeforeVoiceCleanup(t *testing.T) {
|
||||
database := openAdminTestDB(t)
|
||||
hub := &mockHub{}
|
||||
handler := admin.NewAdminAPI(database, "1.0.0", hub, nil, nil, nil, nil, newTestModService(database), newTestRoleService(database))
|
||||
token := createAdminUser(t, database)
|
||||
|
||||
chID, _ := database.AdminCreateChannel(context.Background(), "del-race", "voice", "", "", 0)
|
||||
|
||||
archivedAtCleanup := false
|
||||
hub.onVoiceCleanup = func(channelID int64) {
|
||||
ch, err := database.GetChannel(context.Background(), channelID)
|
||||
if err != nil || ch == nil {
|
||||
t.Fatalf("GetChannel during cleanup: %v", err)
|
||||
}
|
||||
archivedAtCleanup = ch.Archived
|
||||
}
|
||||
|
||||
w := doRequest(t, handler, http.MethodDelete, "/channels/"+itoa(chID), token, nil)
|
||||
if w.Code != http.StatusNoContent {
|
||||
t.Fatalf("status = %d, want 204; body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
if !archivedAtCleanup {
|
||||
t.Errorf("channel.Archived at CleanupVoiceForChannel time = false, want true — a concurrent voice_join would not be refused by the archived gate")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminAPI_DeleteChannel_NotFound(t *testing.T) {
|
||||
database := openAdminTestDB(t)
|
||||
handler := admin.NewAdminAPI(database, "1.0.0", &mockHub{}, nil, nil, nil, nil, newTestModService(database), newTestRoleService(database))
|
||||
|
||||
@@ -281,6 +281,33 @@ func handleDeleteChannel(database *db.DB, hub HubBroadcaster) http.HandlerFunc {
|
||||
}
|
||||
id := existing.ID
|
||||
|
||||
// Mark the channel archived BEFORE evicting participants, mirroring the
|
||||
// archive path (handlePatchChannel): CleanupVoiceForChannel snapshots
|
||||
// voice participants ONCE, up front, so a voice_join racing this delete
|
||||
// could otherwise read the still-live channel row, pass the archived
|
||||
// gate (ws/voice_join.go), and insert a voice_states row after the
|
||||
// snapshot but before AdminDeleteChannel's cascade — leaving that
|
||||
// joiner's hub-side voice state and LiveKit session orphaned with no
|
||||
// DB row left for any sweep to find (OC-0035). Persisting archived=1
|
||||
// first makes voice_join's existing archived check refuse that join
|
||||
// outright, the same way it already refuses one racing an archive.
|
||||
if !existing.Archived {
|
||||
if err := database.AdminUpdateChannel(r.Context(), id, db.ChannelUpdate{
|
||||
Name: existing.Name,
|
||||
Topic: existing.Topic,
|
||||
Category: existing.Category,
|
||||
SlowMode: existing.SlowMode,
|
||||
Position: existing.Position,
|
||||
Archived: true,
|
||||
NSFW: existing.NSFW,
|
||||
VoiceMaxUsers: existing.VoiceMaxUsers,
|
||||
VoiceMaxVideo: existing.VoiceMaxVideo,
|
||||
}); err != nil {
|
||||
writeErr(w, http.StatusInternalServerError, "INTERNAL_ERROR", "failed to delete channel")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Evict voice participants BEFORE deleting the row: the voice_states
|
||||
// FK cascade wipes the rows the cleanup reads, and the stale sweeper
|
||||
// cannot recover participants of a channel that no longer exists.
|
||||
|
||||
Reference in New Issue
Block a user