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;
|
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 { 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 { useRouter } from 'next/router';
|
||||||
import { DebugMode } from '../../lib/Debug';
|
import { DebugMode } from '../../lib/Debug';
|
||||||
|
import { decodePassphrase } from '../../lib/client-utils';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
export default function CustomRoomConnection() {
|
export default function CustomRoomConnection() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { liveKitUrl, token } = router.query;
|
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') {
|
if (typeof liveKitUrl !== 'string') {
|
||||||
return <h2>Missing LiveKit URL</h2>;
|
return <h2>Missing LiveKit URL</h2>;
|
||||||
}
|
}
|
||||||
|
@ -16,7 +48,7 @@ export default function CustomRoomConnection() {
|
||||||
return (
|
return (
|
||||||
<main data-lk-theme="default">
|
<main data-lk-theme="default">
|
||||||
{liveKitUrl && (
|
{liveKitUrl && (
|
||||||
<LiveKitRoom token={token} serverUrl={liveKitUrl} audio={true} video={true}>
|
<LiveKitRoom room={room} token={token} serverUrl={liveKitUrl} audio={true} video={true}>
|
||||||
<VideoConference chatMessageFormatter={formatChatMessageLinks} />
|
<VideoConference chatMessageFormatter={formatChatMessageLinks} />
|
||||||
<DebugMode logLevel={LogLevel.info} />
|
<DebugMode logLevel={LogLevel.info} />
|
||||||
</LiveKitRoom>
|
</LiveKitRoom>
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import type { GetServerSideProps, InferGetServerSidePropsType } from 'next';
|
import type { GetServerSideProps, InferGetServerSidePropsType } from 'next';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import React, { ReactElement } from 'react';
|
import React, { ReactElement, useState } from 'react';
|
||||||
import styles from '../styles/Home.module.css';
|
import styles from '../styles/Home.module.css';
|
||||||
|
import { encodePassphrase } from '../lib/client-utils';
|
||||||
|
|
||||||
interface TabsProps {
|
interface TabsProps {
|
||||||
children: ReactElement[];
|
children: ReactElement[];
|
||||||
|
@ -38,13 +39,28 @@ function Tabs(props: TabsProps) {
|
||||||
|
|
||||||
function DemoMeetingTab({ label }: { label: string }) {
|
function DemoMeetingTab({ label }: { label: string }) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [e2ee, setE2ee] = useState(false);
|
||||||
const startMeeting = () => {
|
const startMeeting = () => {
|
||||||
|
if (e2ee) {
|
||||||
|
const phrase = encodePassphrase(crypto.getRandomValues(new Uint8Array(256)));
|
||||||
|
router.push(`/rooms/${generateRoomId()}#${phrase}`);
|
||||||
|
} else {
|
||||||
router.push(`/rooms/${generateRoomId()}`);
|
router.push(`/rooms/${generateRoomId()}`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className={styles.tabContent}>
|
<div className={styles.tabContent}>
|
||||||
<p style={{ marginTop: 0 }}>Try LiveKit Meet for free with our live demo project.</p>
|
<p style={{ margin: 0 }}>Try LiveKit Meet for free with our live demo project.</p>
|
||||||
<button className="lk-button" onClick={startMeeting}>
|
<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
|
Start Meeting
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -53,12 +69,19 @@ function DemoMeetingTab({ label }: { label: string }) {
|
||||||
|
|
||||||
function CustomConnectionTab({ label }: { label: string }) {
|
function CustomConnectionTab({ label }: { label: string }) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [e2ee, setE2ee] = useState(false);
|
||||||
|
|
||||||
const onSubmit: React.FormEventHandler<HTMLFormElement> = (event) => {
|
const onSubmit: React.FormEventHandler<HTMLFormElement> = (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const formData = new FormData(event.target as HTMLFormElement);
|
const formData = new FormData(event.target as HTMLFormElement);
|
||||||
const serverUrl = formData.get('serverUrl');
|
const serverUrl = formData.get('serverUrl');
|
||||||
const token = formData.get('token');
|
const token = formData.get('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}`);
|
router.push(`/custom/?liveKitUrl=${serverUrl}&token=${token}`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<form className={styles.tabContent} onSubmit={onSubmit}>
|
<form className={styles.tabContent} onSubmit={onSubmit}>
|
||||||
|
@ -80,6 +103,15 @@ function CustomConnectionTab({ label }: { label: string }) {
|
||||||
rows={9}
|
rows={9}
|
||||||
style={{ padding: '1px 2px', fontSize: 'inherit', lineHeight: 'inherit' }}
|
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
|
<hr
|
||||||
style={{ width: '100%', borderColor: 'rgba(255, 255, 255, 0.15)', marginBlock: '1rem' }}
|
style={{ width: '100%', borderColor: 'rgba(255, 255, 255, 0.15)', marginBlock: '1rem' }}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -6,14 +6,22 @@ import {
|
||||||
VideoConference,
|
VideoConference,
|
||||||
formatChatMessageLinks,
|
formatChatMessageLinks,
|
||||||
} from '@livekit/components-react';
|
} 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 type { NextPage } from 'next';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { DebugMode } from '../../lib/Debug';
|
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 Home: NextPage = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -78,6 +86,16 @@ const ActiveRoom = ({ roomName, userChoices, onLeave }: ActiveRoomProps) => {
|
||||||
|
|
||||||
const liveKitUrl = useServerUrl(region as string | undefined);
|
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 => {
|
const roomOptions = useMemo((): RoomOptions => {
|
||||||
return {
|
return {
|
||||||
videoCaptureDefaults: {
|
videoCaptureDefaults: {
|
||||||
|
@ -95,9 +113,20 @@ const ActiveRoom = ({ roomName, userChoices, onLeave }: ActiveRoomProps) => {
|
||||||
},
|
},
|
||||||
adaptiveStream: { pixelDensity: 'screen' },
|
adaptiveStream: { pixelDensity: 'screen' },
|
||||||
dynacast: true,
|
dynacast: true,
|
||||||
|
e2ee:
|
||||||
|
hash && worker
|
||||||
|
? {
|
||||||
|
keyProvider,
|
||||||
|
worker,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
};
|
};
|
||||||
}, [userChoices, hq]);
|
}, [userChoices, hq]);
|
||||||
|
|
||||||
|
const room = useMemo(() => new Room(roomOptions), []);
|
||||||
|
|
||||||
|
room.setE2EEEnabled(true);
|
||||||
|
|
||||||
const connectOptions = useMemo((): RoomConnectOptions => {
|
const connectOptions = useMemo((): RoomConnectOptions => {
|
||||||
return {
|
return {
|
||||||
autoSubscribe: false,
|
autoSubscribe: false,
|
||||||
|
@ -108,9 +137,9 @@ const ActiveRoom = ({ roomName, userChoices, onLeave }: ActiveRoomProps) => {
|
||||||
<>
|
<>
|
||||||
{liveKitUrl && (
|
{liveKitUrl && (
|
||||||
<LiveKitRoom
|
<LiveKitRoom
|
||||||
|
room={room}
|
||||||
token={token}
|
token={token}
|
||||||
serverUrl={liveKitUrl}
|
serverUrl={liveKitUrl}
|
||||||
options={roomOptions}
|
|
||||||
connectOptions={connectOptions}
|
connectOptions={connectOptions}
|
||||||
video={userChoices.videoEnabled}
|
video={userChoices.videoEnabled}
|
||||||
audio={userChoices.audioEnabled}
|
audio={userChoices.audioEnabled}
|
||||||
|
|
Loading…
Add table
Reference in a new issue