minty/src/app/hooks/useExtendedProfile.ts
2025-09-24 10:44:17 -04:00

112 lines
3.3 KiB
TypeScript

import { useCallback } from 'react';
import z from 'zod';
import { useQuery } from '@tanstack/react-query';
import { Capabilities } from 'matrix-js-sdk';
import { useMatrixClient } from './useMatrixClient';
import { useSpecVersions } from './useSpecVersions';
import { IProfileFieldsCapability } from '../../types/matrix/common';
const extendedProfile = z.looseObject({
displayname: z.string().optional(),
avatar_url: z.string().optional(),
'io.fsky.nyx.pronouns': z
.object({
language: z.string(),
summary: z.string(),
})
.array()
.optional()
.catch(undefined),
'us.cloke.msc4175.tz': z.string().optional().catch(undefined),
});
export type ExtendedProfile = z.infer<typeof extendedProfile>;
export function useExtendedProfileSupported(): boolean {
const { versions, unstable_features: unstableFeatures } = useSpecVersions();
return unstableFeatures?.['uk.tcpip.msc4133'] || versions.includes('v1.15');
}
/// Returns the user's MSC4133 extended profile, if our homeserver supports it.
/// This will return `undefined` while the request is in flight and `null` if the HS lacks support.
export function useExtendedProfile(
userId: string
): [ExtendedProfile | undefined | null, () => Promise<void>] {
const mx = useMatrixClient();
const extendedProfileSupported = useExtendedProfileSupported();
const { data, refetch } = useQuery({
queryKey: ['extended-profile', userId],
queryFn: useCallback(async () => {
if (extendedProfileSupported) {
return extendedProfile.parse(await mx.getExtendedProfile(userId));
}
return null;
}, [mx, userId, extendedProfileSupported]),
refetchOnMount: false,
});
return [
data,
async () => {
await refetch();
},
];
}
const LEGACY_FIELDS = ['displayname', 'avatar_url'];
/// Returns whether the given profile field may be edited by the user.
export function profileEditsAllowed(
field: string,
capabilities: Capabilities,
extendedProfileSupported: boolean
): boolean {
if (LEGACY_FIELDS.includes(field)) {
// this field might have a pre-msc4133 capability. check that first
if (capabilities[`m.set_${field}`]?.enabled === false) {
return false;
}
if (!extendedProfileSupported) {
// the homeserver only supports legacy fields
return true;
}
}
if (extendedProfileSupported) {
// the homeserver has msc4133 support
const extendedProfileCapability = capabilities[
'uk.tcpip.msc4133.profile_fields'
] as IProfileFieldsCapability;
if (extendedProfileCapability === undefined) {
// the capability is missing, assume modification is allowed
return true;
}
if (!extendedProfileCapability.enabled) {
// the capability is set to disable profile modifications
return false;
}
if (
extendedProfileCapability.allowed !== undefined &&
!extendedProfileCapability.allowed.includes(field)
) {
// the capability includes an allowlist and `field` isn't in it
return false;
}
if (extendedProfileCapability.disallowed?.includes(field)) {
// the capability includes an blocklist and `field` is in it
return false;
}
// the capability is enabled and `field` isn't blocked
return true;
}
// `field` is an extended profile key and the homeserver lacks msc4133 support
return false;
}