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="" + />