Skip to content

Commit 9a5de12

Browse files
committed
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
1 parent 3e57a76 commit 9a5de12

14 files changed

+339
-58
lines changed

src/analytics/PosthogAnalytics.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ interface PlatformProperties {
7373
appVersion: string;
7474
matrixBackend: "embedded" | "jssdk";
7575
callBackend: "livekit" | "full-mesh";
76+
cryptoVersion?: string;
7677
}
7778

7879
interface PosthogSettings {
@@ -193,6 +194,9 @@ export class PosthogAnalytics {
193194
appVersion,
194195
matrixBackend: widget ? "embedded" : "jssdk",
195196
callBackend: "livekit",
197+
cryptoVersion: widget
198+
? undefined
199+
: window.matrixclient.getCrypto()?.getVersion(),
196200
};
197201
}
198202

src/analytics/PosthogEvents.ts

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,40 @@ limitations under the License.
1616

1717
import { DisconnectReason } from "livekit-client";
1818
import { logger } from "matrix-js-sdk/src/logger";
19+
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc";
1920

2021
import {
2122
IPosthogEvent,
2223
PosthogAnalytics,
2324
RegistrationType,
2425
} from "./PosthogAnalytics";
25-
26+
import { E2eeType } from "../e2ee/e2eeType";
27+
28+
type EncryptionScheme = "none" | "shared" | "per_sender";
29+
30+
function mapE2eeType(type: E2eeType): EncryptionScheme {
31+
switch (type) {
32+
case E2eeType.NONE:
33+
return "none";
34+
case E2eeType.SHARED_KEY:
35+
return "shared";
36+
case E2eeType.PER_PARTICIPANT:
37+
return "per_sender";
38+
}
39+
}
2640
interface CallEnded extends IPosthogEvent {
2741
eventName: "CallEnded";
2842
callId: string;
2943
callParticipantsOnLeave: number;
3044
callParticipantsMax: number;
3145
callDuration: number;
46+
encryption: EncryptionScheme;
47+
toDeviceEncryptionKeysSent: number;
48+
toDeviceEncryptionKeysReceived: number;
49+
toDeviceEncryptionKeysReceivedAverageAge: number;
50+
roomEventEncryptionKeysSent: number;
51+
roomEventEncryptionKeysReceived: number;
52+
roomEventEncryptionKeysReceivedAverageAge: number;
3253
}
3354

3455
export class CallEndedTracker {
@@ -51,6 +72,8 @@ export class CallEndedTracker {
5172
public track(
5273
callId: string,
5374
callParticipantsNow: number,
75+
e2eeType: E2eeType,
76+
rtcSession: MatrixRTCSession,
5477
sendInstantly: boolean,
5578
): void {
5679
PosthogAnalytics.instance.trackEvent<CallEnded>(
@@ -60,6 +83,27 @@ export class CallEndedTracker {
6083
callParticipantsMax: this.cache.maxParticipantsCount,
6184
callParticipantsOnLeave: callParticipantsNow,
6285
callDuration: (Date.now() - this.cache.startTime.getTime()) / 1000,
86+
encryption: mapE2eeType(e2eeType),
87+
toDeviceEncryptionKeysSent:
88+
rtcSession.statistics.counters.toDeviceEncryptionKeysSent,
89+
toDeviceEncryptionKeysReceived:
90+
rtcSession.statistics.counters.toDeviceEncryptionKeysReceived,
91+
toDeviceEncryptionKeysReceivedAverageAge:
92+
rtcSession.statistics.counters.toDeviceEncryptionKeysReceived > 0
93+
? rtcSession.statistics.totals
94+
.toDeviceEncryptionKeysReceivedTotalAge /
95+
rtcSession.statistics.counters.toDeviceEncryptionKeysReceived
96+
: 0,
97+
roomEventEncryptionKeysSent:
98+
rtcSession.statistics.counters.roomEventEncryptionKeysSent,
99+
roomEventEncryptionKeysReceived:
100+
rtcSession.statistics.counters.roomEventEncryptionKeysReceived,
101+
roomEventEncryptionKeysReceivedAverageAge:
102+
rtcSession.statistics.counters.roomEventEncryptionKeysReceived > 0
103+
? rtcSession.statistics.totals
104+
.roomEventEncryptionKeysReceivedTotalAge /
105+
rtcSession.statistics.counters.roomEventEncryptionKeysReceived
106+
: 0,
63107
},
64108
{ send_instantly: sendInstantly },
65109
);
@@ -69,13 +113,15 @@ export class CallEndedTracker {
69113
interface CallStarted extends IPosthogEvent {
70114
eventName: "CallStarted";
71115
callId: string;
116+
encryption: EncryptionScheme;
72117
}
73118

74119
export class CallStartedTracker {
75-
public track(callId: string): void {
120+
public track(callId: string, e2eeType: E2eeType): void {
76121
PosthogAnalytics.instance.trackEvent<CallStarted>({
77122
eventName: "CallStarted",
78123
callId: callId,
124+
encryption: mapE2eeType(e2eeType),
79125
});
80126
}
81127
}

src/home/RegisteredView.tsx

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { useState, useCallback, FormEvent, FormEventHandler, FC } from "react";
1818
import { useHistory } from "react-router-dom";
1919
import { MatrixClient } from "matrix-js-sdk/src/client";
2020
import { useTranslation } from "react-i18next";
21-
import { Heading } from "@vector-im/compound-web";
21+
import { Dropdown, Heading } from "@vector-im/compound-web";
2222
import { logger } from "matrix-js-sdk/src/logger";
2323
import { Button } from "@vector-im/compound-web";
2424

@@ -45,6 +45,17 @@ import { useOptInAnalytics } from "../settings/settings";
4545
interface Props {
4646
client: MatrixClient;
4747
}
48+
const encryptionOptions = {
49+
shared: {
50+
label: "Shared key",
51+
e2eeType: E2eeType.SHARED_KEY,
52+
},
53+
sender: {
54+
label: "Per-participant key",
55+
e2eeType: E2eeType.PER_PARTICIPANT,
56+
},
57+
none: { label: "None", e2eeType: E2eeType.NONE },
58+
};
4859

4960
export const RegisteredView: FC<Props> = ({ client }) => {
5061
const [loading, setLoading] = useState(false);
@@ -59,6 +70,9 @@ export const RegisteredView: FC<Props> = ({ client }) => {
5970
[setJoinExistingCallModalOpen],
6071
);
6172

73+
const [encryption, setEncryption] =
74+
useState<keyof typeof encryptionOptions>("shared");
75+
6276
const onSubmit: FormEventHandler<HTMLFormElement> = useCallback(
6377
(e: FormEvent) => {
6478
e.preventDefault();
@@ -73,21 +87,13 @@ export const RegisteredView: FC<Props> = ({ client }) => {
7387
setError(undefined);
7488
setLoading(true);
7589

76-
const createRoomResult = await createRoom(
90+
const { roomId, encryptionSystem } = await createRoom(
7791
client,
7892
roomName,
79-
E2eeType.SHARED_KEY,
80-
);
81-
if (!createRoomResult.password)
82-
throw new Error("Failed to create room with shared secret");
83-
84-
history.push(
85-
getRelativeRoomUrl(
86-
createRoomResult.roomId,
87-
{ kind: E2eeType.SHARED_KEY, secret: createRoomResult.password },
88-
roomName,
89-
),
93+
encryptionOptions[encryption].e2eeType,
9094
);
95+
96+
history.push(getRelativeRoomUrl(roomId, encryptionSystem, roomName));
9197
}
9298

9399
submit().catch((error) => {
@@ -103,7 +109,7 @@ export const RegisteredView: FC<Props> = ({ client }) => {
103109
}
104110
});
105111
},
106-
[client, history, setJoinExistingCallModalOpen],
112+
[client, history, setJoinExistingCallModalOpen, encryption],
107113
);
108114

109115
const recentRooms = useGroupCallRooms(client);
@@ -142,6 +148,19 @@ export const RegisteredView: FC<Props> = ({ client }) => {
142148
data-testid="home_callName"
143149
/>
144150

151+
<Dropdown
152+
label="Encryption"
153+
defaultValue={encryption}
154+
onValueChange={(x) =>
155+
setEncryption(x as keyof typeof encryptionOptions)
156+
}
157+
values={Object.keys(encryptionOptions).map((value) => [
158+
value,
159+
encryptionOptions[value as keyof typeof encryptionOptions]
160+
.label,
161+
])}
162+
placeholder=""
163+
/>
145164
<Button
146165
type="submit"
147166
size="lg"

src/home/UnauthenticatedView.tsx

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { FC, useCallback, useState, FormEventHandler } from "react";
1818
import { useHistory } from "react-router-dom";
1919
import { randomString } from "matrix-js-sdk/src/randomstring";
2020
import { Trans, useTranslation } from "react-i18next";
21-
import { Button, Heading } from "@vector-im/compound-web";
21+
import { Button, Dropdown, Heading } from "@vector-im/compound-web";
2222
import { logger } from "matrix-js-sdk/src/logger";
2323

2424
import { useClient } from "../ClientContext";
@@ -44,6 +44,18 @@ import { Config } from "../config/Config";
4444
import { E2eeType } from "../e2ee/e2eeType";
4545
import { useOptInAnalytics } from "../settings/settings";
4646

47+
const encryptionOptions = {
48+
shared: {
49+
label: "Shared key",
50+
e2eeType: E2eeType.SHARED_KEY,
51+
},
52+
sender: {
53+
label: "Per-participant key",
54+
e2eeType: E2eeType.PER_PARTICIPANT,
55+
},
56+
none: { label: "None", e2eeType: E2eeType.NONE },
57+
};
58+
4759
export const UnauthenticatedView: FC = () => {
4860
const { setClient } = useClient();
4961
const [loading, setLoading] = useState(false);
@@ -52,6 +64,9 @@ export const UnauthenticatedView: FC = () => {
5264
const { recaptchaKey, register } = useInteractiveRegistration();
5365
const { execute, reset, recaptchaId } = useRecaptcha(recaptchaKey);
5466

67+
const [encryption, setEncryption] =
68+
useState<keyof typeof encryptionOptions>("shared");
69+
5570
const [joinExistingCallModalOpen, setJoinExistingCallModalOpen] =
5671
useState(false);
5772
const onDismissJoinExistingCallModal = useCallback(
@@ -82,13 +97,16 @@ export const UnauthenticatedView: FC = () => {
8297
true,
8398
);
8499

85-
let createRoomResult;
100+
let roomId;
101+
let encryptionSystem;
86102
try {
87-
createRoomResult = await createRoom(
103+
const res = await createRoom(
88104
client,
89105
roomName,
90-
E2eeType.SHARED_KEY,
106+
encryptionOptions[encryption].e2eeType,
91107
);
108+
roomId = res.roomId;
109+
encryptionSystem = res.encryptionSystem;
92110
} catch (error) {
93111
if (!setClient) {
94112
throw error;
@@ -115,17 +133,11 @@ export const UnauthenticatedView: FC = () => {
115133
if (!setClient) {
116134
throw new Error("setClient is undefined");
117135
}
118-
if (!createRoomResult.password)
119-
throw new Error("Failed to create room with shared secret");
136+
// if (!createRoomResult.password)
137+
// throw new Error("Failed to create room with shared secret");
120138

121139
setClient({ client, session });
122-
history.push(
123-
getRelativeRoomUrl(
124-
createRoomResult.roomId,
125-
{ kind: E2eeType.SHARED_KEY, secret: createRoomResult.password },
126-
roomName,
127-
),
128-
);
140+
history.push(getRelativeRoomUrl(roomId, encryptionSystem, roomName));
129141
}
130142

131143
submit().catch((error) => {
@@ -142,6 +154,7 @@ export const UnauthenticatedView: FC = () => {
142154
history,
143155
setJoinExistingCallModalOpen,
144156
setClient,
157+
encryption,
145158
],
146159
);
147160

@@ -204,6 +217,20 @@ export const UnauthenticatedView: FC = () => {
204217
<ErrorMessage error={error} />
205218
</FieldRow>
206219
)}
220+
<Dropdown
221+
label="Encryption"
222+
defaultValue={encryption}
223+
onValueChange={(x) =>
224+
setEncryption(x as keyof typeof encryptionOptions)
225+
}
226+
values={Object.keys(encryptionOptions).map((value) => [
227+
value,
228+
encryptionOptions[value as keyof typeof encryptionOptions]
229+
.label,
230+
])}
231+
placeholder=""
232+
/>
233+
207234
<Button
208235
type="submit"
209236
size="lg"

src/room/GroupCallView.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export const GroupCallView: FC<Props> = ({
9797
const { displayName, avatarUrl } = useProfile(client);
9898
const roomName = useRoomName(rtcSession.room);
9999
const roomAvatar = useRoomAvatar(rtcSession.room);
100-
const { perParticipantE2EE, returnToLobby } = useUrlParams();
100+
const { returnToLobby } = useUrlParams();
101101
const e2eeSystem = useRoomEncryptionSystem(rtcSession.room.roomId);
102102

103103
const matrixInfo = useMemo((): MatrixInfo => {
@@ -191,7 +191,7 @@ export const GroupCallView: FC<Props> = ({
191191
ev: CustomEvent<IWidgetApiRequest>,
192192
): Promise<void> => {
193193
await defaultDeviceSetup(ev.detail.data as unknown as JoinCallData);
194-
await enterRTCSession(rtcSession, perParticipantE2EE);
194+
await enterRTCSession(rtcSession, e2eeSystem.kind);
195195
await widget!.api.transport.reply(ev.detail, {});
196196
};
197197
widget.lazyActions.on(ElementWidgetActions.JoinCall, onJoin);
@@ -201,12 +201,12 @@ export const GroupCallView: FC<Props> = ({
201201
} else if (widget && !preload && skipLobby) {
202202
const join = async (): Promise<void> => {
203203
await defaultDeviceSetup({ audioInput: null, videoInput: null });
204-
await enterRTCSession(rtcSession, perParticipantE2EE);
204+
await enterRTCSession(rtcSession, e2eeSystem.kind);
205205
};
206206
// No lobby and no preload: we enter the RTC Session right away.
207207
join();
208208
}
209-
}, [rtcSession, preload, skipLobby, perParticipantE2EE]);
209+
}, [rtcSession, preload, skipLobby, e2eeSystem]);
210210

211211
const [left, setLeft] = useState(false);
212212
const [leaveError, setLeaveError] = useState<Error | undefined>(undefined);
@@ -223,6 +223,8 @@ export const GroupCallView: FC<Props> = ({
223223
PosthogAnalytics.instance.eventCallEnded.track(
224224
rtcSession.room.roomId,
225225
rtcSession.memberships.length,
226+
matrixInfo.e2eeSystem.kind,
227+
rtcSession,
226228
sendInstantly,
227229
);
228230

@@ -237,7 +239,7 @@ export const GroupCallView: FC<Props> = ({
237239
history.push("/");
238240
}
239241
},
240-
[rtcSession, isPasswordlessUser, confineToRoom, history],
242+
[rtcSession, isPasswordlessUser, confineToRoom, history, matrixInfo],
241243
);
242244

243245
useEffect(() => {
@@ -262,8 +264,8 @@ export const GroupCallView: FC<Props> = ({
262264
const onReconnect = useCallback(() => {
263265
setLeft(false);
264266
setLeaveError(undefined);
265-
enterRTCSession(rtcSession, perParticipantE2EE);
266-
}, [rtcSession, perParticipantE2EE]);
267+
enterRTCSession(rtcSession, e2eeSystem.kind);
268+
}, [rtcSession, e2eeSystem]);
267269

268270
const joinRule = useJoinRule(rtcSession.room);
269271

@@ -316,7 +318,7 @@ export const GroupCallView: FC<Props> = ({
316318
client={client}
317319
matrixInfo={matrixInfo}
318320
muteStates={muteStates}
319-
onEnter={() => void enterRTCSession(rtcSession, perParticipantE2EE)}
321+
onEnter={() => void enterRTCSession(rtcSession, e2eeSystem.kind)}
320322
confineToRoom={confineToRoom}
321323
hideHeader={hideHeader}
322324
participantCount={participantCount}

0 commit comments

Comments
 (0)