mirror of
https://github.com/Novaloop-AG/healthchat-web.git
synced 2026-11-02 21:32:46 +00:00
feat: add ability to edit custom user attributes
This commit is contained in:
@@ -323,6 +323,7 @@
|
||||
@import "./views/settings/_UpdateCheckButton.pcss";
|
||||
@import "./views/settings/_UserPersonalInfoSettings.pcss";
|
||||
@import "./views/settings/_UserProfileSettings.pcss";
|
||||
@import "./views/settings/_ExtendedProfileSettings.pcss";
|
||||
@import "./views/settings/encryption/_AdvancedPanel.pcss";
|
||||
@import "./views/settings/encryption/_ChangeRecoveryKey.pcss";
|
||||
@import "./views/settings/encryption/_EncryptionCard.pcss";
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
.mx_ExtendedProfileSettings {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.mx_ExtendedProfileSettings h3 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.mx_ExtendedProfileSettings_fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.mx_ExtendedProfileSettings_field {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mx_ExtendedProfileSettings_fieldsRow {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.mx_ExtendedProfileSettings_field {
|
||||
flex: 1;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_ExtendedProfileSettings_buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: flex-end;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.mx_ExtendedProfileSettings_multiline textarea {
|
||||
min-height: 200px;
|
||||
resize: vertical;
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type ChangeEvent, useCallback, useEffect, useState } from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { Alert, ErrorMessage } from "@vector-im/compound-web";
|
||||
import classNames from "classnames";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { useToastContext } from "../../../contexts/ToastContext";
|
||||
import InlineSpinner from "../elements/InlineSpinner";
|
||||
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
|
||||
import { SdkContextClass } from "../../../contexts/SDKContext";
|
||||
import Field from "../elements/Field";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
|
||||
const SpinnerToast: React.FC<{ children?: React.ReactNode }> = ({ children }) => (
|
||||
<>
|
||||
<InlineSpinner />
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
|
||||
// Extended profile property keys
|
||||
export const EXT_PROFILE_KEY_TITLE = "io.element.profile.title";
|
||||
export const EXT_PROFILE_KEY_SPECIALIZATION = "io.element.profile.specialization";
|
||||
export const EXT_PROFILE_KEY_ADDRESS = "io.element.profile.address";
|
||||
export const EXT_PROFILE_KEY_WEBSITE = "io.element.profile.website";
|
||||
export const EXT_PROFILE_KEY_FURTHER_INFO = "io.element.profile.further_info";
|
||||
|
||||
// Unstable feature key for extended profiles
|
||||
export const UNSTABLE_MSC4133_EXTENDED_PROFILES = "org.matrix.msc4133.extended_profiles";
|
||||
|
||||
interface ExtendedProfileSettingsProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component for managing extended profile fields for medical professionals.
|
||||
* Allows editing title, specialization, address, website, and further information.
|
||||
*/
|
||||
const ExtendedProfileSettings: React.FC<ExtendedProfileSettingsProps> = ({ className }) => {
|
||||
const [title, setTitle] = useState<string>("");
|
||||
const [specialization, setSpecialization] = useState<string>("");
|
||||
const [address, setAddress] = useState<string>("");
|
||||
const [website, setWebsite] = useState<string>("");
|
||||
const [furtherInfo, setFurtherInfo] = useState<string>("");
|
||||
|
||||
// Store original values for cancel functionality
|
||||
const [originalTitle, setOriginalTitle] = useState<string>("");
|
||||
const [originalSpecialization, setOriginalSpecialization] = useState<string>("");
|
||||
const [originalAddress, setOriginalAddress] = useState<string>("");
|
||||
const [originalWebsite, setOriginalWebsite] = useState<string>("");
|
||||
const [originalFurtherInfo, setOriginalFurtherInfo] = useState<string>("");
|
||||
|
||||
const [isModified, setIsModified] = useState<boolean>(false);
|
||||
const [isSaving, setIsSaving] = useState<boolean>(false);
|
||||
const [saveError, setSaveError] = useState<boolean>(false);
|
||||
const [loadError, setLoadError] = useState<boolean>(false);
|
||||
|
||||
const toastRack = useToastContext();
|
||||
const client = useMatrixClientContext();
|
||||
const userProfilesStore = SdkContextClass.instance.userProfilesStore;
|
||||
const userId = client.getSafeUserId();
|
||||
|
||||
// Load extended profile data without checking server support
|
||||
useEffect(() => {
|
||||
const loadExtendedProfileData = async () => {
|
||||
try {
|
||||
setLoadError(false);
|
||||
// Try to get existing data
|
||||
try {
|
||||
const titleValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_TITLE);
|
||||
const specializationValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_SPECIALIZATION);
|
||||
const addressValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_ADDRESS);
|
||||
const websiteValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_WEBSITE);
|
||||
const furtherInfoValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_FURTHER_INFO);
|
||||
|
||||
const titleStr = titleValue as string || "";
|
||||
const specializationStr = specializationValue as string || "";
|
||||
const addressStr = addressValue as string || "";
|
||||
const websiteStr = websiteValue as string || "";
|
||||
const furtherInfoStr = furtherInfoValue as string || "";
|
||||
|
||||
setTitle(titleStr);
|
||||
setSpecialization(specializationStr);
|
||||
setAddress(addressStr);
|
||||
setWebsite(websiteStr);
|
||||
setFurtherInfo(furtherInfoStr);
|
||||
|
||||
// Store original values for cancel functionality
|
||||
setOriginalTitle(titleStr);
|
||||
setOriginalSpecialization(specializationStr);
|
||||
setOriginalAddress(addressStr);
|
||||
setOriginalWebsite(websiteStr);
|
||||
setOriginalFurtherInfo(furtherInfoStr);
|
||||
} catch (e) {
|
||||
// Just log the error but don't set loadError to true
|
||||
// as we want the fields to show up even when empty
|
||||
logger.warn("Failed to fetch extended profile properties", e);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error("Failed to load extended profile data", e);
|
||||
setLoadError(true);
|
||||
}
|
||||
};
|
||||
|
||||
loadExtendedProfileData();
|
||||
}, [userId, userProfilesStore, client]);
|
||||
|
||||
// Check if any field has been modified
|
||||
useEffect(() => {
|
||||
const hasChanges =
|
||||
title !== originalTitle ||
|
||||
specialization !== originalSpecialization ||
|
||||
address !== originalAddress ||
|
||||
website !== originalWebsite ||
|
||||
furtherInfo !== originalFurtherInfo;
|
||||
|
||||
setIsModified(hasChanges);
|
||||
}, [title, specialization, address, website, furtherInfo,
|
||||
originalTitle, originalSpecialization, originalAddress, originalWebsite, originalFurtherInfo]);
|
||||
|
||||
// Field change handlers
|
||||
const onTitleChanged = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
setTitle(e.target.value);
|
||||
}, []);
|
||||
|
||||
const onSpecializationChanged = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
setSpecialization(e.target.value);
|
||||
}, []);
|
||||
|
||||
const onAddressChanged = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
setAddress(e.target.value);
|
||||
}, []);
|
||||
|
||||
const onWebsiteChanged = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
setWebsite(e.target.value);
|
||||
}, []);
|
||||
|
||||
const onFurtherInfoChanged = useCallback((e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setFurtherInfo(e.target.value);
|
||||
}, []);
|
||||
|
||||
// Cancel handler - restore all original values
|
||||
const onCancel = useCallback(() => {
|
||||
setTitle(originalTitle);
|
||||
setSpecialization(originalSpecialization);
|
||||
setAddress(originalAddress);
|
||||
setWebsite(originalWebsite);
|
||||
setFurtherInfo(originalFurtherInfo);
|
||||
}, [originalTitle, originalSpecialization, originalAddress, originalWebsite, originalFurtherInfo]);
|
||||
|
||||
// Save handler - save all fields at once
|
||||
const onSave = useCallback(async (): Promise<void> => {
|
||||
if (!isModified) return;
|
||||
|
||||
setIsSaving(true);
|
||||
setSaveError(false);
|
||||
const removeToast = toastRack.displayToast(
|
||||
<SpinnerToast>{_t("settings|extended_profile_settings|saving_profile")}</SpinnerToast>
|
||||
);
|
||||
|
||||
try {
|
||||
// Save all modified fields
|
||||
const savePromises = [];
|
||||
|
||||
if (title !== originalTitle) {
|
||||
savePromises.push(userProfilesStore.setExtendedProfileProperty(EXT_PROFILE_KEY_TITLE, title));
|
||||
}
|
||||
|
||||
if (specialization !== originalSpecialization) {
|
||||
savePromises.push(userProfilesStore.setExtendedProfileProperty(EXT_PROFILE_KEY_SPECIALIZATION, specialization));
|
||||
}
|
||||
|
||||
if (address !== originalAddress) {
|
||||
savePromises.push(userProfilesStore.setExtendedProfileProperty(EXT_PROFILE_KEY_ADDRESS, address));
|
||||
}
|
||||
|
||||
if (website !== originalWebsite) {
|
||||
savePromises.push(userProfilesStore.setExtendedProfileProperty(EXT_PROFILE_KEY_WEBSITE, website));
|
||||
}
|
||||
|
||||
if (furtherInfo !== originalFurtherInfo) {
|
||||
savePromises.push(userProfilesStore.setExtendedProfileProperty(EXT_PROFILE_KEY_FURTHER_INFO, furtherInfo));
|
||||
}
|
||||
|
||||
await Promise.all(savePromises);
|
||||
|
||||
// Update original values after successful save
|
||||
setOriginalTitle(title);
|
||||
setOriginalSpecialization(specialization);
|
||||
setOriginalAddress(address);
|
||||
setOriginalWebsite(website);
|
||||
setOriginalFurtherInfo(furtherInfo);
|
||||
|
||||
} catch (e) {
|
||||
logger.error("Failed to save profile information", e);
|
||||
setSaveError(true);
|
||||
throw e;
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
removeToast();
|
||||
}
|
||||
}, [
|
||||
isModified, title, specialization, address, website, furtherInfo,
|
||||
originalTitle, originalSpecialization, originalAddress, originalWebsite, originalFurtherInfo,
|
||||
toastRack, userProfilesStore
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className={classNames("mx_ExtendedProfileSettings", className)}>
|
||||
<div className="mx_ExtendedProfileSettings_section">
|
||||
<h3 className="mx_ExtendedProfileSettings_heading">{_t("settings|extended_profile_settings|additional_information")}</h3>
|
||||
|
||||
{loadError && (
|
||||
<Alert title={_t("common|error")} type="critical">
|
||||
{_t("settings|extended_profile_settings|failed_to_load")}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<div className="mx_ExtendedProfileSettings_fields">
|
||||
<div className="mx_ExtendedProfileSettings_fieldsRow">
|
||||
<div className="mx_ExtendedProfileSettings_field mx_ExtendedProfileSettings_field_title">
|
||||
<Field
|
||||
label={_t("settings|extended_profile_settings|professional_title")}
|
||||
value={title}
|
||||
onChange={onTitleChanged}
|
||||
disabled={isSaving}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mx_ExtendedProfileSettings_field mx_ExtendedProfileSettings_field_specialization">
|
||||
<Field
|
||||
label={_t("settings|extended_profile_settings|specialization")}
|
||||
value={specialization}
|
||||
onChange={onSpecializationChanged}
|
||||
disabled={isSaving}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx_ExtendedProfileSettings_field">
|
||||
<Field
|
||||
label={_t("settings|extended_profile_settings|address")}
|
||||
value={address}
|
||||
onChange={onAddressChanged}
|
||||
disabled={isSaving}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mx_ExtendedProfileSettings_field">
|
||||
<Field
|
||||
label={_t("settings|extended_profile_settings|website")}
|
||||
value={website}
|
||||
onChange={onWebsiteChanged}
|
||||
disabled={isSaving}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mx_ExtendedProfileSettings_field">
|
||||
<Field
|
||||
className="mx_ExtendedProfileSettings_multiline"
|
||||
label={_t("settings|extended_profile_settings|further_information")}
|
||||
value={furtherInfo}
|
||||
onChange={onFurtherInfoChanged}
|
||||
disabled={isSaving}
|
||||
element="textarea"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Single set of buttons for all fields */}
|
||||
<div className="mx_ExtendedProfileSettings_buttons">
|
||||
<AccessibleButton onClick={onCancel} kind="primary_outline" disabled={isSaving || !isModified}>
|
||||
{_t("common|cancel")}
|
||||
</AccessibleButton>
|
||||
<AccessibleButton onClick={onSave} kind="primary" disabled={isSaving || !isModified}>
|
||||
{_t("common|save")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{saveError && (
|
||||
<div className="mx_ExtendedProfileSettings_error">
|
||||
<ErrorMessage>{_t("settings|extended_profile_settings|failed_to_save")}</ErrorMessage>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExtendedProfileSettings;
|
||||
@@ -28,6 +28,7 @@ import LogoutDialog, { shouldShowLogoutDialog } from "../dialogs/LogoutDialog";
|
||||
import Modal from "../../../Modal";
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import { SettingsSection } from "./shared/SettingsSection.tsx";
|
||||
import ExtendedProfileSettings from "./ExtendedProfileSettings";
|
||||
|
||||
const SpinnerToast: React.FC<{ children?: ReactNode }> = ({ children }) => (
|
||||
<>
|
||||
@@ -237,6 +238,10 @@ const UserProfileSettings: React.FC<UserProfileSettingsProps> = ({
|
||||
</Alert>
|
||||
)}
|
||||
{userIdentifier && <UsernameBox username={userIdentifier} />}
|
||||
|
||||
{/* Always display the Extended Profile Settings component */}
|
||||
<ExtendedProfileSettings className="mx_UserProfileSettings_extendedProfile" />
|
||||
|
||||
<Flex gap="var(--cpd-space-4x)" className="mx_UserProfileSettings_profile_buttons">
|
||||
{externalAccountManagementUrl && (
|
||||
<ManageAccountButton externalAccountManagementUrl={externalAccountManagementUrl} />
|
||||
|
||||
@@ -2441,6 +2441,18 @@
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"extended_profile_settings": {
|
||||
"additional_information": "Zusätzliche Informationen",
|
||||
"professional_title": "Titel",
|
||||
"specialization": "Fachgebiet",
|
||||
"address": "Adresse",
|
||||
"website": "Webseite",
|
||||
"further_information": "Weitere Informationen / Qualifikationen",
|
||||
"saving_profile": "Profil wird gespeichert...",
|
||||
"failed_to_load": "Fehler beim Laden der erweiterten Profileinstellungen.",
|
||||
"failed_to_save": "Fehler beim Speichern der erweiterten Profileinstellungen.",
|
||||
"server_not_supported": "Dieser Server unterstützt keine erweiterten Profileinstellungen."
|
||||
},
|
||||
"account": {
|
||||
"dialog_title": "<strong>Einstellungen:</strong> Konto",
|
||||
"title": "Konto"
|
||||
|
||||
@@ -2484,6 +2484,18 @@
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"extended_profile_settings": {
|
||||
"additional_information": "Additional Information",
|
||||
"professional_title": "Title",
|
||||
"specialization": "Specialization",
|
||||
"address": "Address",
|
||||
"website": "Website",
|
||||
"further_information": "Further Information / Qualifications",
|
||||
"saving_profile": "Saving profile...",
|
||||
"failed_to_load": "Failed to load extended profile settings.",
|
||||
"failed_to_save": "Failed to save extended profile settings.",
|
||||
"server_not_supported": "Your server does not support extended profile settings."
|
||||
},
|
||||
"account": {
|
||||
"dialog_title": "<strong>Settings:</strong> Account",
|
||||
"title": "Account"
|
||||
|
||||
@@ -2488,6 +2488,18 @@
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"extended_profile_settings": {
|
||||
"additional_information": "Informations supplémentaires",
|
||||
"professional_title": "Titre",
|
||||
"specialization": "Spécialisation",
|
||||
"address": "Adresse",
|
||||
"website": "Site web",
|
||||
"further_information": "Informations supplémentaires / qualifications",
|
||||
"saving_profile": "Enregistrement des paramètres de profil étendus…",
|
||||
"failed_to_load": "Erreur lors du chargement des paramètres de profil étendus.",
|
||||
"failed_to_save": "Erreur lors de l'enregistrement des paramètres de profil étendus.",
|
||||
"server_not_supported": "Le serveur d’accueil ne prend pas en charge les paramètres de profil étendus."
|
||||
},
|
||||
"account": {
|
||||
"dialog_title": "<strong>Paramètres : </strong> Compte",
|
||||
"title": "Compte"
|
||||
|
||||
@@ -2119,6 +2119,18 @@
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"extended_profile_settings": {
|
||||
"additional_information": "Informazioni aggiuntive",
|
||||
"professional_title": "Titolo",
|
||||
"specialization": "Specializzazione",
|
||||
"address": "Indirizzo",
|
||||
"website": "Sito web",
|
||||
"further_information": "Ulteriori informazioni / dettagli",
|
||||
"saving_profile": "Salvataggio del profilo esteso...",
|
||||
"failed_to_load": "Impossibile caricare le impostazioni del profilo esteso.",
|
||||
"failed_to_save": "Impossibile salvare le impostazioni del profilo esteso.",
|
||||
"server_not_supported": "Il tuo server non supporta le impostazioni del profilo esteso."
|
||||
},
|
||||
"all_rooms_home": "Mostra tutte le stanze nella pagina principale",
|
||||
"all_rooms_home_description": "Tutte le stanze in cui sei appariranno nella pagina principale.",
|
||||
"always_show_message_timestamps": "Mostra sempre l'orario dei messaggi",
|
||||
|
||||
@@ -20,6 +20,7 @@ import { LruCache } from "../utils/LruCache";
|
||||
const cacheSize = 500;
|
||||
|
||||
type StoreProfileValue = IMatrixProfile | undefined | null;
|
||||
type ExtendedProfileValue = string | number | boolean | object | null;
|
||||
|
||||
interface GetOptions {
|
||||
/** Whether calling the function shouuld raise an Error. */
|
||||
@@ -33,6 +34,8 @@ interface GetOptions {
|
||||
export class UserProfilesStore {
|
||||
private profiles = new LruCache<string, IMatrixProfile | null>(cacheSize);
|
||||
private knownProfiles = new LruCache<string, IMatrixProfile | null>(cacheSize);
|
||||
private extendedProfilesSupport = new Map<string, boolean>();
|
||||
private extendedProfiles = new Map<string, Map<string, ExtendedProfileValue>>();
|
||||
|
||||
public constructor(private client: MatrixClient) {
|
||||
client.on(RoomMemberEvent.Membership, this.onRoomMembershipEvent);
|
||||
@@ -115,9 +118,155 @@ export class UserProfilesStore {
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a server supports extended profiles for a user.
|
||||
* Caches the result.
|
||||
*
|
||||
* @param userId - User Id to check
|
||||
* @returns true if the server supports extended profiles, false otherwise
|
||||
*/
|
||||
public async doesServerSupportExtendedProfiles(userId: string): Promise<boolean> {
|
||||
const serverId = userId.split(":")[1];
|
||||
if (this.extendedProfilesSupport.has(serverId)) {
|
||||
return this.extendedProfilesSupport.get(serverId) ?? false;
|
||||
}
|
||||
|
||||
try {
|
||||
const supported = await this.client.doesServerSupportExtendedProfiles();
|
||||
this.extendedProfilesSupport.set(serverId, supported);
|
||||
return supported;
|
||||
} catch (e) {
|
||||
logger.warn(`Error checking extended profiles support for serverId ${serverId}`, e);
|
||||
this.extendedProfilesSupport.set(serverId, false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an extended profile property from cache if available.
|
||||
*
|
||||
* @param userId - User Id for which to get the extended profile property
|
||||
* @param property - The property key to get
|
||||
* @returns The property value if cached, undefined if not cached
|
||||
*/
|
||||
public getExtendedProfileProperty(userId: string, property: string): ExtendedProfileValue | undefined {
|
||||
const userExtendedProfile = this.extendedProfiles.get(userId);
|
||||
if (!userExtendedProfile) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return userExtendedProfile.get(property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or fetch an extended profile property.
|
||||
*
|
||||
* @param userId - User Id for which to get the extended profile property
|
||||
* @param property - The property key to get
|
||||
* @returns The property value, null if it doesn't exist
|
||||
*/
|
||||
public async getOrFetchExtendedProfileProperty(userId: string, property: string): Promise<ExtendedProfileValue | null> {
|
||||
const cached = this.getExtendedProfileProperty(userId, property);
|
||||
if (cached !== undefined) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
return await this.fetchExtendedProfileProperty(userId, property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch an extended profile property from the API.
|
||||
* Caches the result.
|
||||
*
|
||||
* @param userId - User Id for which to fetch the extended profile property
|
||||
* @param property - The property key to fetch
|
||||
* @returns The property value, null if it doesn't exist or there was an error
|
||||
*/
|
||||
public async fetchExtendedProfileProperty(userId: string, property: string): Promise<ExtendedProfileValue | null> {
|
||||
// Removed server support check - assume support is always available
|
||||
try {
|
||||
const rawValue = await this.client.getExtendedProfileProperty(userId, property);
|
||||
// Ensure the value is of type ExtendedProfileValue
|
||||
const value: ExtendedProfileValue = this.validateExtendedProfileValue(rawValue);
|
||||
// Cache the value
|
||||
if (!this.extendedProfiles.has(userId)) {
|
||||
this.extendedProfiles.set(userId, new Map());
|
||||
}
|
||||
this.extendedProfiles.get(userId)?.set(property, value);
|
||||
|
||||
return value;
|
||||
} catch (e) {
|
||||
logger.warn(`Error retrieving extended profile property ${property} for userId ${userId}`, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Hilfsmethode, um den Rückgabewert zu validieren
|
||||
private validateExtendedProfileValue(value: unknown): ExtendedProfileValue {
|
||||
if (value === null ||
|
||||
typeof value === "string" ||
|
||||
typeof value === "number" ||
|
||||
typeof value === "boolean" ||
|
||||
(typeof value === "object" && value !== null)) {
|
||||
return value as ExtendedProfileValue;
|
||||
}
|
||||
|
||||
// Fallback für ungültige Werte
|
||||
logger.warn(`Invalid extended profile value type: ${typeof value}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an extended profile property for the current user.
|
||||
*
|
||||
* @param property - The property key to set
|
||||
* @param value - The value to set
|
||||
* @returns true if successful, false otherwise
|
||||
*/
|
||||
public async setExtendedProfileProperty(property: string, value: ExtendedProfileValue): Promise<boolean> {
|
||||
try {
|
||||
await this.client.setExtendedProfileProperty(property, value);
|
||||
|
||||
const userId = this.client.getSafeUserId();
|
||||
if (!this.extendedProfiles.has(userId)) {
|
||||
this.extendedProfiles.set(userId, new Map());
|
||||
}
|
||||
this.extendedProfiles.get(userId)?.set(property, value);
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
logger.warn(`Error setting extended profile property ${property}`, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an extended profile property for the current user.
|
||||
*
|
||||
* @param property - The property key to delete
|
||||
* @returns true if successful, false otherwise
|
||||
*/
|
||||
public async deleteExtendedProfileProperty(property: string): Promise<boolean> {
|
||||
try {
|
||||
await this.client.deleteExtendedProfileProperty(property);
|
||||
|
||||
const userId = this.client.getSafeUserId();
|
||||
if (this.extendedProfiles.has(userId)) {
|
||||
this.extendedProfiles.get(userId)?.delete(property);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
logger.warn(`Error deleting extended profile property ${property}`, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public flush(): void {
|
||||
this.profiles = new LruCache<string, IMatrixProfile | null>(cacheSize);
|
||||
this.knownProfiles = new LruCache<string, IMatrixProfile | null>(cacheSize);
|
||||
this.extendedProfilesSupport = new Map<string, boolean>();
|
||||
this.extendedProfiles = new Map<string, Map<string, ExtendedProfileValue>>();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user