matrix-react-sdk
Version:
SDK for matrix.org using React
110 lines (91 loc) • 3.64 kB
text/typescript
/*
Copyright 2024 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import BasePlatform from "../../BasePlatform";
import { IConfigOptions } from "../../IConfigOptions";
import { DeepReadonly } from "../../@types/common";
export type DeviceClientInformation = {
name?: string;
version?: string;
url?: string;
};
const formatUrl = (): string | undefined => {
// don't record url for electron clients
if (window.electron) {
return undefined;
}
// strip query-string and fragment from uri
const url = new URL(window.location.href);
return [
url.host,
url.pathname.replace(/\/$/, ""), // Remove trailing slash if present
].join("");
};
const clientInformationEventPrefix = "io.element.matrix_client_information.";
export const getClientInformationEventType = (deviceId: string): string => `${clientInformationEventPrefix}${deviceId}`;
/**
* Record extra client information for the current device
* https://github.com/vector-im/element-meta/blob/develop/spec/matrix_client_information.md
*/
export const recordClientInformation = async (
matrixClient: MatrixClient,
sdkConfig: DeepReadonly<IConfigOptions>,
platform?: BasePlatform,
): Promise<void> => {
const deviceId = matrixClient.getDeviceId()!;
const { brand } = sdkConfig;
const version = await platform?.getAppVersion();
const type = getClientInformationEventType(deviceId);
const url = formatUrl();
await matrixClient.setAccountData(type, {
name: brand,
version,
url,
});
};
/**
* Remove client information events for devices that no longer exist
* @param validDeviceIds - ids of current devices,
* client information for devices NOT in this list will be removed
*/
export const pruneClientInformation = (validDeviceIds: string[], matrixClient: MatrixClient): void => {
Array.from(matrixClient.store.accountData.values()).forEach((event) => {
if (!event.getType().startsWith(clientInformationEventPrefix)) {
return;
}
const [, deviceId] = event.getType().split(clientInformationEventPrefix);
if (deviceId && !validDeviceIds.includes(deviceId)) {
matrixClient.deleteAccountData(event.getType());
}
});
};
/**
* Remove extra client information for current device
*/
export const removeClientInformation = async (matrixClient: MatrixClient): Promise<void> => {
const deviceId = matrixClient.getDeviceId()!;
const type = getClientInformationEventType(deviceId);
const clientInformation = getDeviceClientInformation(matrixClient, deviceId);
// if a non-empty client info event exists, remove it
if (clientInformation.name || clientInformation.version || clientInformation.url) {
await matrixClient.deleteAccountData(type);
}
};
const sanitizeContentString = (value: unknown): string | undefined =>
value && typeof value === "string" ? value : undefined;
export const getDeviceClientInformation = (matrixClient: MatrixClient, deviceId: string): DeviceClientInformation => {
const event = matrixClient.getAccountData(getClientInformationEventType(deviceId));
if (!event) {
return {};
}
const { name, version, url } = event.getContent();
return {
name: sanitizeContentString(name),
version: sanitizeContentString(version),
url: sanitizeContentString(url),
};
};