From 497b38b609a66a424f868539fe41ff7a41a1b8d0 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 19 Aug 2024 16:47:27 +0100 Subject: [PATCH 1/3] Prototyping for to-device key distribution Show participant ID and some encryption status message Allow encryption system to be chosen at point of room creation Send cryptoVersion platform data to Posthog Send key distribution stats to posthog Send encryption type for CallStarted and CallEnded events Update js-sdk --- src/analytics/PosthogAnalytics.ts | 4 ++ src/analytics/PosthogEvents.ts | 48 +++++++++++++++++++++- src/home/RegisteredView.tsx | 45 +++++++++++++++------ src/home/UnauthenticatedView.tsx | 53 +++++++++++++++++++------ src/room/GroupCallView.tsx | 18 +++++---- src/rtcSessionHelper.test.ts | 3 +- src/rtcSessionHelpers.ts | 10 +++-- src/state/CallViewModel.ts | 42 +++++++++++++++++--- src/state/MediaViewModel.ts | 66 +++++++++++++++++++++++++++++-- src/tile/GridTile.tsx | 12 ++++++ src/tile/MediaView.module.css | 20 +++++++++- src/tile/MediaView.tsx | 27 +++++++++++-- src/tile/SpotlightTile.tsx | 18 +++++++++ src/utils/matrix.ts | 27 +++++++++++-- 14 files changed, 337 insertions(+), 56 deletions(-) diff --git a/src/analytics/PosthogAnalytics.ts b/src/analytics/PosthogAnalytics.ts index 05979a89..ca0df15f 100644 --- a/src/analytics/PosthogAnalytics.ts +++ b/src/analytics/PosthogAnalytics.ts @@ -73,6 +73,7 @@ interface PlatformProperties { appVersion: string; matrixBackend: "embedded" | "jssdk"; callBackend: "livekit" | "full-mesh"; + cryptoVersion?: string; } interface PosthogSettings { @@ -193,6 +194,9 @@ export class PosthogAnalytics { appVersion, matrixBackend: widget ? "embedded" : "jssdk", callBackend: "livekit", + cryptoVersion: widget + ? undefined + : window.matrixclient.getCrypto()?.getVersion(), }; } diff --git a/src/analytics/PosthogEvents.ts b/src/analytics/PosthogEvents.ts index 778392ba..1776b4ed 100644 --- a/src/analytics/PosthogEvents.ts +++ b/src/analytics/PosthogEvents.ts @@ -16,19 +16,40 @@ limitations under the License. import { DisconnectReason } from "livekit-client"; import { logger } from "matrix-js-sdk/src/logger"; +import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc"; import { IPosthogEvent, PosthogAnalytics, RegistrationType, } from "./PosthogAnalytics"; +import { E2eeType } from "../e2ee/e2eeType"; +type EncryptionScheme = "none" | "shared" | "per_sender"; + +function mapE2eeType(type: E2eeType): EncryptionScheme { + switch (type) { + case E2eeType.NONE: + return "none"; + case E2eeType.SHARED_KEY: + return "shared"; + case E2eeType.PER_PARTICIPANT: + return "per_sender"; + } +} interface CallEnded extends IPosthogEvent { eventName: "CallEnded"; callId: string; callParticipantsOnLeave: number; callParticipantsMax: number; callDuration: number; + encryption: EncryptionScheme; + toDeviceEncryptionKeysSent: number; + toDeviceEncryptionKeysReceived: number; + toDeviceEncryptionKeysReceivedAverageAge: number; + roomEventEncryptionKeysSent: number; + roomEventEncryptionKeysReceived: number; + roomEventEncryptionKeysReceivedAverageAge: number; } export class CallEndedTracker { @@ -51,6 +72,8 @@ export class CallEndedTracker { public track( callId: string, callParticipantsNow: number, + e2eeType: E2eeType, + rtcSession: MatrixRTCSession, sendInstantly: boolean, ): void { PosthogAnalytics.instance.trackEvent( @@ -60,6 +83,27 @@ export class CallEndedTracker { callParticipantsMax: this.cache.maxParticipantsCount, callParticipantsOnLeave: callParticipantsNow, callDuration: (Date.now() - this.cache.startTime.getTime()) / 1000, + encryption: mapE2eeType(e2eeType), + toDeviceEncryptionKeysSent: + rtcSession.statistics.counters.toDeviceEncryptionKeysSent, + toDeviceEncryptionKeysReceived: + rtcSession.statistics.counters.toDeviceEncryptionKeysReceived, + toDeviceEncryptionKeysReceivedAverageAge: + rtcSession.statistics.counters.toDeviceEncryptionKeysReceived > 0 + ? rtcSession.statistics.totals + .toDeviceEncryptionKeysReceivedTotalAge / + rtcSession.statistics.counters.toDeviceEncryptionKeysReceived + : 0, + roomEventEncryptionKeysSent: + rtcSession.statistics.counters.roomEventEncryptionKeysSent, + roomEventEncryptionKeysReceived: + rtcSession.statistics.counters.roomEventEncryptionKeysReceived, + roomEventEncryptionKeysReceivedAverageAge: + rtcSession.statistics.counters.roomEventEncryptionKeysReceived > 0 + ? rtcSession.statistics.totals + .roomEventEncryptionKeysReceivedTotalAge / + rtcSession.statistics.counters.roomEventEncryptionKeysReceived + : 0, }, { send_instantly: sendInstantly }, ); @@ -69,13 +113,15 @@ export class CallEndedTracker { interface CallStarted extends IPosthogEvent { eventName: "CallStarted"; callId: string; + encryption: EncryptionScheme; } export class CallStartedTracker { - public track(callId: string): void { + public track(callId: string, e2eeType: E2eeType): void { PosthogAnalytics.instance.trackEvent({ eventName: "CallStarted", callId: callId, + encryption: mapE2eeType(e2eeType), }); } } diff --git a/src/home/RegisteredView.tsx b/src/home/RegisteredView.tsx index 3335acd2..70aea754 100644 --- a/src/home/RegisteredView.tsx +++ b/src/home/RegisteredView.tsx @@ -18,7 +18,7 @@ import { useState, useCallback, FormEvent, FormEventHandler, FC } from "react"; import { useHistory } from "react-router-dom"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { useTranslation } from "react-i18next"; -import { Heading } from "@vector-im/compound-web"; +import { Dropdown, Heading } from "@vector-im/compound-web"; import { logger } from "matrix-js-sdk/src/logger"; import { Button } from "@vector-im/compound-web"; @@ -45,6 +45,17 @@ import { useOptInAnalytics } from "../settings/settings"; interface Props { client: MatrixClient; } +const encryptionOptions = { + shared: { + label: "Shared key", + e2eeType: E2eeType.SHARED_KEY, + }, + sender: { + label: "Per-participant key", + e2eeType: E2eeType.PER_PARTICIPANT, + }, + none: { label: "None", e2eeType: E2eeType.NONE }, +}; export const RegisteredView: FC = ({ client }) => { const [loading, setLoading] = useState(false); @@ -59,6 +70,9 @@ export const RegisteredView: FC = ({ client }) => { [setJoinExistingCallModalOpen], ); + const [encryption, setEncryption] = + useState("shared"); + const onSubmit: FormEventHandler = useCallback( (e: FormEvent) => { e.preventDefault(); @@ -73,21 +87,13 @@ export const RegisteredView: FC = ({ client }) => { setError(undefined); setLoading(true); - const createRoomResult = await createRoom( + const { roomId, encryptionSystem } = await createRoom( client, roomName, - E2eeType.SHARED_KEY, + encryptionOptions[encryption].e2eeType, ); - if (!createRoomResult.password) - throw new Error("Failed to create room with shared secret"); - history.push( - getRelativeRoomUrl( - createRoomResult.roomId, - { kind: E2eeType.SHARED_KEY, secret: createRoomResult.password }, - roomName, - ), - ); + history.push(getRelativeRoomUrl(roomId, encryptionSystem, roomName)); } submit().catch((error) => { @@ -103,7 +109,7 @@ export const RegisteredView: FC = ({ client }) => { } }); }, - [client, history, setJoinExistingCallModalOpen], + [client, history, setJoinExistingCallModalOpen, encryption], ); const recentRooms = useGroupCallRooms(client); @@ -142,6 +148,19 @@ export const RegisteredView: FC = ({ client }) => { data-testid="home_callName" /> + + setEncryption(x as keyof typeof encryptionOptions) + } + values={Object.keys(encryptionOptions).map((value) => [ + value, + encryptionOptions[value as keyof typeof encryptionOptions] + .label, + ])} + placeholder="" + />