diff --git a/src/app/plugins/react-custom-html-parser.tsx b/src/app/plugins/react-custom-html-parser.tsx index 60aead019..17cbb84fb 100644 --- a/src/app/plugins/react-custom-html-parser.tsx +++ b/src/app/plugins/react-custom-html-parser.tsx @@ -125,7 +125,7 @@ function KatexRenderer({ useEffect(() => { let mounted = true; - void Promise.all([import('katex'), import('katex/dist/katex.min.css')]).then(([katex]) => { + void import('katex').then((katex) => { if (mounted) { setHtml(katex.default.renderToString(math, { throwOnError: false, displayMode })); } diff --git a/src/app/utils/mediaTransport.test.ts b/src/app/utils/mediaTransport.test.ts index 9d599313c..de6caf937 100644 --- a/src/app/utils/mediaTransport.test.ts +++ b/src/app/utils/mediaTransport.test.ts @@ -114,6 +114,36 @@ describe('fetchMediaBlob', () => { TEST_TIMEOUT ); + it('selects the JSON-encoded active session instead of falling back to the first account', async () => { + const { getActiveMediaSession, getCurrentMediaSessionScope } = await import('./mediaTransport'); + + localStorage.setItem( + 'matrixSessions', + JSON.stringify([ + { + baseUrl: 'https://matrix.example.org', + userId: '@alice:example.org', + deviceId: 'ALICE', + accessToken: 'alice-token', + }, + { + baseUrl: 'https://other.example.org', + userId: '@bob:example.org', + deviceId: 'BOB', + accessToken: 'bob-token', + }, + ]) + ); + localStorage.setItem('matrixActiveSession', JSON.stringify('@bob:example.org')); + + expect(getActiveMediaSession()).toEqual({ + baseUrl: 'https://other.example.org', + accessToken: 'bob-token', + userId: '@bob:example.org', + }); + expect(getCurrentMediaSessionScope()).toBe('@bob:example.org'); + }); + it( 'uses caller-provided auth and cache scope when present', async () => { diff --git a/src/app/utils/mediaTransport.ts b/src/app/utils/mediaTransport.ts index e4a93b710..34083eb76 100644 --- a/src/app/utils/mediaTransport.ts +++ b/src/app/utils/mediaTransport.ts @@ -65,11 +65,24 @@ function parseStoredSessions(): StoredSession[] { } } +function getStoredActiveSessionId(): string | undefined { + const raw = localStorage.getItem(ACTIVE_SESSION_KEY); + if (!raw) return undefined; + + try { + const parsed = JSON.parse(raw); + return typeof parsed === 'string' ? parsed : undefined; + } catch { + // Keep reading pre-multi-account values written without JSON encoding. + return raw; + } +} + function getActiveStoredSession(): StoredSession | undefined { if (typeof localStorage === 'undefined') return undefined; const sessions = parseStoredSessions(); - const activeSessionId = localStorage.getItem(ACTIVE_SESSION_KEY); + const activeSessionId = getStoredActiveSessionId(); return ( (activeSessionId ? sessions.find((session) => session.userId === activeSessionId) diff --git a/src/app/utils/tauriMediaAuth.test.ts b/src/app/utils/tauriMediaAuth.test.ts index db576446e..2c5713f71 100644 --- a/src/app/utils/tauriMediaAuth.test.ts +++ b/src/app/utils/tauriMediaAuth.test.ts @@ -112,4 +112,30 @@ describe('Tauri media session coordinator', () => { const scopes = commands.setMediaSession.mock.calls.map(([params]) => params.scope); expect(scopes).toEqual(['@a:matrix.example', '@a:matrix.example']); }); + + it('syncs the native session when the active account changes in this window', async () => { + mediaTransport.getActiveMediaSession + .mockReturnValueOnce({ + baseUrl: 'https://one.example', + accessToken: 'one', + userId: '@a:one.example', + }) + .mockReturnValue({ + baseUrl: 'https://two.example', + accessToken: 'two', + userId: '@b:two.example', + }); + + await initTauriMediaSession(); + commands.setMediaSession.mockClear(); + + window.dispatchEvent(new Event('sable-session-changed')); + await vi.waitFor(() => expect(commands.setMediaSession).toHaveBeenCalledTimes(1)); + + expect(commands.setMediaSession).toHaveBeenCalledWith({ + baseUrl: 'https://two.example', + token: 'two', + scope: '@b:two.example', + }); + }); }); diff --git a/src/app/utils/tauriMediaAuth.ts b/src/app/utils/tauriMediaAuth.ts index 40da92901..6ca4877d0 100644 --- a/src/app/utils/tauriMediaAuth.ts +++ b/src/app/utils/tauriMediaAuth.ts @@ -6,6 +6,7 @@ import { getActiveMediaSession } from './mediaTransport'; const log = createLogger('tauri-media-auth'); let pendingNativeWrite: Promise = Promise.resolve(); +let tauriMediaSessionListenersInstalled = false; export const updateTauriMediaSession = ( baseUrl?: string, @@ -41,9 +42,14 @@ const syncTauriMediaSession = (): Promise => { export const initTauriMediaSession = (): Promise => { if (!isTauri()) return Promise.resolve(); - const initialSync = syncTauriMediaSession(); - window.addEventListener('storage', () => { - void syncTauriMediaSession(); - }); - return initialSync; + if (!tauriMediaSessionListenersInstalled) { + const sync = () => { + void syncTauriMediaSession(); + }; + window.addEventListener('storage', sync); + window.addEventListener('sable-session-changed', sync); + tauriMediaSessionListenersInstalled = true; + } + + return syncTauriMediaSession(); }; diff --git a/src/index.tsx b/src/index.tsx index 1b67abe5b..ded943743 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -7,6 +7,7 @@ import '@fontsource/space-mono/400.css'; import '@fontsource/space-mono/700.css'; import '@fontsource/space-mono/400-italic.css'; import '@fontsource/space-mono/700-italic.css'; +import 'katex/dist/katex.min.css'; import 'folds/dist/style.css'; import { configClass, varsClass } from 'folds'; import App from './app/pages/App'; diff --git a/vite.config.ts b/vite.config.ts index f980a90c8..e1dd090b7 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -143,7 +143,7 @@ export default defineConfig(({ command }) => { clearScreen: false, appType: 'spa', publicDir: false, - base: buildConfig.base, + base: command === 'build' && isTauriBuild ? './' : buildConfig.base, envPrefix: ['VITE_', 'TAURI_ENV_*'], define: { APP_VERSION: JSON.stringify(appVersion),