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 Unverified
SHA256:H91DFuEG4jpk5edzLA4aUKkpv4zv9Q339ipRmkph6TkThe key is unknown
4 files · +19 -11
M src/convex/queries.ts +4 -2
| @@ -230,11 +230,13 @@ export const getUsers = authQuery({ | |||
| 230 | 230 | } | |
| 231 | 231 | }); | |
| 232 | 232 | ||
| 233 | - | // Authenticated query: Get chat rooms | |
| 233 | + | // Authenticated query: Get chat rooms (direct rooms only when the user is a member) | |
| 234 | 234 | export const getChatRooms = authQuery({ | |
| 235 | 235 | args: {}, | |
| 236 | 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 | 240 | return rooms.map((room) => ({ | |
| 239 | 241 | id: room._id, | |
| 240 | 242 | name: room.name, | |
| @@ -230,11 +230,13 @@ export const getUsers = authQuery({ | |||||
| 230 | } | 230 | } | ||
| 231 | }); | 231 | }); | ||
| 232 | 232 | ||||
| 233 | - | // Authenticated query: Get chat rooms | 233 | + | // 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 @@ | |||
| 6 | 6 | import type { Id } from '../../../convex/_generated/dataModel'; | |
| 7 | 7 | import type { Buddy, AimStatus } from '$lib/types/aim'; | |
| 8 | 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 | 10 | import { authQuery, useAimClient, convexAvailable } from '$lib/aim/client'; | |
| 11 | 11 | import { chatState } from '$lib/states/chat.svelte'; | |
| 12 | 12 | import { desktop } from '$lib/states/desktop.svelte'; | |
| @@ -209,7 +209,8 @@ | |||
| 209 | 209 | async function logout() { | |
| 210 | 210 | dropdownMenu = null; | |
| 211 | 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 | 214 | window.location.reload(); | |
| 214 | 215 | } catch { | |
| 215 | 216 | error = 'Déconnexion impossible.'; | |
| @@ -403,7 +404,9 @@ | |||
| 403 | 404 | })} | |
| 404 | 405 | ><img src="/aim/running-man-16.png" alt="" width="12" height="12" /> | |
| 405 | 406 | <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 | |
| 407 | 410 | ></button | |
| 408 | 411 | >{/if}{/each}{/if} | |
| 409 | 412 | {/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})` : ''}</span | 407 | + | >{room.other.nickname}{room.unreadCount |
| 408 | + | ? ` (${room.unreadCount >= 100 ? '99+' : room.unreadCount})` | |||
| 409 | + | : ''}</span | |||
| 407 | ></button | 410 | ></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'; | |||
| 6 | 6 | ||
| 7 | 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 | 15 | export const POST: RequestHandler = async ({ cookies, locals }) => { | |
| 10 | - | let success = true; | |
| 11 | 16 | if (locals.session) { | |
| 12 | 17 | try { | |
| 13 | 18 | await invalidateSession(locals.session.id); | |
| 14 | 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 | 21 | log.error('Session invalidation failed', { | |
| 17 | 22 | error: err instanceof Error ? err.message : 'Unknown error' | |
| 18 | 23 | }); | |
| 24 | + | return json({ success: false }, 500); | |
| 19 | 25 | } | |
| 20 | 26 | try { | |
| 21 | 27 | await users.updateStatus(locals.session.userId, 'offline'); | |
| @@ -26,8 +32,5 @@ export const POST: RequestHandler = async ({ cookies, locals }) => { | |||
| 26 | 32 | } | |
| 27 | 33 | } | |
| 28 | 34 | 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); | |
| 33 | 36 | }; | |
| @@ -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 | }; | ||