Add e2ee option (#106)
* Add checkbox for e2ee * add also to custom tab * fix SSR
This commit is contained in:
parent
9a4fb5ddfd
commit
041d41a5fe
5 changed files with 130 additions and 10 deletions
16
lib/DummyKeyProvider.ts
Normal file
16
lib/DummyKeyProvider.ts
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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' }}
|
||||
/>
|
||||
|
|
|
@ -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}
|
||||
|
|
Loading…
Reference in a new issue