detached 1c1c842

fix(aim): keep session cookie when logout fails and hide others' direct rooms from getChatRooms

commit
1c1c842bd4771002ad6a8ab556da86fdfc740600
author
Kit · · 2026-09-29 05:59 UTC
parent
7a8fd7b
signature
SSH UnverifiedSHA256:H91DFuEG4jpk5edzLA4aUKkpv4zv9Q339ipRmkph6Tk The key is unknown
4 files · +19 -11
M src/convex/queries.ts +4 -2
@@ -230,11 +230,13 @@ export const getUsers = authQuery({
230230 }
231231 });
232232
233-// Authenticated query: Get chat rooms
233+// Authenticated query: Get chat rooms (direct rooms only when the user is a member)
234234 export const getChatRooms = authQuery({
235235 args: {},
236236 handler: async (ctx) => {
237- const rooms = await ctx.db.query('chatRooms').collect();
237+ const rooms = (await ctx.db.query('chatRooms').collect()).filter(
238+ (room) => room.type !== 'direct' || (room.memberIds ?? []).includes(ctx.user._id)
239+ );
238240 return rooms.map((room) => ({
239241 id: room._id,
240242 name: room.name,
@@ -230,11 +230,13 @@ export const getUsers = authQuery({
230 }230 }
231 });231 });
232 232
233-// Authenticated query: Get chat rooms233+// Authenticated query: Get chat rooms (direct rooms only when the user is a member)
234 export const getChatRooms = authQuery({234 export const getChatRooms = authQuery({
235 args: {},235 args: {},
236 handler: async (ctx) => {236 handler: async (ctx) => {
237- const rooms = await ctx.db.query('chatRooms').collect();237+ const rooms = (await ctx.db.query('chatRooms').collect()).filter(
238+ (room) => room.type !== 'direct' || (room.memberIds ?? []).includes(ctx.user._id)
239+ );
238 return rooms.map((room) => ({240 return rooms.map((room) => ({
239 id: room._id,241 id: room._id,
240 name: room.name,242 name: room.name,
R src/lib/aim/sounds.ts → src/lib/aim/sounds.svelte.ts +0 -0 renamed, 100% similar

renamed, 100% similar

M src/lib/components/desktop/buddy-list.svelte +6 -3
@@ -6,7 +6,7 @@
66 import type { Id } from '../../../convex/_generated/dataModel';
77 import type { Buddy, AimStatus } from '$lib/types/aim';
88 import { DEFAULT_BUDDY_GROUPS, STATUS_LABELS_FR } from '$lib/types/aim';
9- import { playSound } from '$lib/aim/sounds';
9+ import { playSound } from '$lib/aim/sounds.svelte';
1010 import { authQuery, useAimClient, convexAvailable } from '$lib/aim/client';
1111 import { chatState } from '$lib/states/chat.svelte';
1212 import { desktop } from '$lib/states/desktop.svelte';
@@ -209,7 +209,8 @@
209209 async function logout() {
210210 dropdownMenu = null;
211211 try {
212- await fetch('/api/session/logout', { method: 'POST' });
212+ const response = await fetch('/api/session/logout', { method: 'POST' });
213+ if (!response.ok) throw new Error('logout failed');
213214 window.location.reload();
214215 } catch {
215216 error = 'Déconnexion impossible.';
@@ -403,7 +404,9 @@
403404 })}
404405 ><img src="/aim/running-man-16.png" alt="" width="12" height="12" />
405406 <span class="truncate"
406- >{room.other.nickname}{room.unreadCount ? ` (${room.unreadCount})` : ''}</span
407+ >{room.other.nickname}{room.unreadCount
408+ ? ` (${room.unreadCount >= 100 ? '99+' : room.unreadCount})`
409+ : ''}</span
407410 ></button
408411 >{/if}{/each}{/if}
409412 {/if}
@@ -6,7 +6,7 @@
6 import type { Id } from '../../../convex/_generated/dataModel';6 import type { Id } from '../../../convex/_generated/dataModel';
7 import type { Buddy, AimStatus } from '$lib/types/aim';7 import type { Buddy, AimStatus } from '$lib/types/aim';
8 import { DEFAULT_BUDDY_GROUPS, STATUS_LABELS_FR } from '$lib/types/aim';8 import { DEFAULT_BUDDY_GROUPS, STATUS_LABELS_FR } from '$lib/types/aim';
9- import { playSound } from '$lib/aim/sounds';9+ import { playSound } from '$lib/aim/sounds.svelte';
10 import { authQuery, useAimClient, convexAvailable } from '$lib/aim/client';10 import { authQuery, useAimClient, convexAvailable } from '$lib/aim/client';
11 import { chatState } from '$lib/states/chat.svelte';11 import { chatState } from '$lib/states/chat.svelte';
12 import { desktop } from '$lib/states/desktop.svelte';12 import { desktop } from '$lib/states/desktop.svelte';
@@ -209,7 +209,8 @@
209 async function logout() {209 async function logout() {
210 dropdownMenu = null;210 dropdownMenu = null;
211 try {211 try {
212- await fetch('/api/session/logout', { method: 'POST' });212+ const response = await fetch('/api/session/logout', { method: 'POST' });
213+ if (!response.ok) throw new Error('logout failed');
213 window.location.reload();214 window.location.reload();
214 } catch {215 } catch {
215 error = 'Déconnexion impossible.';216 error = 'Déconnexion impossible.';
@@ -403,7 +404,9 @@
403 })}404 })}
404 ><img src="/aim/running-man-16.png" alt="" width="12" height="12" />405 ><img src="/aim/running-man-16.png" alt="" width="12" height="12" />
405 <span class="truncate"406 <span class="truncate"
406- >{room.other.nickname}{room.unreadCount ? ` (${room.unreadCount})` : ''}</span407+ >{room.other.nickname}{room.unreadCount
408+ ? ` (${room.unreadCount >= 100 ? '99+' : room.unreadCount})`
409+ : ''}</span
407 ></button410 ></button
408 >{/if}{/each}{/if}411 >{/if}{/each}{/if}
409 {/if}412 {/if}
M src/routes/api/session/logout/+server.ts +9 -6
@@ -6,16 +6,22 @@ import { createLogger } from '$lib/utils/logger.server';
66
77 const log = createLogger('logout-server');
88
9+const json = (body: object, status: number) =>
10+ new Response(JSON.stringify(body), {
11+ status,
12+ headers: { 'Content-Type': 'application/json' }
13+ });
14+
915 export const POST: RequestHandler = async ({ cookies, locals }) => {
10- let success = true;
1116 if (locals.session) {
1217 try {
1318 await invalidateSession(locals.session.id);
1419 } catch (err) {
15- success = false;
20+ // Keep the cookie: the session is still valid, so the client must not think it is signed out.
1621 log.error('Session invalidation failed', {
1722 error: err instanceof Error ? err.message : 'Unknown error'
1823 });
24+ return json({ success: false }, 500);
1925 }
2026 try {
2127 await users.updateStatus(locals.session.userId, 'offline');
@@ -26,8 +32,5 @@ export const POST: RequestHandler = async ({ cookies, locals }) => {
2632 }
2733 }
2834 deleteSessionTokenCookie({ cookies });
29- return new Response(JSON.stringify({ success }), {
30- status: success ? 200 : 500,
31- headers: { 'Content-Type': 'application/json' }
32- });
35+ return json({ success: true }, 200);
3336 };
@@ -6,16 +6,22 @@ import { createLogger } from '$lib/utils/logger.server';
6 6
7 const log = createLogger('logout-server');7 const log = createLogger('logout-server');
8 8
9+const json = (body: object, status: number) =>
10+ new Response(JSON.stringify(body), {
11+ status,
12+ headers: { 'Content-Type': 'application/json' }
13+ });
14+
9 export const POST: RequestHandler = async ({ cookies, locals }) => {15 export const POST: RequestHandler = async ({ cookies, locals }) => {
10- let success = true;
11 if (locals.session) {16 if (locals.session) {
12 try {17 try {
13 await invalidateSession(locals.session.id);18 await invalidateSession(locals.session.id);
14 } catch (err) {19 } catch (err) {
15- success = false;20+ // Keep the cookie: the session is still valid, so the client must not think it is signed out.
16 log.error('Session invalidation failed', {21 log.error('Session invalidation failed', {
17 error: err instanceof Error ? err.message : 'Unknown error'22 error: err instanceof Error ? err.message : 'Unknown error'
18 });23 });
24+ return json({ success: false }, 500);
19 }25 }
20 try {26 try {
21 await users.updateStatus(locals.session.userId, 'offline');27 await users.updateStatus(locals.session.userId, 'offline');
@@ -26,8 +32,5 @@ export const POST: RequestHandler = async ({ cookies, locals }) => {
26 }32 }
27 }33 }
28 deleteSessionTokenCookie({ cookies });34 deleteSessionTokenCookie({ cookies });
29- return new Response(JSON.stringify({ success }), {35+ return json({ success: true }, 200);
30- status: success ? 200 : 500,
31- headers: { 'Content-Type': 'application/json' }
32- });
33 };36 };

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 3

Tags 9

↑↓ moveenter openesc close