All checks were successful
Trigger cinny-mobile / dispatch (push) Successful in 1s
Replace legacy profile style metadata with MSC4133 profile fields, add in-app update notes, tighten account profile name spacing, and wire desktop media save helpers through the client.
143 lines
3.6 KiB
TypeScript
143 lines
3.6 KiB
TypeScript
import { useCallback, useEffect, useState } from 'react';
|
|
import { useMatrixClient } from './useMatrixClient';
|
|
import { useMediaAuthentication } from './useMediaAuthentication';
|
|
import { mxcUrlToHttp } from '../utils/matrix';
|
|
import { getCurrentAccessToken } from '../utils/auth';
|
|
import {
|
|
deleteBannerUrl,
|
|
loadBannerUrl,
|
|
registerBannerCacheClear,
|
|
saveBannerUrl,
|
|
} from '../utils/profileFields';
|
|
|
|
/**
|
|
* Hook to manage the user's profile banner (MSC4427 via MSC4133).
|
|
*/
|
|
export function useUserBanner(): [
|
|
string | undefined,
|
|
(banner: string | undefined) => Promise<void>,
|
|
boolean
|
|
] {
|
|
const mx = useMatrixClient();
|
|
const [banner, setBanner] = useState<string | undefined>();
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
const userId = mx.getUserId();
|
|
if (!userId) {
|
|
setLoading(false);
|
|
return undefined;
|
|
}
|
|
|
|
let cancelled = false;
|
|
|
|
const load = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const bannerMxc = await loadBannerUrl(mx, userId);
|
|
if (!cancelled) setBanner(bannerMxc);
|
|
} catch {
|
|
if (!cancelled) setBanner(undefined);
|
|
}
|
|
if (!cancelled) setLoading(false);
|
|
};
|
|
|
|
load();
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [mx]);
|
|
|
|
const updateBanner = useCallback(
|
|
async (newBanner: string | undefined) => {
|
|
if (!newBanner) {
|
|
await deleteBannerUrl(mx);
|
|
setBanner(undefined);
|
|
return;
|
|
}
|
|
|
|
await saveBannerUrl(mx, newBanner);
|
|
setBanner(newBanner);
|
|
},
|
|
[mx]
|
|
);
|
|
|
|
return [banner, updateBanner, loading];
|
|
}
|
|
|
|
const userBannerCache = new Map<string, { bannerMxc: string | undefined; timestamp: number }>();
|
|
const CACHE_TTL_MS = 5 * 60 * 1000;
|
|
|
|
registerBannerCacheClear((userId: string) => {
|
|
userBannerCache.delete(userId);
|
|
});
|
|
|
|
async function fetchBannerBlobUrl(
|
|
mx: ReturnType<typeof useMatrixClient>,
|
|
bannerMxc: string,
|
|
useAuthentication: boolean
|
|
): Promise<string | undefined> {
|
|
const bannerHttpUrl = mxcUrlToHttp(mx, bannerMxc, useAuthentication);
|
|
if (!bannerHttpUrl) return undefined;
|
|
|
|
const accessToken = getCurrentAccessToken();
|
|
const headers: HeadersInit = {};
|
|
if (useAuthentication && accessToken) {
|
|
headers.Authorization = `Bearer ${accessToken}`;
|
|
}
|
|
|
|
const response = await fetch(bannerHttpUrl, { headers });
|
|
if (!response.ok) return undefined;
|
|
|
|
const blob = await response.blob();
|
|
return URL.createObjectURL(blob);
|
|
}
|
|
|
|
/**
|
|
* Hook to get another user's profile banner as a blob URL.
|
|
*/
|
|
export function useOtherUserBanner(userId: string): string | undefined {
|
|
const mx = useMatrixClient();
|
|
const useAuthentication = useMediaAuthentication();
|
|
const [bannerBlobUrl, setBannerBlobUrl] = useState<string | undefined>();
|
|
|
|
useEffect(() => {
|
|
if (!userId) {
|
|
setBannerBlobUrl(undefined);
|
|
return undefined;
|
|
}
|
|
|
|
let blobUrl: string | undefined;
|
|
let cancelled = false;
|
|
|
|
const load = async () => {
|
|
const cached = userBannerCache.get(userId);
|
|
let bannerMxc: string | undefined;
|
|
|
|
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
|
|
bannerMxc = cached.bannerMxc;
|
|
} else {
|
|
bannerMxc = await loadBannerUrl(mx, userId);
|
|
userBannerCache.set(userId, { bannerMxc, timestamp: Date.now() });
|
|
}
|
|
|
|
if (!bannerMxc) {
|
|
if (!cancelled) setBannerBlobUrl(undefined);
|
|
return;
|
|
}
|
|
|
|
blobUrl = await fetchBannerBlobUrl(mx, bannerMxc, useAuthentication);
|
|
if (!cancelled) setBannerBlobUrl(blobUrl);
|
|
};
|
|
|
|
load();
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
if (blobUrl) URL.revokeObjectURL(blobUrl);
|
|
};
|
|
}, [mx, useAuthentication, userId]);
|
|
|
|
return bannerBlobUrl;
|
|
}
|