2022-05-04 17:09:48 +01:00
|
|
|
/*
|
2023-01-03 16:55:26 +00:00
|
|
|
Copyright 2022 New Vector Ltd
|
2022-05-04 17:09:48 +01:00
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
|
limitations under the License.
|
|
|
|
|
*/
|
|
|
|
|
|
2023-09-27 18:26:16 -04:00
|
|
|
import { ComponentProps, useCallback, useEffect, useState } from "react";
|
2023-06-30 16:43:28 +01:00
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
|
|
|
// @ts-ignore
|
2022-04-07 14:22:36 -07:00
|
|
|
import pako from "pako";
|
2022-08-12 16:46:53 -04:00
|
|
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
2023-06-30 16:43:28 +01:00
|
|
|
import { ClientEvent } from "matrix-js-sdk/src/client";
|
2023-09-25 18:04:34 +01:00
|
|
|
import { logger } from "matrix-js-sdk/src/logger";
|
2022-06-06 22:42:48 +02:00
|
|
|
|
|
|
|
|
import { getLogsForReport } from "./rageshake";
|
2022-04-07 14:22:36 -07:00
|
|
|
import { useClient } from "../ClientContext";
|
2022-11-03 19:43:41 +01:00
|
|
|
import { Config } from "../config/Config";
|
2023-04-11 01:09:52 -04:00
|
|
|
import { ElementCallOpenTelemetry } from "../otel/otel";
|
2023-09-17 14:35:35 -04:00
|
|
|
import { RageshakeRequestModal } from "../room/RageshakeRequestModal";
|
2023-04-11 01:09:52 -04:00
|
|
|
|
|
|
|
|
const gzip = (text: string): Blob => {
|
|
|
|
|
// encode as UTF-8
|
|
|
|
|
const buf = new TextEncoder().encode(text);
|
|
|
|
|
// compress
|
|
|
|
|
return new Blob([pako.gzip(buf)]);
|
|
|
|
|
};
|
2022-04-07 14:22:36 -07:00
|
|
|
|
2022-06-06 22:42:48 +02:00
|
|
|
interface RageShakeSubmitOptions {
|
|
|
|
|
sendLogs: boolean;
|
2022-07-30 10:06:28 +02:00
|
|
|
rageshakeRequestId?: string;
|
2022-08-02 00:46:16 +02:00
|
|
|
description?: string;
|
|
|
|
|
roomId?: string;
|
|
|
|
|
label?: string;
|
2022-06-06 22:42:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useSubmitRageshake(): {
|
|
|
|
|
submitRageshake: (opts: RageShakeSubmitOptions) => Promise<void>;
|
|
|
|
|
sending: boolean;
|
|
|
|
|
sent: boolean;
|
2023-06-30 16:43:28 +01:00
|
|
|
error?: Error;
|
2022-06-06 22:42:48 +02:00
|
|
|
} {
|
2023-06-30 16:43:28 +01:00
|
|
|
const { client } = useClient();
|
|
|
|
|
|
|
|
|
|
const [{ sending, sent, error }, setState] = useState<{
|
|
|
|
|
sending: boolean;
|
|
|
|
|
sent: boolean;
|
|
|
|
|
error?: Error;
|
|
|
|
|
}>({
|
2022-04-07 14:22:36 -07:00
|
|
|
sending: false,
|
|
|
|
|
sent: false,
|
2023-06-30 16:43:28 +01:00
|
|
|
error: undefined,
|
2022-04-07 14:22:36 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const submitRageshake = useCallback(
|
2023-06-30 16:43:28 +01:00
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
|
|
|
// @ts-ignore
|
2022-04-07 14:22:36 -07:00
|
|
|
async (opts) => {
|
2022-12-21 10:17:53 +00:00
|
|
|
if (!Config.get().rageshake?.submit_url) {
|
2022-12-21 09:42:27 +00:00
|
|
|
throw new Error("No rageshake URL is configured");
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-07 14:22:36 -07:00
|
|
|
if (sending) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
2023-06-30 16:43:28 +01:00
|
|
|
setState({ sending: true, sent: false, error: undefined });
|
2022-04-07 14:22:36 -07:00
|
|
|
|
|
|
|
|
let userAgent = "UNKNOWN";
|
|
|
|
|
if (window.navigator && window.navigator.userAgent) {
|
|
|
|
|
userAgent = window.navigator.userAgent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let touchInput = "UNKNOWN";
|
|
|
|
|
try {
|
|
|
|
|
// MDN claims broad support across browsers
|
|
|
|
|
touchInput = String(window.matchMedia("(pointer: coarse)").matches);
|
|
|
|
|
} catch (e) {}
|
|
|
|
|
|
2022-11-17 16:06:41 +00:00
|
|
|
let description = opts.rageshakeRequestId
|
|
|
|
|
? `Rageshake ${opts.rageshakeRequestId}`
|
|
|
|
|
: "";
|
|
|
|
|
if (opts.description) description += `: ${opts.description}`;
|
|
|
|
|
|
2022-04-07 14:22:36 -07:00
|
|
|
const body = new FormData();
|
|
|
|
|
body.append(
|
|
|
|
|
"text",
|
2022-11-17 16:06:41 +00:00
|
|
|
description ?? "User did not supply any additional text."
|
2022-04-07 14:22:36 -07:00
|
|
|
);
|
|
|
|
|
body.append("app", "matrix-video-chat");
|
2022-06-06 22:42:48 +02:00
|
|
|
body.append(
|
|
|
|
|
"version",
|
|
|
|
|
(import.meta.env.VITE_APP_VERSION as string) || "dev"
|
|
|
|
|
);
|
2022-04-07 14:22:36 -07:00
|
|
|
body.append("user_agent", userAgent);
|
2022-06-06 22:42:48 +02:00
|
|
|
body.append("installed_pwa", "false");
|
2022-04-07 14:22:36 -07:00
|
|
|
body.append("touch_input", touchInput);
|
2023-06-23 14:47:32 -04:00
|
|
|
body.append("call_backend", "livekit");
|
2022-04-07 14:22:36 -07:00
|
|
|
|
|
|
|
|
if (client) {
|
2023-06-30 16:43:28 +01:00
|
|
|
const userId = client.getUserId()!;
|
2022-04-07 14:22:36 -07:00
|
|
|
const user = client.getUser(userId);
|
2023-06-30 16:43:28 +01:00
|
|
|
body.append("display_name", user?.displayName ?? "");
|
|
|
|
|
body.append("user_id", client.credentials.userId ?? "");
|
|
|
|
|
body.append("device_id", client.deviceId ?? "");
|
2022-04-07 14:22:36 -07:00
|
|
|
|
|
|
|
|
if (opts.roomId) {
|
|
|
|
|
body.append("room_id", opts.roomId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (client.isCryptoEnabled()) {
|
|
|
|
|
const keys = [`ed25519:${client.getDeviceEd25519Key()}`];
|
|
|
|
|
if (client.getDeviceCurve25519Key) {
|
|
|
|
|
keys.push(`curve25519:${client.getDeviceCurve25519Key()}`);
|
|
|
|
|
}
|
|
|
|
|
body.append("device_keys", keys.join(", "));
|
2023-06-30 16:43:28 +01:00
|
|
|
body.append("cross_signing_key", client.getCrossSigningId()!);
|
2022-04-07 14:22:36 -07:00
|
|
|
|
|
|
|
|
// add cross-signing status information
|
2023-06-30 16:43:28 +01:00
|
|
|
const crossSigning = client.crypto!.crossSigningInfo;
|
|
|
|
|
const secretStorage = client.crypto!.secretStorage;
|
2022-04-07 14:22:36 -07:00
|
|
|
|
|
|
|
|
body.append(
|
|
|
|
|
"cross_signing_ready",
|
|
|
|
|
String(await client.isCrossSigningReady())
|
|
|
|
|
);
|
|
|
|
|
body.append(
|
|
|
|
|
"cross_signing_supported_by_hs",
|
|
|
|
|
String(
|
|
|
|
|
await client.doesServerSupportUnstableFeature(
|
|
|
|
|
"org.matrix.e2e_cross_signing"
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
);
|
2023-06-30 16:43:28 +01:00
|
|
|
body.append("cross_signing_key", crossSigning.getId()!);
|
2022-04-07 14:22:36 -07:00
|
|
|
body.append(
|
|
|
|
|
"cross_signing_privkey_in_secret_storage",
|
|
|
|
|
String(
|
|
|
|
|
!!(await crossSigning.isStoredInSecretStorage(secretStorage))
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const pkCache = client.getCrossSigningCacheCallbacks();
|
|
|
|
|
body.append(
|
|
|
|
|
"cross_signing_master_privkey_cached",
|
|
|
|
|
String(
|
2023-06-30 16:43:28 +01:00
|
|
|
!!(
|
|
|
|
|
pkCache?.getCrossSigningKeyCache &&
|
|
|
|
|
(await pkCache.getCrossSigningKeyCache("master"))
|
|
|
|
|
)
|
2022-04-07 14:22:36 -07:00
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
body.append(
|
|
|
|
|
"cross_signing_self_signing_privkey_cached",
|
|
|
|
|
String(
|
|
|
|
|
!!(
|
2023-06-30 16:43:28 +01:00
|
|
|
pkCache?.getCrossSigningKeyCache &&
|
2022-04-07 14:22:36 -07:00
|
|
|
(await pkCache.getCrossSigningKeyCache("self_signing"))
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
body.append(
|
|
|
|
|
"cross_signing_user_signing_privkey_cached",
|
|
|
|
|
String(
|
|
|
|
|
!!(
|
2023-06-30 16:43:28 +01:00
|
|
|
pkCache?.getCrossSigningKeyCache &&
|
2022-04-07 14:22:36 -07:00
|
|
|
(await pkCache.getCrossSigningKeyCache("user_signing"))
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
body.append(
|
|
|
|
|
"secret_storage_ready",
|
|
|
|
|
String(await client.isSecretStorageReady())
|
|
|
|
|
);
|
|
|
|
|
body.append(
|
|
|
|
|
"secret_storage_key_in_account",
|
|
|
|
|
String(!!(await secretStorage.hasKey()))
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
body.append(
|
|
|
|
|
"session_backup_key_in_secret_storage",
|
|
|
|
|
String(!!(await client.isKeyBackupKeyStored()))
|
|
|
|
|
);
|
|
|
|
|
const sessionBackupKeyFromCache =
|
2023-06-30 16:43:28 +01:00
|
|
|
await client.crypto!.getSessionBackupPrivateKey();
|
2022-04-07 14:22:36 -07:00
|
|
|
body.append(
|
|
|
|
|
"session_backup_key_cached",
|
|
|
|
|
String(!!sessionBackupKeyFromCache)
|
|
|
|
|
);
|
|
|
|
|
body.append(
|
|
|
|
|
"session_backup_key_well_formed",
|
|
|
|
|
String(sessionBackupKeyFromCache instanceof Uint8Array)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (opts.label) {
|
|
|
|
|
body.append("label", opts.label);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// add storage persistence/quota information
|
|
|
|
|
if (navigator.storage && navigator.storage.persisted) {
|
|
|
|
|
try {
|
|
|
|
|
body.append(
|
|
|
|
|
"storageManager_persisted",
|
|
|
|
|
String(await navigator.storage.persisted())
|
|
|
|
|
);
|
|
|
|
|
} catch (e) {}
|
|
|
|
|
} else if (document.hasStorageAccess) {
|
|
|
|
|
// Safari
|
|
|
|
|
try {
|
|
|
|
|
body.append(
|
|
|
|
|
"storageManager_persisted",
|
|
|
|
|
String(await document.hasStorageAccess())
|
|
|
|
|
);
|
|
|
|
|
} catch (e) {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (navigator.storage && navigator.storage.estimate) {
|
|
|
|
|
try {
|
2022-06-06 22:42:48 +02:00
|
|
|
const estimate: {
|
|
|
|
|
quota?: number;
|
|
|
|
|
usage?: number;
|
|
|
|
|
usageDetails?: { [x: string]: unknown };
|
|
|
|
|
} = await navigator.storage.estimate();
|
2022-04-07 14:22:36 -07:00
|
|
|
body.append("storageManager_quota", String(estimate.quota));
|
|
|
|
|
body.append("storageManager_usage", String(estimate.usage));
|
|
|
|
|
if (estimate.usageDetails) {
|
|
|
|
|
Object.keys(estimate.usageDetails).forEach((k) => {
|
|
|
|
|
body.append(
|
|
|
|
|
`storageManager_usage_${k}`,
|
2023-06-30 16:43:28 +01:00
|
|
|
String(estimate.usageDetails![k])
|
2022-04-07 14:22:36 -07:00
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (opts.sendLogs) {
|
|
|
|
|
const logs = await getLogsForReport();
|
|
|
|
|
|
|
|
|
|
for (const entry of logs) {
|
2023-04-11 01:09:52 -04:00
|
|
|
body.append("compressed-log", gzip(entry.lines), entry.id);
|
2022-04-07 14:22:36 -07:00
|
|
|
}
|
|
|
|
|
|
2023-04-11 01:09:52 -04:00
|
|
|
body.append(
|
|
|
|
|
"file",
|
2023-04-12 17:07:18 -04:00
|
|
|
gzip(ElementCallOpenTelemetry.instance.rageshakeProcessor!.dump()),
|
2023-05-03 10:43:46 +01:00
|
|
|
"traces.json.gz"
|
2023-04-11 01:09:52 -04:00
|
|
|
);
|
2022-04-07 14:22:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (opts.rageshakeRequestId) {
|
|
|
|
|
body.append(
|
|
|
|
|
"group_call_rageshake_request_id",
|
|
|
|
|
opts.rageshakeRequestId
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-30 16:43:28 +01:00
|
|
|
await fetch(Config.get().rageshake!.submit_url, {
|
2022-12-21 09:42:27 +00:00
|
|
|
method: "POST",
|
|
|
|
|
body,
|
|
|
|
|
});
|
2022-04-07 14:22:36 -07:00
|
|
|
|
2023-06-30 16:43:28 +01:00
|
|
|
setState({ sending: false, sent: true, error: undefined });
|
2022-04-07 14:22:36 -07:00
|
|
|
} catch (error) {
|
2023-06-30 16:43:28 +01:00
|
|
|
setState({ sending: false, sent: false, error: error as Error });
|
2023-09-25 18:04:34 +01:00
|
|
|
logger.error(error);
|
2022-04-07 14:22:36 -07:00
|
|
|
}
|
|
|
|
|
},
|
2023-09-27 18:26:16 -04:00
|
|
|
[client, sending]
|
2022-04-07 14:22:36 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
submitRageshake,
|
|
|
|
|
sending,
|
|
|
|
|
sent,
|
|
|
|
|
error,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-06 22:42:48 +02:00
|
|
|
export function useRageshakeRequest(): (
|
|
|
|
|
roomId: string,
|
|
|
|
|
rageshakeRequestId: string
|
|
|
|
|
) => void {
|
2022-04-07 14:22:36 -07:00
|
|
|
const { client } = useClient();
|
|
|
|
|
|
|
|
|
|
const sendRageshakeRequest = useCallback(
|
2023-06-30 16:43:28 +01:00
|
|
|
(roomId: string, rageshakeRequestId: string) => {
|
|
|
|
|
client!.sendEvent(roomId, "org.matrix.rageshake_request", {
|
2022-04-07 14:22:36 -07:00
|
|
|
request_id: rageshakeRequestId,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
[client]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return sendRageshakeRequest;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-17 14:35:35 -04:00
|
|
|
export function useRageshakeRequestModal(
|
|
|
|
|
roomId: string
|
|
|
|
|
): ComponentProps<typeof RageshakeRequestModal> {
|
|
|
|
|
const [open, setOpen] = useState(false);
|
|
|
|
|
const onDismiss = useCallback(() => setOpen(false), [setOpen]);
|
2023-06-30 16:43:28 +01:00
|
|
|
const { client } = useClient();
|
2022-06-08 16:36:22 +02:00
|
|
|
const [rageshakeRequestId, setRageshakeRequestId] = useState<string>();
|
2022-04-07 14:22:36 -07:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
2023-06-30 16:43:28 +01:00
|
|
|
if (!client) return;
|
|
|
|
|
|
2022-06-06 22:42:48 +02:00
|
|
|
const onEvent = (event: MatrixEvent) => {
|
2022-04-07 14:22:36 -07:00
|
|
|
const type = event.getType();
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
type === "org.matrix.rageshake_request" &&
|
|
|
|
|
roomId === event.getRoomId() &&
|
|
|
|
|
client.getUserId() !== event.getSender()
|
|
|
|
|
) {
|
|
|
|
|
setRageshakeRequestId(event.getContent().request_id);
|
2023-09-17 14:35:35 -04:00
|
|
|
setOpen(true);
|
2022-04-07 14:22:36 -07:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2022-06-06 22:42:48 +02:00
|
|
|
client.on(ClientEvent.Event, onEvent);
|
2022-04-07 14:22:36 -07:00
|
|
|
|
|
|
|
|
return () => {
|
2022-06-06 22:42:48 +02:00
|
|
|
client.removeListener(ClientEvent.Event, onEvent);
|
2022-04-07 14:22:36 -07:00
|
|
|
};
|
2023-09-17 14:35:35 -04:00
|
|
|
}, [setOpen, roomId, client]);
|
2022-04-07 14:22:36 -07:00
|
|
|
|
2023-06-30 16:43:28 +01:00
|
|
|
return {
|
2023-09-17 14:35:35 -04:00
|
|
|
rageshakeRequestId: rageshakeRequestId ?? "",
|
|
|
|
|
roomId,
|
|
|
|
|
open,
|
|
|
|
|
onDismiss,
|
2023-06-30 16:43:28 +01:00
|
|
|
};
|
2022-04-07 14:22:36 -07:00
|
|
|
}
|