Files
blap/apps/web/src/hooks/useUserStatus.ts
T
Will Hunt 4bee845010 Show user status in timeline (#32991)
* Use other branch

* All the changes that got lost

* Fix merge

* Ensure emoji can only be one character long

* Fixup labs feature

* Remove redundant check

* Update snapshot

* update snapshot

* add snapshot

* unpin

* fix pnpm lock

* undo pn[m lockfile changes altogether

as we shouldn't actually need any afaik

* update snpahot for changed IDs

* Snapshot update

* Snapshot update

* There is now another section

* more snapshots

* more snapshot

* More snapshots

* oh come on snapshots

* actual snapshot update

* Fix sonar issues

* just update the thing manually

* [screams internally]

* Update snapshot

* test for useUserStatus

* Make useUserStatus actually truncate

* Split out slash command to its own file

& add test

* Remove irrelevant comment

* doc

* Comment on non-obvious error message

---------

Co-authored-by: David Baker <dbkr@users.noreply.github.com>
2026-04-29 08:14:22 +00:00

99 lines
3.5 KiB
TypeScript

/**
Copyright 2026 Element Creations 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 { useEffect, useState } from "react";
import { ClientEvent, MatrixError } from "matrix-js-sdk/src/matrix";
import { logger as rootLogger } from "matrix-js-sdk/src/logger";
import { useMatrixClientContext } from "../contexts/MatrixClientContext";
import { useTypedEventEmitter } from "./useEventEmitter";
import { useFeatureEnabled } from "./useSettings";
const logger = rootLogger.getChild("useUserStatus");
export interface UserStatus {
emoji: string;
text: string;
}
const MAX_STATUS_TEXT_BYTES = 256;
export function userStatusTextWithinMaxLength(text: string): boolean {
const textEncoder = new TextEncoder();
return textEncoder.encode(text).length <= MAX_STATUS_TEXT_BYTES;
}
/**
* Hook to get the MSC4426 user status for a given user ID. Returns undefined if the feature is disabled,
* the user does not have a status, or if there was an error fetching the status.
*
* @param userId The ID of the user whose status is being fetched.
* @returns The user's status, or undefined if not available.
*/
export function useUserStatus(userId: string | undefined): UserStatus | undefined {
const isEnabled = useFeatureEnabled("feature_user_status");
const matrixClient = useMatrixClientContext();
const [rawUserStatus, setRawUserStatus] = useState<unknown>();
useTypedEventEmitter(matrixClient, ClientEvent.UserProfileUpdate, (syncedUserId, syncProfile) => {
if (syncedUserId !== userId) {
return;
}
if (syncProfile["org.matrix.msc4426.status"]) {
setRawUserStatus(syncProfile["org.matrix.msc4426.status"]);
}
});
useEffect(() => {
(async () => {
if (!isEnabled) {
return;
}
if (!userId) {
setRawUserStatus(undefined);
return;
}
if ((await matrixClient.doesServerSupportExtendedProfiles()) === false) {
setRawUserStatus(undefined);
return;
}
try {
const result = await matrixClient.getExtendedProfileProperty(userId, "org.matrix.msc4426.status");
setRawUserStatus(result);
} catch (ex) {
if (ex instanceof MatrixError && ex.errcode === "M_NOT_FOUND") {
setRawUserStatus(undefined);
} else {
logger.warn(`Failed to get userStatus for ${userId}`, ex);
}
}
})();
}, [isEnabled, userId, matrixClient]);
if (!isEnabled) {
return;
}
if (typeof rawUserStatus !== "object" || rawUserStatus === null) {
logger.warn(`value of "org.matrix.msc4426.status" was not an object for ${userId}`);
return;
}
if ("emoji" in rawUserStatus === false || typeof rawUserStatus.emoji !== "string" || !rawUserStatus.emoji) {
logger.warn(`"emoji" property was not a valid string for ${userId}`);
return;
}
if ("text" in rawUserStatus === false || typeof rawUserStatus.text !== "string" || !rawUserStatus.text) {
logger.warn(`"text" property was not a valid string for ${userId}`);
return;
}
return {
emoji: rawUserStatus.emoji,
text: userStatusTextWithinMaxLength(rawUserStatus.text)
? rawUserStatus.text
: `${rawUserStatus.text.slice(0, MAX_STATUS_TEXT_BYTES)}`,
};
}