feat: implement centralized access token management for media downloads and user authentication
This commit is contained in:
@@ -8,6 +8,7 @@ import { useZoom } from '../../hooks/useZoom';
|
||||
import { usePan } from '../../hooks/usePan';
|
||||
import { downloadMedia } from '../../utils/matrix';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { getCurrentAccessToken } from '../../utils/auth';
|
||||
|
||||
export type ImageViewerProps = {
|
||||
alt: string;
|
||||
@@ -23,7 +24,8 @@ export const ImageViewer = as<'div', ImageViewerProps>(
|
||||
|
||||
const handleDownload = async () => {
|
||||
try {
|
||||
const fileContent = await downloadMedia(src, mx.getAccessToken());
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const fileContent = await downloadMedia(src, getCurrentAccessToken());
|
||||
FileSaver.saveAs(fileContent, alt);
|
||||
} catch (error) {
|
||||
console.warn('[ImageViewer] Failed to download media:', error);
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
downloadMedia,
|
||||
mxcUrlToHttp,
|
||||
} from '../../utils/matrix';
|
||||
import { getCurrentAccessToken } from '../../utils/auth';
|
||||
|
||||
const badgeStyles = { maxWidth: toRem(100) };
|
||||
|
||||
@@ -28,7 +29,8 @@ export function FileDownloadButton({ filename, url, mimeType, encInfo }: FileDow
|
||||
const [downloadState, download] = useAsyncCallback(
|
||||
useCallback(async () => {
|
||||
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
|
||||
const accessToken = mx.getAccessToken();
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const accessToken = getCurrentAccessToken();
|
||||
const fileContent = encInfo
|
||||
? await downloadEncryptedMedia(mediaUrl, (encBuf) => decryptFile(encBuf, mimeType, encInfo), accessToken)
|
||||
: await downloadMedia(mediaUrl, accessToken);
|
||||
|
||||
@@ -55,7 +55,8 @@ export function AudioContent({
|
||||
const [srcState, loadSrc] = useAsyncCallback(
|
||||
useCallback(async () => {
|
||||
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
|
||||
const accessToken = mx.getAccessToken();
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const accessToken = getCurrentAccessToken();
|
||||
const fileContent = encInfo
|
||||
? await downloadEncryptedMedia(mediaUrl, (encBuf) => decryptFile(encBuf, mimeType, encInfo), accessToken)
|
||||
: await downloadMedia(mediaUrl, accessToken);
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
mxcUrlToHttp,
|
||||
} from '../../../utils/matrix';
|
||||
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||
import { getCurrentAccessToken } from '../../../utils/auth';
|
||||
import { ModalWide } from '../../../styles/Modal.css';
|
||||
|
||||
const renderErrorButton = (retry: () => void, text: string) => (
|
||||
@@ -87,7 +88,8 @@ export function ReadTextFile({ body, mimeType, url, encInfo, renderViewer }: Rea
|
||||
const [textState, loadText] = useAsyncCallback(
|
||||
useCallback(async () => {
|
||||
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
|
||||
const accessToken = mx.getAccessToken();
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const accessToken = getCurrentAccessToken();
|
||||
const fileContent = encInfo
|
||||
? await downloadEncryptedMedia(mediaUrl, (encBuf) => decryptFile(encBuf, mimeType, encInfo), accessToken)
|
||||
: await downloadMedia(mediaUrl, accessToken);
|
||||
@@ -178,7 +180,8 @@ export function ReadPdfFile({ body, mimeType, url, encInfo, renderViewer }: Read
|
||||
const [pdfState, loadPdf] = useAsyncCallback(
|
||||
useCallback(async () => {
|
||||
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
|
||||
const accessToken = mx.getAccessToken();
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const accessToken = getCurrentAccessToken();
|
||||
const fileContent = encInfo
|
||||
? await downloadEncryptedMedia(mediaUrl, (encBuf) => decryptFile(encBuf, mimeType, encInfo), accessToken)
|
||||
: await downloadMedia(mediaUrl, accessToken);
|
||||
@@ -256,7 +259,8 @@ export function DownloadFile({ body, mimeType, url, info, encInfo }: DownloadFil
|
||||
const [downloadState, download] = useAsyncCallback(
|
||||
useCallback(async () => {
|
||||
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
|
||||
const accessToken = mx.getAccessToken();
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const accessToken = getCurrentAccessToken();
|
||||
const fileContent = encInfo
|
||||
? await downloadEncryptedMedia(mediaUrl, (encBuf) => decryptFile(encBuf, mimeType, encInfo), accessToken)
|
||||
: await downloadMedia(mediaUrl, accessToken);
|
||||
|
||||
@@ -31,6 +31,7 @@ import { decryptFile, downloadEncryptedMedia, mxcUrlToHttp } from '../../../util
|
||||
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||
import { ModalWide } from '../../../styles/Modal.css';
|
||||
import { validBlurHash } from '../../../utils/blurHash';
|
||||
import { getCurrentAccessToken } from '../../../utils/auth';
|
||||
|
||||
/**
|
||||
* Fetches media with authentication headers and returns a blob URL.
|
||||
@@ -129,7 +130,8 @@ export const ImageContent = as<'div', ImageContentProps>(
|
||||
const [srcState, loadSrc] = useAsyncCallback(
|
||||
useCallback(async () => {
|
||||
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
|
||||
const accessToken = mx.getAccessToken();
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const accessToken = getCurrentAccessToken();
|
||||
if (encInfo) {
|
||||
const fileContent = await downloadEncryptedMedia(
|
||||
mediaUrl,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
||||
import { decryptFile, downloadEncryptedMedia, isAuthenticatedMediaUrl, mxcUrlToHttp } from '../../../utils/matrix';
|
||||
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||
import { FALLBACK_MIMETYPE } from '../../../utils/mimeTypes';
|
||||
import { getCurrentAccessToken } from '../../../utils/auth';
|
||||
|
||||
/**
|
||||
* Fetches media with authentication headers and returns a blob URL.
|
||||
@@ -57,7 +58,8 @@ export function ThumbnailContent({ info, renderImage }: ThumbnailContentProps) {
|
||||
}
|
||||
|
||||
const mediaUrl = mxcUrlToHttp(mx, thumbMxcUrl, useAuthentication) ?? thumbMxcUrl;
|
||||
const accessToken = mx.getAccessToken();
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const accessToken = getCurrentAccessToken();
|
||||
if (encInfo) {
|
||||
const fileContent = await downloadEncryptedMedia(
|
||||
mediaUrl,
|
||||
|
||||
@@ -82,7 +82,8 @@ export const VideoContent = as<'div', VideoContentProps>(
|
||||
const [srcState, loadSrc] = useAsyncCallback(
|
||||
useCallback(async () => {
|
||||
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
|
||||
const accessToken = mx.getAccessToken();
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const accessToken = getCurrentAccessToken();
|
||||
const fileContent = encInfo
|
||||
? await downloadEncryptedMedia(mediaUrl, (encBuf) =>
|
||||
decryptFile(encBuf, mimeType, encInfo), accessToken
|
||||
|
||||
@@ -64,6 +64,7 @@ import {
|
||||
getExtension,
|
||||
ImageMetadata,
|
||||
} from '../../../utils/imageMetadata';
|
||||
import { getCurrentAccessToken } from '../../../utils/auth';
|
||||
|
||||
/**
|
||||
* Banner upload component for user's profile banner
|
||||
@@ -397,7 +398,8 @@ function ProfileBanner({ avatarRef, nameRef }: { avatarRef: React.RefObject<HTML
|
||||
return;
|
||||
}
|
||||
|
||||
const accessToken = mx.getAccessToken();
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const accessToken = getCurrentAccessToken();
|
||||
const headers: HeadersInit = {};
|
||||
if (useAuthentication && accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`;
|
||||
@@ -493,7 +495,8 @@ function ProfileBanner({ avatarRef, nameRef }: { avatarRef: React.RefObject<HTML
|
||||
const httpUrl = mxcUrlToHttp(mx, upload.mxc, useAuthentication);
|
||||
if (!httpUrl) throw new Error('Could not resolve uploaded avatar URL');
|
||||
|
||||
const accessToken = mx.getAccessToken();
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const accessToken = getCurrentAccessToken();
|
||||
let response = await fetch(httpUrl, {
|
||||
headers: accessToken && useAuthentication ? { Authorization: `Bearer ${accessToken}` } : undefined,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useMatrixClient } from './useMatrixClient';
|
||||
import { getCurrentAccessToken } from '../utils/auth';
|
||||
|
||||
/**
|
||||
* Fetches media with authentication and returns a blob URL.
|
||||
@@ -40,7 +41,8 @@ export const useAuthenticatedMediaUrl = (
|
||||
|
||||
const fetchMedia = async () => {
|
||||
try {
|
||||
const accessToken = mx.getAccessToken();
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const accessToken = getCurrentAccessToken();
|
||||
let response = await fetch(src, {
|
||||
method: 'GET',
|
||||
headers: accessToken
|
||||
@@ -104,7 +106,8 @@ export const useAuthenticatedMediaFetch = () => {
|
||||
}
|
||||
|
||||
try {
|
||||
const accessToken = mx.getAccessToken();
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const accessToken = getCurrentAccessToken();
|
||||
let response = await fetch(src, {
|
||||
method: 'GET',
|
||||
headers: accessToken
|
||||
|
||||
@@ -3,6 +3,7 @@ import { MatrixClient, UserEvent, UserEventHandlerMap } from 'matrix-js-sdk';
|
||||
import { useMatrixClient } from './useMatrixClient';
|
||||
import { useMediaAuthentication } from './useMediaAuthentication';
|
||||
import { mxcUrlToHttp } from '../utils/matrix';
|
||||
import { getCurrentAccessToken } from '../utils/auth';
|
||||
import {
|
||||
fetchAndExtractMetadata,
|
||||
extractMetadataFromImage,
|
||||
@@ -25,7 +26,8 @@ async function fetchAvatarData(
|
||||
if (!url) return null;
|
||||
|
||||
try {
|
||||
const accessToken = mx.getAccessToken();
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const accessToken = getCurrentAccessToken();
|
||||
let response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: accessToken && useAuthentication ? { Authorization: `Bearer ${accessToken}` } : undefined,
|
||||
@@ -85,7 +87,8 @@ export function useUserBanner(): [
|
||||
return;
|
||||
}
|
||||
|
||||
const accessToken = mx.getAccessToken();
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const accessToken = getCurrentAccessToken();
|
||||
const metadata = await fetchAndExtractMetadata(httpUrl, useAuthentication ? accessToken : null);
|
||||
setBanner(metadata.banner);
|
||||
} catch {
|
||||
@@ -236,7 +239,8 @@ export function useOtherUserBanner(userId: string, avatarMxc: string | undefined
|
||||
return;
|
||||
}
|
||||
|
||||
const accessToken = mx.getAccessToken();
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const accessToken = getCurrentAccessToken();
|
||||
const metadata = await fetchAndExtractMetadata(httpUrl, useAuthentication ? accessToken : null);
|
||||
|
||||
bannerMxc = metadata.banner;
|
||||
@@ -257,7 +261,8 @@ export function useOtherUserBanner(userId: string, avatarMxc: string | undefined
|
||||
return;
|
||||
}
|
||||
|
||||
const accessToken = mx.getAccessToken();
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const accessToken = getCurrentAccessToken();
|
||||
const headers: HeadersInit = {};
|
||||
if (useAuthentication && accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { MatrixClient, UserEvent, UserEventHandlerMap } from 'matrix-js-sdk';
|
||||
import { useMatrixClient } from './useMatrixClient';
|
||||
import { useMediaAuthentication } from './useMediaAuthentication';
|
||||
import { mxcUrlToHttp } from '../utils/matrix';
|
||||
import { getCurrentAccessToken } from '../utils/auth';
|
||||
import {
|
||||
embedColorInImage,
|
||||
extractColorFromImage,
|
||||
@@ -25,7 +26,8 @@ async function fetchAvatarData(
|
||||
if (!url) return null;
|
||||
|
||||
try {
|
||||
const accessToken = mx.getAccessToken();
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const accessToken = getCurrentAccessToken();
|
||||
let response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: accessToken && useAuthentication ? { Authorization: `Bearer ${accessToken}` } : undefined,
|
||||
@@ -86,7 +88,8 @@ export function useUserColor(): [
|
||||
return;
|
||||
}
|
||||
|
||||
const accessToken = mx.getAccessToken();
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const accessToken = getCurrentAccessToken();
|
||||
const extractedColor = await fetchAndExtractColor(httpUrl, useAuthentication ? accessToken : null);
|
||||
setColor(extractedColor);
|
||||
} catch (e) {
|
||||
@@ -237,7 +240,8 @@ export function useOtherUserColor(userId: string, avatarMxc: string | undefined)
|
||||
return;
|
||||
}
|
||||
|
||||
const accessToken = mx.getAccessToken();
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const accessToken = getCurrentAccessToken();
|
||||
const extractedColor = await fetchAndExtractColor(httpUrl, useAuthentication ? accessToken : null);
|
||||
|
||||
// Cache the result
|
||||
|
||||
@@ -2,8 +2,7 @@ import { useCallback, useEffect, useState } from 'react';
|
||||
import { UserEvent, UserEventHandlerMap } from 'matrix-js-sdk';
|
||||
import { useMatrixClient } from './useMatrixClient';
|
||||
import { useMediaAuthentication } from './useMediaAuthentication';
|
||||
import { mxcUrlToHttp } from '../utils/matrix';
|
||||
import {
|
||||
import { mxcUrlToHttp } from '../utils/matrix';import { getCurrentAccessToken } from '../utils/auth';import {
|
||||
fetchAndExtractMetadata,
|
||||
extractMetadataFromImage,
|
||||
embedMetadataInImage,
|
||||
@@ -26,7 +25,8 @@ async function fetchAvatarData(
|
||||
if (!url) return null;
|
||||
|
||||
try {
|
||||
const accessToken = mx.getAccessToken();
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const accessToken = getCurrentAccessToken();
|
||||
let response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: accessToken && useAuthentication ? { Authorization: `Bearer ${accessToken}` } : undefined,
|
||||
@@ -91,7 +91,8 @@ export function useUserProfileStyle(): [
|
||||
return;
|
||||
}
|
||||
|
||||
const accessToken = mx.getAccessToken();
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const accessToken = getCurrentAccessToken();
|
||||
const metadata = await fetchAndExtractMetadata(httpUrl, useAuthentication ? accessToken : null);
|
||||
setStyle({
|
||||
avatarBorderColor: metadata.avatarBorderColor,
|
||||
@@ -231,7 +232,8 @@ export function useOtherUserProfileStyle(userId: string, avatarMxc: string | und
|
||||
return;
|
||||
}
|
||||
|
||||
const accessToken = mx.getAccessToken();
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const accessToken = getCurrentAccessToken();
|
||||
const metadata = await fetchAndExtractMetadata(httpUrl, useAuthentication ? accessToken : null);
|
||||
|
||||
const styleData: ProfileStyleData = {
|
||||
|
||||
95
src/app/utils/auth.ts
Normal file
95
src/app/utils/auth.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Authentication utilities for managing access tokens across multiple accounts
|
||||
*/
|
||||
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import { getLocalStorageItem, setLocalStorageItem } from '../state/utils/atomWithLocalStorage';
|
||||
import { Session, Sessions, MATRIX_SESSIONS_KEY, CURRENT_SESSION_KEY } from '../state/sessions';
|
||||
|
||||
/**
|
||||
* Global reference to the current MatrixClient instance
|
||||
* Set by initMatrix.ts when a client is created
|
||||
*/
|
||||
let globalMatrixClient: MatrixClient | null = null;
|
||||
|
||||
/**
|
||||
* Set the global MatrixClient reference
|
||||
* This should be called from initMatrix.ts after creating the client
|
||||
* @param client - The MatrixClient instance or null to clear
|
||||
*/
|
||||
export function setGlobalMatrixClient(client: MatrixClient | null): void {
|
||||
globalMatrixClient = client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current MatrixClient instance
|
||||
* @returns The MatrixClient instance or null if not initialized
|
||||
*/
|
||||
export function getGlobalMatrixClient(): MatrixClient | null {
|
||||
return globalMatrixClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the access token for a session in localStorage
|
||||
* This should be called when a token is refreshed by the MatrixClient
|
||||
* @param userId - The user ID
|
||||
* @param newToken - The new access token
|
||||
*/
|
||||
export function updateSessionToken(userId: string, newToken: string): void {
|
||||
const sessions = getLocalStorageItem<Sessions>(MATRIX_SESSIONS_KEY, []);
|
||||
const sessionIndex = sessions.findIndex((s: Session) => s.userId === userId);
|
||||
|
||||
if (sessionIndex >= 0) {
|
||||
sessions[sessionIndex] = {
|
||||
...sessions[sessionIndex],
|
||||
accessToken: newToken,
|
||||
};
|
||||
setLocalStorageItem(MATRIX_SESSIONS_KEY, sessions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the access token for the currently active session.
|
||||
* Prioritizes the token from the active MatrixClient instance (most up-to-date),
|
||||
* then falls back to localStorage. This ensures we always use the freshest token,
|
||||
* even if it's been refreshed by the SDK.
|
||||
*
|
||||
* @returns The current access token or undefined if no session is active
|
||||
*/
|
||||
export function getCurrentAccessToken(): string | undefined {
|
||||
// First try to get the token from the active MatrixClient if available
|
||||
// This ensures we get the most up-to-date token, including any refreshed tokens
|
||||
if (globalMatrixClient) {
|
||||
const clientToken = globalMatrixClient.getAccessToken?.();
|
||||
if (clientToken) {
|
||||
return clientToken;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to localStorage if MatrixClient is not available or doesn't have a token
|
||||
const currentUserId = getLocalStorageItem<string | undefined>(CURRENT_SESSION_KEY, undefined);
|
||||
|
||||
if (!currentUserId) {
|
||||
// Fallback to old single-account storage for migration
|
||||
return localStorage.getItem('cinny_access_token') ?? undefined;
|
||||
}
|
||||
|
||||
const sessions = getLocalStorageItem<Sessions>(MATRIX_SESSIONS_KEY, []);
|
||||
const currentSession = sessions.find((s: Session) => s.userId === currentUserId);
|
||||
|
||||
return currentSession?.accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the access token for a specific user.
|
||||
* Useful when you need to fetch data for a non-active account.
|
||||
*
|
||||
* @param userId - The Matrix user ID
|
||||
* @returns The access token for the specified user or undefined
|
||||
*/
|
||||
export function getAccessTokenForUser(userId: string): string | undefined {
|
||||
const sessions = getLocalStorageItem<Sessions>(MATRIX_SESSIONS_KEY, []);
|
||||
const session = sessions.find((s: Session) => s.userId === userId);
|
||||
|
||||
return session?.accessToken;
|
||||
}
|
||||
@@ -308,20 +308,28 @@ export const isAuthenticatedMediaUrl = (url: string): boolean =>
|
||||
* For authenticated media URLs, the access token is required.
|
||||
* Falls back to unauthenticated request if authenticated request fails with 401.
|
||||
* Returns an empty blob with error type if all attempts fail, to prevent cascading failures.
|
||||
*
|
||||
* @param src - The media URL to download
|
||||
* @param accessToken - Optional access token (if not provided, will use current session's token)
|
||||
*/
|
||||
export const downloadMedia = async (src: string, accessToken?: string | null): Promise<Blob> => {
|
||||
// Import here to avoid circular dependencies
|
||||
const { getCurrentAccessToken } = await import('./auth');
|
||||
|
||||
// Use provided token, or fall back to current session's token
|
||||
const token = accessToken ?? getCurrentAccessToken();
|
||||
const needsAuth = isAuthenticatedMediaUrl(src);
|
||||
const headers: HeadersInit = {};
|
||||
|
||||
if (needsAuth && accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`;
|
||||
if (needsAuth && token) {
|
||||
headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
try {
|
||||
let res = await fetch(src, { method: 'GET', headers });
|
||||
|
||||
// If we got a 401 and we tried with auth, fallback to unauthenticated request
|
||||
if (!res.ok && res.status === 401 && needsAuth && accessToken) {
|
||||
if (!res.ok && res.status === 401 && needsAuth && token) {
|
||||
console.warn('[downloadMedia] Auth failed (401), attempting unauthenticated fallback for:', src);
|
||||
res = await fetch(src, { method: 'GET' });
|
||||
}
|
||||
|
||||
@@ -279,6 +279,11 @@ export const initClient = async (session: Session): Promise<MatrixClient> => {
|
||||
// Setup handler to detect and auto-fix one-time key conflicts
|
||||
setupKeyConflictHandler(mx, session);
|
||||
|
||||
// Register this as the global MatrixClient so auth utils can access the latest token
|
||||
const { setGlobalMatrixClient } = await import('../app/utils/auth');
|
||||
setGlobalMatrixClient(mx);
|
||||
console.log('[initMatrix] Registered global MatrixClient for auth utils');
|
||||
|
||||
return mx;
|
||||
} catch (error: any) {
|
||||
// Handle crypto store account mismatch error
|
||||
@@ -361,6 +366,11 @@ export const fixKeyConflict = async (mx: MatrixClient): Promise<void> => {
|
||||
|
||||
export const logoutClient = async (mx: MatrixClient) => {
|
||||
mx.stopClient();
|
||||
|
||||
// Clear the global MatrixClient reference
|
||||
const { setGlobalMatrixClient } = await import('../app/utils/auth');
|
||||
setGlobalMatrixClient(null);
|
||||
|
||||
try {
|
||||
await mx.logout();
|
||||
} catch {
|
||||
|
||||
@@ -32,33 +32,18 @@ if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register(swUrl);
|
||||
navigator.serviceWorker.addEventListener('message', (event) => {
|
||||
if (event.data?.type === 'token' && event.data?.responseKey) {
|
||||
// Get access token from the current session
|
||||
let token: string | undefined;
|
||||
// Use the centralized token utility to get the current access token
|
||||
// This ensures we always use the most up-to-date token from the MatrixClient
|
||||
const getCurrentAccessToken = async () => {
|
||||
const { getCurrentAccessToken: getToken } = await import('./app/utils/auth');
|
||||
return getToken();
|
||||
};
|
||||
|
||||
// First check the new multi-account session storage
|
||||
const sessionsJson = localStorage.getItem('matrixSessions');
|
||||
const currentUserId = localStorage.getItem('currentSessionUserId');
|
||||
|
||||
if (sessionsJson && currentUserId) {
|
||||
try {
|
||||
const sessions = JSON.parse(sessionsJson);
|
||||
const currentSession = sessions.find((s: { userId: string }) => s.userId === currentUserId);
|
||||
if (currentSession?.accessToken) {
|
||||
token = currentSession.accessToken;
|
||||
}
|
||||
} catch {
|
||||
// JSON parse failed, fall through to fallback
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to old single-account storage (for migration)
|
||||
if (!token) {
|
||||
token = localStorage.getItem('cinny_access_token') ?? undefined;
|
||||
}
|
||||
|
||||
event.source!.postMessage({
|
||||
responseKey: event.data.responseKey,
|
||||
token,
|
||||
getCurrentAccessToken().then(token => {
|
||||
event.source!.postMessage({
|
||||
responseKey: event.data.responseKey,
|
||||
token,
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user