Use simple strings for e2ee and allow for users to type in a custom passphrase (#116)
* wip * add e2ee options to custom tab and update client
This commit is contained in:
parent
9244b0d8b5
commit
4110951948
6 changed files with 88 additions and 65 deletions
|
@ -20,13 +20,24 @@ export function useServerUrl(region?: string) {
|
||||||
return serverUrl;
|
return serverUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function encodePassphrase(bytes: Uint8Array) {
|
export function encodePassphrase(passphrase: string) {
|
||||||
const binString = Array.from(bytes, (x) => String.fromCodePoint(x)).join('');
|
return encodeURIComponent(passphrase);
|
||||||
return btoa(binString);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decodePassphrase(base64String: string) {
|
export function decodePassphrase(base64String: string) {
|
||||||
const binString = atob(base64String);
|
return decodeURIComponent(base64String);
|
||||||
// @ts-ignore
|
}
|
||||||
return Uint8Array.from(binString, (m) => m.codePointAt(0));
|
|
||||||
|
export function generateRoomId(): string {
|
||||||
|
return `${randomString(4)}-${randomString(4)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function randomString(length: number): string {
|
||||||
|
let result = '';
|
||||||
|
const characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
const charactersLength = characters.length;
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@livekit/components-react": "1.1.7",
|
"@livekit/components-react": "1.1.8",
|
||||||
"@livekit/components-styles": "1.0.6",
|
"@livekit/components-styles": "1.0.6",
|
||||||
"livekit-client": "1.13.2",
|
"livekit-client": "1.13.2",
|
||||||
"livekit-server-sdk": "1.2.6",
|
"livekit-server-sdk": "1.2.6",
|
||||||
|
|
|
@ -16,13 +16,13 @@ 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 e2eePassphrase = typeof window !== 'undefined' && decodePassphrase(window.location.hash);
|
||||||
const worker =
|
const worker =
|
||||||
typeof window !== 'undefined' &&
|
typeof window !== 'undefined' &&
|
||||||
new Worker(new URL('livekit-client/e2ee-worker', import.meta.url));
|
new Worker(new URL('livekit-client/e2ee-worker', import.meta.url));
|
||||||
const keyProvider = new ExternalE2EEKeyProvider();
|
const keyProvider = new ExternalE2EEKeyProvider();
|
||||||
|
|
||||||
const e2eeEnabled = !!(hash && worker);
|
const e2eeEnabled = !!(e2eePassphrase && worker);
|
||||||
|
|
||||||
const roomOptions = useMemo((): RoomOptions => {
|
const roomOptions = useMemo((): RoomOptions => {
|
||||||
return {
|
return {
|
||||||
|
@ -43,7 +43,7 @@ export default function CustomRoomConnection() {
|
||||||
|
|
||||||
const room = useMemo(() => new Room(roomOptions), []);
|
const room = useMemo(() => new Room(roomOptions), []);
|
||||||
if (e2eeEnabled) {
|
if (e2eeEnabled) {
|
||||||
keyProvider.setKey(decodePassphrase(hash.substring(1)));
|
keyProvider.setKey(e2eePassphrase);
|
||||||
room.setE2EEEnabled(true);
|
room.setE2EEEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type { GetServerSideProps, InferGetServerSidePropsType } from 'next';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import React, { ReactElement, useState } 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';
|
import { encodePassphrase, generateRoomId, randomString } from '../lib/client-utils';
|
||||||
|
|
||||||
interface TabsProps {
|
interface TabsProps {
|
||||||
children: ReactElement[];
|
children: ReactElement[];
|
||||||
|
@ -39,27 +39,12 @@ 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) {
|
router.push(`/rooms/${generateRoomId()}`);
|
||||||
const phrase = encodePassphrase(crypto.getRandomValues(new Uint8Array(256)));
|
|
||||||
router.push(`/rooms/${generateRoomId()}#${phrase}`);
|
|
||||||
} else {
|
|
||||||
router.push(`/rooms/${generateRoomId()}`);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className={styles.tabContent}>
|
<div className={styles.tabContent}>
|
||||||
<p style={{ margin: 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>
|
||||||
<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}>
|
<button style={{ marginTop: '1rem' }} className="lk-button" onClick={startMeeting}>
|
||||||
Start Meeting
|
Start Meeting
|
||||||
</button>
|
</button>
|
||||||
|
@ -69,7 +54,9 @@ 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 [e2ee, setE2ee] = useState(false);
|
||||||
|
const [sharedPassphrase, setSharedPassphrase] = useState(randomString(64));
|
||||||
|
|
||||||
const onSubmit: React.FormEventHandler<HTMLFormElement> = (event) => {
|
const onSubmit: React.FormEventHandler<HTMLFormElement> = (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -77,8 +64,9 @@ function CustomConnectionTab({ label }: { label: string }) {
|
||||||
const serverUrl = formData.get('serverUrl');
|
const serverUrl = formData.get('serverUrl');
|
||||||
const token = formData.get('token');
|
const token = formData.get('token');
|
||||||
if (e2ee) {
|
if (e2ee) {
|
||||||
const passphrase = encodePassphrase(crypto.getRandomValues(new Uint8Array(256)));
|
router.push(
|
||||||
router.push(`/custom/?liveKitUrl=${serverUrl}&token=${token}#${passphrase}`);
|
`/custom/?liveKitUrl=${serverUrl}&token=${token}#${encodePassphrase(sharedPassphrase)}`,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
router.push(`/custom/?liveKitUrl=${serverUrl}&token=${token}`);
|
router.push(`/custom/?liveKitUrl=${serverUrl}&token=${token}`);
|
||||||
}
|
}
|
||||||
|
@ -103,15 +91,29 @@ 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' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
||||||
<input
|
<div style={{ display: 'flex', flexDirection: 'row', gap: '1rem' }}>
|
||||||
id="use-e2ee"
|
<input
|
||||||
type="checkbox"
|
id="use-e2ee"
|
||||||
checked={e2ee}
|
type="checkbox"
|
||||||
onChange={(ev) => setE2ee(ev.target.checked)}
|
checked={e2ee}
|
||||||
></input>
|
onChange={(ev) => setE2ee(ev.target.checked)}
|
||||||
<label htmlFor="use-e2ee">Enable end-to-end encryption</label>
|
></input>
|
||||||
|
<label htmlFor="use-e2ee">Enable end-to-end encryption</label>
|
||||||
|
</div>
|
||||||
|
{e2ee && (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'row', gap: '1rem' }}>
|
||||||
|
<label htmlFor="passphrase">Passphrase</label>
|
||||||
|
<input
|
||||||
|
id="passphrase"
|
||||||
|
type="password"
|
||||||
|
value={sharedPassphrase}
|
||||||
|
onChange={(ev) => setSharedPassphrase(ev.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</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' }}
|
||||||
/>
|
/>
|
||||||
|
@ -179,17 +181,3 @@ const Home = ({ tabIndex }: InferGetServerSidePropsType<typeof getServerSideProp
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Home;
|
export default Home;
|
||||||
|
|
||||||
function generateRoomId(): string {
|
|
||||||
return `${randomString(4)}-${randomString(4)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function randomString(length: number): string {
|
|
||||||
let result = '';
|
|
||||||
const characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
|
||||||
const charactersLength = characters.length;
|
|
||||||
for (let i = 0; i < length; i++) {
|
|
||||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
'use client';
|
||||||
import {
|
import {
|
||||||
LiveKitRoom,
|
LiveKitRoom,
|
||||||
PreJoin,
|
PreJoin,
|
||||||
|
@ -20,13 +21,35 @@ 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 { decodePassphrase, useServerUrl } from '../../lib/client-utils';
|
import {
|
||||||
|
decodePassphrase,
|
||||||
|
encodePassphrase,
|
||||||
|
randomString,
|
||||||
|
useServerUrl,
|
||||||
|
} from '../../lib/client-utils';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
|
||||||
|
const PreJoinNoSSR = dynamic(
|
||||||
|
async () => {
|
||||||
|
return (await import('@livekit/components-react')).PreJoin;
|
||||||
|
},
|
||||||
|
{ ssr: false },
|
||||||
|
);
|
||||||
|
|
||||||
const Home: NextPage = () => {
|
const Home: NextPage = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { name: roomName } = router.query;
|
const { name: roomName } = router.query;
|
||||||
|
const e2eePassphrase =
|
||||||
|
typeof window !== 'undefined' && decodePassphrase(location.hash.substring(1));
|
||||||
|
|
||||||
const [preJoinChoices, setPreJoinChoices] = useState<LocalUserChoices | undefined>(undefined);
|
const [preJoinChoices, setPreJoinChoices] = useState<LocalUserChoices | undefined>(undefined);
|
||||||
|
|
||||||
|
function handlePreJoinSubmit(values: LocalUserChoices) {
|
||||||
|
if (values.e2ee) {
|
||||||
|
location.hash = encodePassphrase(values.sharedPassphrase);
|
||||||
|
}
|
||||||
|
setPreJoinChoices(values);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
|
@ -45,18 +68,18 @@ const Home: NextPage = () => {
|
||||||
></ActiveRoom>
|
></ActiveRoom>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ display: 'grid', placeItems: 'center', height: '100%' }}>
|
<div style={{ display: 'grid', placeItems: 'center', height: '100%' }}>
|
||||||
<PreJoin
|
<PreJoinNoSSR
|
||||||
onError={(err) => console.log('error while setting up prejoin', err)}
|
onError={(err) => console.log('error while setting up prejoin', err)}
|
||||||
defaults={{
|
defaults={{
|
||||||
username: '',
|
username: '',
|
||||||
videoEnabled: true,
|
videoEnabled: true,
|
||||||
audioEnabled: true,
|
audioEnabled: true,
|
||||||
|
e2ee: !!e2eePassphrase,
|
||||||
|
sharedPassphrase: e2eePassphrase || randomString(64),
|
||||||
}}
|
}}
|
||||||
onSubmit={(values) => {
|
onSubmit={handlePreJoinSubmit}
|
||||||
console.log('Joining with: ', values);
|
showE2EEOptions={true}
|
||||||
setPreJoinChoices(values);
|
></PreJoinNoSSR>
|
||||||
}}
|
|
||||||
></PreJoin>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
|
@ -85,12 +108,12 @@ 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 worker =
|
const worker =
|
||||||
typeof window !== 'undefined' &&
|
typeof window !== 'undefined' &&
|
||||||
|
userChoices.e2ee &&
|
||||||
new Worker(new URL('livekit-client/e2ee-worker', import.meta.url));
|
new Worker(new URL('livekit-client/e2ee-worker', import.meta.url));
|
||||||
|
|
||||||
const e2eeEnabled = !!(hash && worker);
|
const e2eeEnabled = !!(userChoices.e2ee && worker);
|
||||||
const keyProvider = new ExternalE2EEKeyProvider();
|
const keyProvider = new ExternalE2EEKeyProvider();
|
||||||
|
|
||||||
const roomOptions = useMemo((): RoomOptions => {
|
const roomOptions = useMemo((): RoomOptions => {
|
||||||
|
@ -100,6 +123,7 @@ const ActiveRoom = ({ roomName, userChoices, onLeave }: ActiveRoomProps) => {
|
||||||
resolution: hq === 'true' ? VideoPresets.h2160 : VideoPresets.h720,
|
resolution: hq === 'true' ? VideoPresets.h2160 : VideoPresets.h720,
|
||||||
},
|
},
|
||||||
publishDefaults: {
|
publishDefaults: {
|
||||||
|
dtx: false,
|
||||||
videoSimulcastLayers:
|
videoSimulcastLayers:
|
||||||
hq === 'true'
|
hq === 'true'
|
||||||
? [VideoPresets.h1080, VideoPresets.h720]
|
? [VideoPresets.h1080, VideoPresets.h720]
|
||||||
|
@ -123,7 +147,7 @@ const ActiveRoom = ({ roomName, userChoices, onLeave }: ActiveRoomProps) => {
|
||||||
const room = useMemo(() => new Room(roomOptions), []);
|
const room = useMemo(() => new Room(roomOptions), []);
|
||||||
|
|
||||||
if (e2eeEnabled) {
|
if (e2eeEnabled) {
|
||||||
keyProvider.setKey(decodePassphrase(hash.substring(1)));
|
keyProvider.setKey(decodePassphrase(userChoices.sharedPassphrase));
|
||||||
room.setE2EEEnabled(true);
|
room.setE2EEEnabled(true);
|
||||||
}
|
}
|
||||||
const connectOptions = useMemo((): RoomConnectOptions => {
|
const connectOptions = useMemo((): RoomConnectOptions => {
|
||||||
|
|
|
@ -101,10 +101,10 @@
|
||||||
loglevel "^1.8.1"
|
loglevel "^1.8.1"
|
||||||
rxjs "^7.8.0"
|
rxjs "^7.8.0"
|
||||||
|
|
||||||
"@livekit/components-react@1.1.7":
|
"@livekit/components-react@1.1.8":
|
||||||
version "1.1.7"
|
version "1.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/@livekit/components-react/-/components-react-1.1.7.tgz#f861350dc0df80fb3f027efa6a1706b9b203923e"
|
resolved "https://registry.yarnpkg.com/@livekit/components-react/-/components-react-1.1.8.tgz#34f67a6646bcd5c44d87d3fed5be5010b297f111"
|
||||||
integrity sha512-wAOtzetPYMPrE6aA+ABzmLQA57IkvFC+qJiuvSWumbtzVmUZTaD56Y9T2d4F7/zwki3npbUQuABZmFaiMKChpg==
|
integrity sha512-Ljlcqkgg7IqdQIDr7/ViEsL8GZkmSkoMC38hz3CkCcmHP+e8U4+4be2C2feiHb4hHw+tLd1DPuElyEWuTeiQKQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@livekit/components-core" "0.6.15"
|
"@livekit/components-core" "0.6.15"
|
||||||
"@react-hook/latest" "^1.0.3"
|
"@react-hook/latest" "^1.0.3"
|
||||||
|
|
Loading…
Reference in a new issue