Add e2ee option (#106)

* Add checkbox for e2ee

* add also to custom tab

* fix SSR
This commit is contained in:
lukasIO 2023-08-30 11:25:22 +02:00 committed by GitHub
parent 9a4fb5ddfd
commit 041d41a5fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 130 additions and 10 deletions

16
lib/DummyKeyProvider.ts Normal file
View file

@ -0,0 +1,16 @@
// just for demonstration purposes, extremely insecure
import { BaseKeyProvider, createKeyMaterialFromString } from 'livekit-client';
export class DummyKeyProvider extends BaseKeyProvider {
readonly participantKeys = new Map([
['dev1', 'dev1key'],
['dev2', 'dev2key'],
]);
async setKey(participantId: string) {
// @ts-ignore
const cryptoKey = await createKeyMaterialFromString(this.participantKeys.get(participantId));
this.onSetEncryptionKey(cryptoKey, participantId);
}
}

View file

@ -19,3 +19,14 @@ export function useServerUrl(region?: string) {
});
return serverUrl;
}
export function encodePassphrase(bytes: Uint8Array) {
const binString = Array.from(bytes, (x) => String.fromCodePoint(x)).join('');
return btoa(binString);
}
export function decodePassphrase(base64String: string) {
const binString = atob(base64String);
// @ts-ignore
return Uint8Array.from(binString, (m) => m.codePointAt(0));
}

View file

@ -1,11 +1,43 @@
import { formatChatMessageLinks, LiveKitRoom, VideoConference } from '@livekit/components-react';
import { LogLevel } from 'livekit-client';
import { ExternalE2EEKeyProvider, LogLevel, Room, RoomOptions, VideoPresets } from 'livekit-client';
import { useRouter } from 'next/router';
import { DebugMode } from '../../lib/Debug';
import { decodePassphrase } from '../../lib/client-utils';
import { useMemo } from 'react';
export default function CustomRoomConnection() {
const router = useRouter();
const { liveKitUrl, token } = router.query;
const hash = typeof window !== 'undefined' && window.location.hash;
const keyProvider = new ExternalE2EEKeyProvider();
if (hash) {
keyProvider.setKey(decodePassphrase(hash.substring(1)));
}
const worker =
typeof window !== 'undefined' &&
new Worker(new URL('livekit-client/e2ee-worker', import.meta.url));
const roomOptions = useMemo((): RoomOptions => {
return {
publishDefaults: {
videoSimulcastLayers: [VideoPresets.h540, VideoPresets.h216],
},
adaptiveStream: { pixelDensity: 'screen' },
dynacast: true,
e2ee:
hash && worker
? {
keyProvider,
worker,
}
: undefined,
};
}, []);
const room = useMemo(() => new Room(roomOptions), []);
if (typeof liveKitUrl !== 'string') {
return <h2>Missing LiveKit URL</h2>;
}
@ -16,7 +48,7 @@ export default function CustomRoomConnection() {
return (
<main data-lk-theme="default">
{liveKitUrl && (
<LiveKitRoom token={token} serverUrl={liveKitUrl} audio={true} video={true}>
<LiveKitRoom room={room} token={token} serverUrl={liveKitUrl} audio={true} video={true}>
<VideoConference chatMessageFormatter={formatChatMessageLinks} />
<DebugMode logLevel={LogLevel.info} />
</LiveKitRoom>

View file

@ -1,7 +1,8 @@
import type { GetServerSideProps, InferGetServerSidePropsType } from 'next';
import { useRouter } from 'next/router';
import React, { ReactElement } from 'react';
import React, { ReactElement, useState } from 'react';
import styles from '../styles/Home.module.css';
import { encodePassphrase } from '../lib/client-utils';
interface TabsProps {
children: ReactElement[];
@ -38,13 +39,28 @@ function Tabs(props: TabsProps) {
function DemoMeetingTab({ label }: { label: string }) {
const router = useRouter();
const [e2ee, setE2ee] = useState(false);
const startMeeting = () => {
router.push(`/rooms/${generateRoomId()}`);
if (e2ee) {
const phrase = encodePassphrase(crypto.getRandomValues(new Uint8Array(256)));
router.push(`/rooms/${generateRoomId()}#${phrase}`);
} else {
router.push(`/rooms/${generateRoomId()}`);
}
};
return (
<div className={styles.tabContent}>
<p style={{ marginTop: 0 }}>Try LiveKit Meet for free with our live demo project.</p>
<button className="lk-button" onClick={startMeeting}>
<p style={{ margin: 0 }}>Try LiveKit Meet for free with our live demo project.</p>
<div style={{ display: 'flex', gap: '1rem' }}>
<input
id="use-e2ee"
type="checkbox"
checked={e2ee}
onChange={(ev) => setE2ee(ev.target.checked)}
></input>
<label htmlFor="use-e2ee">Enable end-to-end encryption</label>
</div>
<button style={{ marginTop: '1rem' }} className="lk-button" onClick={startMeeting}>
Start Meeting
</button>
</div>
@ -53,12 +69,19 @@ function DemoMeetingTab({ label }: { label: string }) {
function CustomConnectionTab({ label }: { label: string }) {
const router = useRouter();
const [e2ee, setE2ee] = useState(false);
const onSubmit: React.FormEventHandler<HTMLFormElement> = (event) => {
event.preventDefault();
const formData = new FormData(event.target as HTMLFormElement);
const serverUrl = formData.get('serverUrl');
const token = formData.get('token');
router.push(`/custom/?liveKitUrl=${serverUrl}&token=${token}`);
if (e2ee) {
const passphrase = encodePassphrase(crypto.getRandomValues(new Uint8Array(256)));
router.push(`/custom/?liveKitUrl=${serverUrl}&token=${token}#${passphrase}`);
} else {
router.push(`/custom/?liveKitUrl=${serverUrl}&token=${token}`);
}
};
return (
<form className={styles.tabContent} onSubmit={onSubmit}>
@ -80,6 +103,15 @@ function CustomConnectionTab({ label }: { label: string }) {
rows={9}
style={{ padding: '1px 2px', fontSize: 'inherit', lineHeight: 'inherit' }}
/>
<div style={{ display: 'flex', gap: '1rem' }}>
<input
id="use-e2ee"
type="checkbox"
checked={e2ee}
onChange={(ev) => setE2ee(ev.target.checked)}
></input>
<label htmlFor="use-e2ee">Enable end-to-end encryption</label>
</div>
<hr
style={{ width: '100%', borderColor: 'rgba(255, 255, 255, 0.15)', marginBlock: '1rem' }}
/>

View file

@ -6,14 +6,22 @@ import {
VideoConference,
formatChatMessageLinks,
} from '@livekit/components-react';
import { LogLevel, RoomConnectOptions, RoomOptions, VideoPresets } from 'livekit-client';
import {
ExternalE2EEKeyProvider,
LogLevel,
Room,
RoomConnectOptions,
RoomOptions,
VideoPresets,
} from 'livekit-client';
import type { NextPage } from 'next';
import Head from 'next/head';
import { useRouter } from 'next/router';
import { useMemo, useState } from 'react';
import { DebugMode } from '../../lib/Debug';
import { useServerUrl } from '../../lib/client-utils';
import { decodePassphrase, useServerUrl } from '../../lib/client-utils';
import { DummyKeyProvider } from '../../lib/DummyKeyProvider';
const Home: NextPage = () => {
const router = useRouter();
@ -78,6 +86,16 @@ const ActiveRoom = ({ roomName, userChoices, onLeave }: ActiveRoomProps) => {
const liveKitUrl = useServerUrl(region as string | undefined);
const hash = typeof window !== 'undefined' && window.location.hash;
const keyProvider = new ExternalE2EEKeyProvider();
if (hash) {
keyProvider.setKey(decodePassphrase(hash.substring(1)));
}
const worker =
typeof window !== 'undefined' &&
new Worker(new URL('livekit-client/e2ee-worker', import.meta.url));
const roomOptions = useMemo((): RoomOptions => {
return {
videoCaptureDefaults: {
@ -95,9 +113,20 @@ const ActiveRoom = ({ roomName, userChoices, onLeave }: ActiveRoomProps) => {
},
adaptiveStream: { pixelDensity: 'screen' },
dynacast: true,
e2ee:
hash && worker
? {
keyProvider,
worker,
}
: undefined,
};
}, [userChoices, hq]);
const room = useMemo(() => new Room(roomOptions), []);
room.setE2EEEnabled(true);
const connectOptions = useMemo((): RoomConnectOptions => {
return {
autoSubscribe: false,
@ -108,9 +137,9 @@ const ActiveRoom = ({ roomName, userChoices, onLeave }: ActiveRoomProps) => {
<>
{liveKitUrl && (
<LiveKitRoom
room={room}
token={token}
serverUrl={liveKitUrl}
options={roomOptions}
connectOptions={connectOptions}
video={userChoices.videoEnabled}
audio={userChoices.audioEnabled}