Merge branch 'livekit' into copy-alias
This commit is contained in:
@@ -55,7 +55,7 @@
|
|||||||
"i18next": "^21.10.0",
|
"i18next": "^21.10.0",
|
||||||
"i18next-browser-languagedetector": "^6.1.8",
|
"i18next-browser-languagedetector": "^6.1.8",
|
||||||
"i18next-http-backend": "^1.4.4",
|
"i18next-http-backend": "^1.4.4",
|
||||||
"livekit-client": "1.12.0",
|
"livekit-client": "1.12.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#b698217445318f453e0b1086364a33113eaa85d9",
|
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#b698217445318f453e0b1086364a33113eaa85d9",
|
||||||
"matrix-widget-api": "^1.3.1",
|
"matrix-widget-api": "^1.3.1",
|
||||||
@@ -83,12 +83,12 @@
|
|||||||
"@storybook/react": "^6.5.0-alpha.5",
|
"@storybook/react": "^6.5.0-alpha.5",
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^13.4.0",
|
||||||
"@types/node": "^18.13.0",
|
|
||||||
"@types/request": "^2.48.8",
|
|
||||||
"@types/content-type": "^1.1.5",
|
"@types/content-type": "^1.1.5",
|
||||||
"@types/dom-screen-wake-lock": "^1.0.1",
|
"@types/dom-screen-wake-lock": "^1.0.1",
|
||||||
"@types/grecaptcha": "^3.0.4",
|
"@types/grecaptcha": "^3.0.4",
|
||||||
|
"@types/node": "^18.13.0",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
|
"@types/request": "^2.48.8",
|
||||||
"@types/sdp-transform": "^2.4.5",
|
"@types/sdp-transform": "^2.4.5",
|
||||||
"@types/uuid": "9",
|
"@types/uuid": "9",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
||||||
|
|||||||
@@ -116,5 +116,6 @@
|
|||||||
"By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Mit einem Klick auf „Anruf beitreten“ akzeptierst du unseren <2>Endbenutzer-Lizenzvertrag (EULA)</2>",
|
"By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Mit einem Klick auf „Anruf beitreten“ akzeptierst du unseren <2>Endbenutzer-Lizenzvertrag (EULA)</2>",
|
||||||
"By clicking \"Go\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Mit einem Klick auf „Los geht’s“ akzeptierst du unseren <2>Endbenutzer-Lizenzvertrag (EULA)</2>",
|
"By clicking \"Go\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Mit einem Klick auf „Los geht’s“ akzeptierst du unseren <2>Endbenutzer-Lizenzvertrag (EULA)</2>",
|
||||||
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "Diese Seite wird durch reCAPTCHA geschützt und es gelten Googles <2>Datenschutzerklärung</2> und <6>Nutzungsbedingungen</6>. <9></9>Mit einem Klick auf „Registrieren“ akzeptierst du unseren <2>Endbenutzer-Lizenzvertrag (EULA)</2>",
|
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "Diese Seite wird durch reCAPTCHA geschützt und es gelten Googles <2>Datenschutzerklärung</2> und <6>Nutzungsbedingungen</6>. <9></9>Mit einem Klick auf „Registrieren“ akzeptierst du unseren <2>Endbenutzer-Lizenzvertrag (EULA)</2>",
|
||||||
"Element Call is temporarily not end-to-end encrypted while we test scalability.": "Element Call ist temporär nicht Ende-zu-Ende-verschlüsselt, während wir die Skalierbarkeit testen."
|
"Element Call is temporarily not end-to-end encrypted while we test scalability.": "Element Call ist temporär nicht Ende-zu-Ende-verschlüsselt, während wir die Skalierbarkeit testen.",
|
||||||
|
"Connectivity to the server has been lost.": "Die Verbindung zum Server wurde getrennt."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
"Download debug logs": "Download debug logs",
|
"Download debug logs": "Download debug logs",
|
||||||
"Element Call Home": "Element Call Home",
|
"Element Call Home": "Element Call Home",
|
||||||
"Element Call is temporarily not end-to-end encrypted while we test scalability.": "Element Call is temporarily not end-to-end encrypted while we test scalability.",
|
"Element Call is temporarily not end-to-end encrypted while we test scalability.": "Element Call is temporarily not end-to-end encrypted while we test scalability.",
|
||||||
|
"Enable end-to-end encryption (password protected calls)": "Enable end-to-end encryption (password protected calls)",
|
||||||
"Exit full screen": "Exit full screen",
|
"Exit full screen": "Exit full screen",
|
||||||
"Expose developer settings in the settings window.": "Expose developer settings in the settings window.",
|
"Expose developer settings in the settings window.": "Expose developer settings in the settings window.",
|
||||||
"Feedback": "Feedback",
|
"Feedback": "Feedback",
|
||||||
@@ -73,13 +74,16 @@
|
|||||||
"Not registered yet? <2>Create an account</2>": "Not registered yet? <2>Create an account</2>",
|
"Not registered yet? <2>Create an account</2>": "Not registered yet? <2>Create an account</2>",
|
||||||
"Other users are trying to join this call from incompatible versions. These users should ensure that they have refreshed their browsers:<1>{userLis}</1>": "Other users are trying to join this call from incompatible versions. These users should ensure that they have refreshed their browsers:<1>{userLis}</1>",
|
"Other users are trying to join this call from incompatible versions. These users should ensure that they have refreshed their browsers:<1>{userLis}</1>": "Other users are trying to join this call from incompatible versions. These users should ensure that they have refreshed their browsers:<1>{userLis}</1>",
|
||||||
"Password": "Password",
|
"Password": "Password",
|
||||||
|
"Password (if none, E2EE is disabled)": "Password (if none, E2EE is disabled)",
|
||||||
"Passwords must match": "Passwords must match",
|
"Passwords must match": "Passwords must match",
|
||||||
"Profile": "Profile",
|
"Profile": "Profile",
|
||||||
"Recaptcha dismissed": "Recaptcha dismissed",
|
"Recaptcha dismissed": "Recaptcha dismissed",
|
||||||
"Recaptcha not loaded": "Recaptcha not loaded",
|
"Recaptcha not loaded": "Recaptcha not loaded",
|
||||||
|
"Reconnect": "Reconnect",
|
||||||
"Register": "Register",
|
"Register": "Register",
|
||||||
"Registering…": "Registering…",
|
"Registering…": "Registering…",
|
||||||
"Remove": "Remove",
|
"Remove": "Remove",
|
||||||
|
"Retry sending logs": "Retry sending logs",
|
||||||
"Return to home screen": "Return to home screen",
|
"Return to home screen": "Return to home screen",
|
||||||
"Select an option": "Select an option",
|
"Select an option": "Select an option",
|
||||||
"Send debug logs": "Send debug logs",
|
"Send debug logs": "Send debug logs",
|
||||||
@@ -99,7 +103,7 @@
|
|||||||
"Submitting…": "Submitting…",
|
"Submitting…": "Submitting…",
|
||||||
"Take me Home": "Take me Home",
|
"Take me Home": "Take me Home",
|
||||||
"Thanks, we received your feedback!": "Thanks, we received your feedback!",
|
"Thanks, we received your feedback!": "Thanks, we received your feedback!",
|
||||||
"Thanks! We'll get right on it.": "Thanks! We'll get right on it.",
|
"Thanks!": "Thanks!",
|
||||||
"This call already exists, would you like to join?": "This call already exists, would you like to join?",
|
"This call already exists, would you like to join?": "This call already exists, would you like to join?",
|
||||||
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>",
|
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>",
|
||||||
"Turn off camera": "Turn off camera",
|
"Turn off camera": "Turn off camera",
|
||||||
@@ -116,6 +120,7 @@
|
|||||||
"Walkie-talkie call name": "Walkie-talkie call name",
|
"Walkie-talkie call name": "Walkie-talkie call name",
|
||||||
"WebRTC is not supported or is being blocked in this browser.": "WebRTC is not supported or is being blocked in this browser.",
|
"WebRTC is not supported or is being blocked in this browser.": "WebRTC is not supported or is being blocked in this browser.",
|
||||||
"Yes, join call": "Yes, join call",
|
"Yes, join call": "Yes, join call",
|
||||||
|
"You were disconnected from the call": "You were disconnected from the call",
|
||||||
"Your feedback": "Your feedback",
|
"Your feedback": "Your feedback",
|
||||||
"Your recent calls": "Your recent calls"
|
"Your recent calls": "Your recent calls"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,5 +116,6 @@
|
|||||||
"By clicking \"Go\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Klõpsides „Jätka“, nõustud sa meie <2>Lõppkasutaja litsentsilepinguga (EULA)</2>",
|
"By clicking \"Go\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Klõpsides „Jätka“, nõustud sa meie <2>Lõppkasutaja litsentsilepinguga (EULA)</2>",
|
||||||
"By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Klõpsides „Liitu kõnega kohe“, nõustud sa meie <2>Lõppkasutaja litsentsilepinguga (EULA)</2>",
|
"By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Klõpsides „Liitu kõnega kohe“, nõustud sa meie <2>Lõppkasutaja litsentsilepinguga (EULA)</2>",
|
||||||
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "Selles saidis on kasutusel ReCAPTCHA ja kehtivad Google'i <2>Privaatsuspoliitika</2> ning <6>Teenusetingimused</6>.<9></9>Klõpsides „Registreeru“, sa nõustud meie <12>Lõppkasutaja litsentsilepingu (EULA) tingimustega</12>",
|
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "Selles saidis on kasutusel ReCAPTCHA ja kehtivad Google'i <2>Privaatsuspoliitika</2> ning <6>Teenusetingimused</6>.<9></9>Klõpsides „Registreeru“, sa nõustud meie <12>Lõppkasutaja litsentsilepingu (EULA) tingimustega</12>",
|
||||||
"Element Call is temporarily not end-to-end encrypted while we test scalability.": "Seni kuni me testime skaleeritavust, siis Element Call ajutiselt pole läbivalt krüptitud."
|
"Element Call is temporarily not end-to-end encrypted while we test scalability.": "Seni kuni me testime skaleeritavust, siis Element Call ajutiselt pole läbivalt krüptitud.",
|
||||||
|
"Connectivity to the server has been lost.": "Võrguühendus serveriga on katkenud."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,5 +116,6 @@
|
|||||||
"By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Kliknutím na \"Pripojiť sa k hovoru teraz\" súhlasíte s našou <2>Licenčnou zmluvou s koncovým používateľom (EULA)</2>",
|
"By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Kliknutím na \"Pripojiť sa k hovoru teraz\" súhlasíte s našou <2>Licenčnou zmluvou s koncovým používateľom (EULA)</2>",
|
||||||
"By clicking \"Go\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Kliknutím na tlačidlo \"Prejsť\" vyjadrujete súhlas s našou <2>Licenčnou zmluvou s koncovým používateľom (EULA)</2>",
|
"By clicking \"Go\", you agree to our <2>End User Licensing Agreement (EULA)</2>": "Kliknutím na tlačidlo \"Prejsť\" vyjadrujete súhlas s našou <2>Licenčnou zmluvou s koncovým používateľom (EULA)</2>",
|
||||||
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "Táto stránka je chránená systémom ReCAPTCHA a platia na ňu <2>Pravidlá ochrany osobných údajov spoločnosti Google</2> a <6>Podmienky poskytovania služieb</6>.<9></9>Kliknutím na tlačidlo \"Registrovať sa\" súhlasíte s našou <12>Licenčnou zmluvou s koncovým používateľom (EULA)</12>",
|
"This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>": "Táto stránka je chránená systémom ReCAPTCHA a platia na ňu <2>Pravidlá ochrany osobných údajov spoločnosti Google</2> a <6>Podmienky poskytovania služieb</6>.<9></9>Kliknutím na tlačidlo \"Registrovať sa\" súhlasíte s našou <12>Licenčnou zmluvou s koncovým používateľom (EULA)</12>",
|
||||||
"Element Call is temporarily not end-to-end encrypted while we test scalability.": "Element Call nie je dočasne šifrovaný, kým testujeme škálovateľnosť."
|
"Element Call is temporarily not end-to-end encrypted while we test scalability.": "Element Call nie je dočasne šifrovaný, kým testujeme škálovateľnosť.",
|
||||||
|
"Connectivity to the server has been lost.": "Spojenie so serverom sa stratilo."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,11 +22,10 @@ import * as Sentry from "@sentry/react";
|
|||||||
|
|
||||||
import { Header, HeaderLogo, LeftNav, RightNav } from "./Header";
|
import { Header, HeaderLogo, LeftNav, RightNav } from "./Header";
|
||||||
import { LinkButton, Button } from "./button";
|
import { LinkButton, Button } from "./button";
|
||||||
import { useSubmitRageshake } from "./settings/submit-rageshake";
|
|
||||||
import { ErrorMessage } from "./input/Input";
|
|
||||||
import styles from "./FullScreenView.module.css";
|
import styles from "./FullScreenView.module.css";
|
||||||
import { translatedError, TranslatedError } from "./TranslatedError";
|
import { TranslatedError } from "./TranslatedError";
|
||||||
import { Config } from "./config/Config";
|
import { Config } from "./config/Config";
|
||||||
|
import { RageshakeButton } from "./settings/RageshakeButton";
|
||||||
|
|
||||||
interface FullScreenViewProps {
|
interface FullScreenViewProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -99,37 +98,11 @@ export function ErrorView({ error }: ErrorViewProps) {
|
|||||||
|
|
||||||
export function CrashView() {
|
export function CrashView() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { submitRageshake, sending, sent, error } = useSubmitRageshake();
|
|
||||||
|
|
||||||
const sendDebugLogs = useCallback(() => {
|
|
||||||
submitRageshake({
|
|
||||||
description: "**Soft Crash**",
|
|
||||||
sendLogs: true,
|
|
||||||
});
|
|
||||||
}, [submitRageshake]);
|
|
||||||
|
|
||||||
const onReload = useCallback(() => {
|
const onReload = useCallback(() => {
|
||||||
window.location.href = "/";
|
window.location.href = "/";
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
let logsComponent: JSX.Element | null = null;
|
|
||||||
if (sent) {
|
|
||||||
logsComponent = <div>{t("Thanks! We'll get right on it.")}</div>;
|
|
||||||
} else if (sending) {
|
|
||||||
logsComponent = <div>{t("Sending…")}</div>;
|
|
||||||
} else if (Config.get().rageshake?.submit_url) {
|
|
||||||
logsComponent = (
|
|
||||||
<Button
|
|
||||||
size="lg"
|
|
||||||
variant="default"
|
|
||||||
onPress={sendDebugLogs}
|
|
||||||
className={styles.wideButton}
|
|
||||||
>
|
|
||||||
{t("Send debug logs")}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FullScreenView>
|
<FullScreenView>
|
||||||
<Trans>
|
<Trans>
|
||||||
@@ -141,10 +114,7 @@ export function CrashView() {
|
|||||||
</Trans>
|
</Trans>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={styles.sendLogsSection}>{logsComponent}</div>
|
<RageshakeButton description="***Soft Crash***" />
|
||||||
{error && (
|
|
||||||
<ErrorMessage error={translatedError("Couldn't send debug logs!", t)} />
|
|
||||||
)}
|
|
||||||
<Button
|
<Button
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="default"
|
variant="default"
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
MuteMicrophoneTracker,
|
MuteMicrophoneTracker,
|
||||||
UndecryptableToDeviceEventTracker,
|
UndecryptableToDeviceEventTracker,
|
||||||
QualitySurveyEventTracker,
|
QualitySurveyEventTracker,
|
||||||
|
CallDisconnectedEventTracker,
|
||||||
} from "./PosthogEvents";
|
} from "./PosthogEvents";
|
||||||
import { Config } from "../config/Config";
|
import { Config } from "../config/Config";
|
||||||
import { getUrlParams } from "../UrlParams";
|
import { getUrlParams } from "../UrlParams";
|
||||||
@@ -437,4 +438,5 @@ export class PosthogAnalytics {
|
|||||||
public eventMuteCamera = new MuteCameraTracker();
|
public eventMuteCamera = new MuteCameraTracker();
|
||||||
public eventUndecryptableToDevice = new UndecryptableToDeviceEventTracker();
|
public eventUndecryptableToDevice = new UndecryptableToDeviceEventTracker();
|
||||||
public eventQualitySurvey = new QualitySurveyEventTracker();
|
public eventQualitySurvey = new QualitySurveyEventTracker();
|
||||||
|
public eventCallDisconnected = new CallDisconnectedEventTracker();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { DisconnectReason } from "livekit-client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IPosthogEvent,
|
IPosthogEvent,
|
||||||
PosthogAnalytics,
|
PosthogAnalytics,
|
||||||
@@ -181,3 +183,17 @@ export class QualitySurveyEventTracker {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CallDisconnectedEvent {
|
||||||
|
eventName: "CallDisconnected";
|
||||||
|
reason?: DisconnectReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CallDisconnectedEventTracker {
|
||||||
|
track(reason?: DisconnectReason) {
|
||||||
|
PosthogAnalytics.instance.trackEvent<CallDisconnectedEvent>({
|
||||||
|
eventName: "CallDisconnected",
|
||||||
|
reason,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import { Room, RoomOptions, setLogLevel } from "livekit-client";
|
import {
|
||||||
|
E2EEOptions,
|
||||||
|
ExternalE2EEKeyProvider,
|
||||||
|
Room,
|
||||||
|
RoomOptions,
|
||||||
|
setLogLevel,
|
||||||
|
} from "livekit-client";
|
||||||
import { useLiveKitRoom } from "@livekit/components-react";
|
import { useLiveKitRoom } from "@livekit/components-react";
|
||||||
import { useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
|
import E2EEWorker from "livekit-client/e2ee-worker?worker";
|
||||||
|
|
||||||
import { defaultLiveKitOptions } from "./options";
|
import { defaultLiveKitOptions } from "./options";
|
||||||
import { SFUConfig } from "./openIDSFU";
|
import { SFUConfig } from "./openIDSFU";
|
||||||
@@ -15,12 +22,34 @@ export type DeviceChoices = {
|
|||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type E2EEConfig = {
|
||||||
|
sharedKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
setLogLevel("debug");
|
setLogLevel("debug");
|
||||||
|
|
||||||
export function useLiveKit(
|
export function useLiveKit(
|
||||||
userChoices: UserChoices,
|
userChoices: UserChoices,
|
||||||
sfuConfig?: SFUConfig
|
sfuConfig?: SFUConfig,
|
||||||
|
e2eeConfig?: E2EEConfig
|
||||||
): Room | undefined {
|
): Room | undefined {
|
||||||
|
const e2eeOptions = useMemo(() => {
|
||||||
|
if (!e2eeConfig?.sharedKey) return undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
keyProvider: new ExternalE2EEKeyProvider(),
|
||||||
|
worker: new E2EEWorker(),
|
||||||
|
} as E2EEOptions;
|
||||||
|
}, [e2eeConfig]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!e2eeConfig?.sharedKey || !e2eeOptions) return;
|
||||||
|
|
||||||
|
(e2eeOptions.keyProvider as ExternalE2EEKeyProvider).setKey(
|
||||||
|
e2eeConfig?.sharedKey
|
||||||
|
);
|
||||||
|
}, [e2eeOptions, e2eeConfig?.sharedKey]);
|
||||||
|
|
||||||
const roomOptions = useMemo((): RoomOptions => {
|
const roomOptions = useMemo((): RoomOptions => {
|
||||||
const options = defaultLiveKitOptions;
|
const options = defaultLiveKitOptions;
|
||||||
options.videoCaptureDefaults = {
|
options.videoCaptureDefaults = {
|
||||||
@@ -31,15 +60,22 @@ export function useLiveKit(
|
|||||||
...options.audioCaptureDefaults,
|
...options.audioCaptureDefaults,
|
||||||
deviceId: userChoices.audio?.selectedId,
|
deviceId: userChoices.audio?.selectedId,
|
||||||
};
|
};
|
||||||
return options;
|
|
||||||
}, [userChoices.video, userChoices.audio]);
|
|
||||||
|
|
||||||
|
options.e2ee = e2eeOptions;
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}, [userChoices.video, userChoices.audio, e2eeOptions]);
|
||||||
|
|
||||||
|
// We have to create the room manually here due to a bug inside
|
||||||
|
// @livekit/components-react. JSON.stringify() is used in deps of a
|
||||||
|
// useEffect() with an argument that references itself, if E2EE is enabled
|
||||||
|
const roomWithoutProps = useMemo(() => new Room(roomOptions), [roomOptions]);
|
||||||
const { room } = useLiveKitRoom({
|
const { room } = useLiveKitRoom({
|
||||||
token: sfuConfig?.jwt,
|
token: sfuConfig?.jwt,
|
||||||
serverUrl: sfuConfig?.url,
|
serverUrl: sfuConfig?.url,
|
||||||
audio: userChoices.audio?.enabled ?? false,
|
audio: userChoices.audio?.enabled ?? false,
|
||||||
video: userChoices.video?.enabled ?? false,
|
video: userChoices.video?.enabled ?? false,
|
||||||
options: roomOptions,
|
room: roomWithoutProps,
|
||||||
});
|
});
|
||||||
|
|
||||||
return room;
|
return room;
|
||||||
|
|||||||
@@ -31,6 +31,15 @@ limitations under the License.
|
|||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.disconnectedButtons {
|
||||||
|
display: grid;
|
||||||
|
gap: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rageshakeButton {
|
||||||
|
grid-column: 2;
|
||||||
|
}
|
||||||
|
|
||||||
.callEndedButton {
|
.callEndedButton {
|
||||||
margin-top: 54px;
|
margin-top: 54px;
|
||||||
margin-left: 30px;
|
margin-left: 30px;
|
||||||
|
|||||||
@@ -28,15 +28,20 @@ import { Header, HeaderLogo, LeftNav, RightNav } from "../Header";
|
|||||||
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
||||||
import { FieldRow, InputField } from "../input/Input";
|
import { FieldRow, InputField } from "../input/Input";
|
||||||
import { StarRatingInput } from "../input/StarRatingInput";
|
import { StarRatingInput } from "../input/StarRatingInput";
|
||||||
|
import { RageshakeButton } from "../settings/RageshakeButton";
|
||||||
|
|
||||||
export function CallEndedView({
|
export function CallEndedView({
|
||||||
client,
|
client,
|
||||||
isPasswordlessUser,
|
isPasswordlessUser,
|
||||||
endedCallId,
|
endedCallId,
|
||||||
|
leaveError,
|
||||||
|
reconnect,
|
||||||
}: {
|
}: {
|
||||||
client: MatrixClient;
|
client: MatrixClient;
|
||||||
isPasswordlessUser: boolean;
|
isPasswordlessUser: boolean;
|
||||||
endedCallId: string;
|
endedCallId: string;
|
||||||
|
leaveError?: Error;
|
||||||
|
reconnect: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@@ -76,6 +81,7 @@ export function CallEndedView({
|
|||||||
},
|
},
|
||||||
[endedCallId, history, isPasswordlessUser, starRating]
|
[endedCallId, history, isPasswordlessUser, starRating]
|
||||||
);
|
);
|
||||||
|
|
||||||
const createAccountDialog = isPasswordlessUser && (
|
const createAccountDialog = isPasswordlessUser && (
|
||||||
<div className={styles.callEndedContent}>
|
<div className={styles.callEndedContent}>
|
||||||
<Trans>
|
<Trans>
|
||||||
@@ -138,6 +144,59 @@ export function CallEndedView({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const renderBody = () => {
|
||||||
|
if (leaveError) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<main className={styles.main}>
|
||||||
|
<Headline className={styles.headline}>
|
||||||
|
<Trans>You were disconnected from the call</Trans>
|
||||||
|
</Headline>
|
||||||
|
<div className={styles.disconnectedButtons}>
|
||||||
|
<Button size="lg" variant="default" onClick={reconnect}>
|
||||||
|
{t("Reconnect")}
|
||||||
|
</Button>
|
||||||
|
<div className={styles.rageshakeButton}>
|
||||||
|
<RageshakeButton description="***Call disconnected***" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<Body className={styles.footer}>
|
||||||
|
<Link color="primary" to="/">
|
||||||
|
{t("Return to home screen")}
|
||||||
|
</Link>
|
||||||
|
</Body>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<main className={styles.main}>
|
||||||
|
<Headline className={styles.headline}>
|
||||||
|
{surveySubmitted
|
||||||
|
? t("{{displayName}}, your call has ended.", {
|
||||||
|
displayName,
|
||||||
|
})
|
||||||
|
: t("{{displayName}}, your call has ended.", {
|
||||||
|
displayName,
|
||||||
|
}) +
|
||||||
|
"\n" +
|
||||||
|
t("How did it go?")}
|
||||||
|
</Headline>
|
||||||
|
{!surveySubmitted && PosthogAnalytics.instance.isEnabled()
|
||||||
|
? qualitySurveyDialog
|
||||||
|
: createAccountDialog}
|
||||||
|
</main>
|
||||||
|
<Body className={styles.footer}>
|
||||||
|
<Link color="primary" to="/">
|
||||||
|
{t("Not now, return to home screen")}
|
||||||
|
</Link>
|
||||||
|
</Body>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header>
|
<Header>
|
||||||
@@ -146,29 +205,7 @@ export function CallEndedView({
|
|||||||
</LeftNav>
|
</LeftNav>
|
||||||
<RightNav />
|
<RightNav />
|
||||||
</Header>
|
</Header>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>{renderBody()}</div>
|
||||||
<main className={styles.main}>
|
|
||||||
<Headline className={styles.headline}>
|
|
||||||
{surveySubmitted
|
|
||||||
? t("{{displayName}}, your call has ended.", {
|
|
||||||
displayName,
|
|
||||||
})
|
|
||||||
: t("{{displayName}}, your call has ended.", {
|
|
||||||
displayName,
|
|
||||||
}) +
|
|
||||||
"\n" +
|
|
||||||
t("How did it go?")}
|
|
||||||
</Headline>
|
|
||||||
{!surveySubmitted && PosthogAnalytics.instance.isEnabled()
|
|
||||||
? qualitySurveyDialog
|
|
||||||
: createAccountDialog}
|
|
||||||
</main>
|
|
||||||
<Body className={styles.footer}>
|
|
||||||
<Link color="primary" to="/">
|
|
||||||
{t("Not now, return to home screen")}
|
|
||||||
</Link>
|
|
||||||
</Body>
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,10 +32,17 @@ import { CallEndedView } from "./CallEndedView";
|
|||||||
import { useSentryGroupCallHandler } from "./useSentryGroupCallHandler";
|
import { useSentryGroupCallHandler } from "./useSentryGroupCallHandler";
|
||||||
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
||||||
import { useProfile } from "../profile/useProfile";
|
import { useProfile } from "../profile/useProfile";
|
||||||
import { UserChoices } from "../livekit/useLiveKit";
|
import { E2EEConfig, UserChoices } from "../livekit/useLiveKit";
|
||||||
import { findDeviceByName } from "../media-utils";
|
import { findDeviceByName } from "../media-utils";
|
||||||
import { OpenIDLoader } from "../livekit/OpenIDLoader";
|
import { OpenIDLoader } from "../livekit/OpenIDLoader";
|
||||||
import { ActiveCall } from "./InCallView";
|
import { ActiveCall } from "./InCallView";
|
||||||
|
import { Config } from "../config/Config";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If there already is this many participants in the call, we automatically mute
|
||||||
|
* the user
|
||||||
|
*/
|
||||||
|
const MUTE_PARTICIPANT_COUNT = 8;
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@@ -164,42 +171,47 @@ export function GroupCallView({
|
|||||||
useSentryGroupCallHandler(groupCall);
|
useSentryGroupCallHandler(groupCall);
|
||||||
|
|
||||||
const [left, setLeft] = useState(false);
|
const [left, setLeft] = useState(false);
|
||||||
|
const [leaveError, setLeaveError] = useState<Error | undefined>(undefined);
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
const onLeave = useCallback(async () => {
|
const onLeave = useCallback(
|
||||||
setLeft(true);
|
async (leaveError?: Error) => {
|
||||||
|
setLeaveError(leaveError);
|
||||||
|
setLeft(true);
|
||||||
|
|
||||||
let participantCount = 0;
|
let participantCount = 0;
|
||||||
for (const deviceMap of groupCall.participants.values()) {
|
for (const deviceMap of groupCall.participants.values()) {
|
||||||
participantCount += deviceMap.size;
|
participantCount += deviceMap.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// In embedded/widget mode the iFrame will be killed right after the call ended prohibiting the posthog event from getting sent,
|
// In embedded/widget mode the iFrame will be killed right after the call ended prohibiting the posthog event from getting sent,
|
||||||
// therefore we want the event to be sent instantly without getting queued/batched.
|
// therefore we want the event to be sent instantly without getting queued/batched.
|
||||||
const sendInstantly = !!widget;
|
const sendInstantly = !!widget;
|
||||||
PosthogAnalytics.instance.eventCallEnded.track(
|
PosthogAnalytics.instance.eventCallEnded.track(
|
||||||
groupCall.groupCallId,
|
groupCall.groupCallId,
|
||||||
participantCount,
|
participantCount,
|
||||||
sendInstantly
|
sendInstantly
|
||||||
);
|
);
|
||||||
|
|
||||||
leave();
|
leave();
|
||||||
if (widget) {
|
if (widget) {
|
||||||
// we need to wait until the callEnded event is tracked. Otherwise the iFrame gets killed before the callEnded event got tracked.
|
// we need to wait until the callEnded event is tracked. Otherwise the iFrame gets killed before the callEnded event got tracked.
|
||||||
await new Promise((resolve) => window.setTimeout(resolve, 10)); // 10ms
|
await new Promise((resolve) => window.setTimeout(resolve, 10)); // 10ms
|
||||||
widget.api.setAlwaysOnScreen(false);
|
widget.api.setAlwaysOnScreen(false);
|
||||||
PosthogAnalytics.instance.logout();
|
PosthogAnalytics.instance.logout();
|
||||||
widget.api.transport.send(ElementWidgetActions.HangupCall, {});
|
widget.api.transport.send(ElementWidgetActions.HangupCall, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!isPasswordlessUser &&
|
!isPasswordlessUser &&
|
||||||
!isEmbedded &&
|
!isEmbedded &&
|
||||||
!PosthogAnalytics.instance.isEnabled()
|
!PosthogAnalytics.instance.isEnabled()
|
||||||
) {
|
) {
|
||||||
history.push("/");
|
history.push("/");
|
||||||
}
|
}
|
||||||
}, [groupCall, leave, isPasswordlessUser, isEmbedded, history]);
|
},
|
||||||
|
[groupCall, leave, isPasswordlessUser, isEmbedded, history]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (widget && state === GroupCallState.Entered) {
|
if (widget && state === GroupCallState.Entered) {
|
||||||
@@ -218,6 +230,21 @@ export function GroupCallView({
|
|||||||
const [userChoices, setUserChoices] = useState<UserChoices | undefined>(
|
const [userChoices, setUserChoices] = useState<UserChoices | undefined>(
|
||||||
undefined
|
undefined
|
||||||
);
|
);
|
||||||
|
const [e2eeConfig, setE2EEConfig] = useState<E2EEConfig | undefined>(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
const onReconnect = useCallback(() => {
|
||||||
|
setLeft(false);
|
||||||
|
setLeaveError(undefined);
|
||||||
|
groupCall.enter();
|
||||||
|
}, [groupCall]);
|
||||||
|
|
||||||
|
const livekitServiceURL =
|
||||||
|
groupCall.livekitServiceURL ?? Config.get().livekit?.livekit_service_url;
|
||||||
|
if (!livekitServiceURL) {
|
||||||
|
return <ErrorView error={new Error("No livekit_service_url defined")} />;
|
||||||
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <ErrorView error={error} />;
|
return <ErrorView error={error} />;
|
||||||
@@ -236,6 +263,7 @@ export function GroupCallView({
|
|||||||
unencryptedEventsFromUsers={unencryptedEventsFromUsers}
|
unencryptedEventsFromUsers={unencryptedEventsFromUsers}
|
||||||
hideHeader={hideHeader}
|
hideHeader={hideHeader}
|
||||||
userChoices={userChoices}
|
userChoices={userChoices}
|
||||||
|
e2eeConfig={e2eeConfig}
|
||||||
otelGroupCallMembership={otelGroupCallMembership}
|
otelGroupCallMembership={otelGroupCallMembership}
|
||||||
/>
|
/>
|
||||||
</OpenIDLoader>
|
</OpenIDLoader>
|
||||||
@@ -249,13 +277,16 @@ export function GroupCallView({
|
|||||||
// submitting anything.
|
// submitting anything.
|
||||||
if (
|
if (
|
||||||
isPasswordlessUser ||
|
isPasswordlessUser ||
|
||||||
(PosthogAnalytics.instance.isEnabled() && !isEmbedded)
|
(PosthogAnalytics.instance.isEnabled() && !isEmbedded) ||
|
||||||
|
leaveError
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<CallEndedView
|
<CallEndedView
|
||||||
endedCallId={groupCall.groupCallId}
|
endedCallId={groupCall.groupCallId}
|
||||||
client={client}
|
client={client}
|
||||||
isPasswordlessUser={isPasswordlessUser}
|
isPasswordlessUser={isPasswordlessUser}
|
||||||
|
leaveError={leaveError}
|
||||||
|
reconnect={onReconnect}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -276,11 +307,12 @@ export function GroupCallView({
|
|||||||
return (
|
return (
|
||||||
<LobbyView
|
<LobbyView
|
||||||
matrixInfo={matrixInfo}
|
matrixInfo={matrixInfo}
|
||||||
onEnter={(choices: UserChoices) => {
|
onEnter={(choices: UserChoices, e2eeConfig?: E2EEConfig) => {
|
||||||
setUserChoices(choices);
|
setUserChoices(choices);
|
||||||
|
setE2EEConfig(e2eeConfig);
|
||||||
enter();
|
enter();
|
||||||
}}
|
}}
|
||||||
muteAudio={participants.size > 8}
|
initWithMutedAudio={participants.size > MUTE_PARTICIPANT_COUNT}
|
||||||
isEmbedded={isEmbedded}
|
isEmbedded={isEmbedded}
|
||||||
hideHeader={hideHeader}
|
hideHeader={hideHeader}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
} from "@livekit/components-react";
|
} from "@livekit/components-react";
|
||||||
import { usePreventScroll } from "@react-aria/overlays";
|
import { usePreventScroll } from "@react-aria/overlays";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Room, Track } from "livekit-client";
|
import { DisconnectReason, Room, RoomEvent, Track } from "livekit-client";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
import { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall";
|
import { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall";
|
||||||
@@ -33,6 +33,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import useMeasure from "react-use-measure";
|
import useMeasure from "react-use-measure";
|
||||||
import { OverlayTriggerState } from "@react-stately/overlays";
|
import { OverlayTriggerState } from "@react-stately/overlays";
|
||||||
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
||||||
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import type { IWidgetApiRequest } from "matrix-widget-api";
|
import type { IWidgetApiRequest } from "matrix-widget-api";
|
||||||
import {
|
import {
|
||||||
@@ -77,12 +78,13 @@ import { SettingsModal } from "../settings/SettingsModal";
|
|||||||
import { InviteModal } from "./InviteModal";
|
import { InviteModal } from "./InviteModal";
|
||||||
import { useRageshakeRequestModal } from "../settings/submit-rageshake";
|
import { useRageshakeRequestModal } from "../settings/submit-rageshake";
|
||||||
import { RageshakeRequestModal } from "./RageshakeRequestModal";
|
import { RageshakeRequestModal } from "./RageshakeRequestModal";
|
||||||
import { UserChoices, useLiveKit } from "../livekit/useLiveKit";
|
import { E2EEConfig, UserChoices, useLiveKit } from "../livekit/useLiveKit";
|
||||||
import { useMediaDevicesSwitcher } from "../livekit/useMediaDevicesSwitcher";
|
import { useMediaDevicesSwitcher } from "../livekit/useMediaDevicesSwitcher";
|
||||||
import { useFullscreen } from "./useFullscreen";
|
import { useFullscreen } from "./useFullscreen";
|
||||||
import { useLayoutStates } from "../video-grid/Layout";
|
import { useLayoutStates } from "../video-grid/Layout";
|
||||||
import { useSFUConfig } from "../livekit/OpenIDLoader";
|
import { useSFUConfig } from "../livekit/OpenIDLoader";
|
||||||
import { E2EELock } from "../E2EELock";
|
import { E2EELock } from "../E2EELock";
|
||||||
|
import { useEventEmitterThree } from "../useEvents";
|
||||||
import { useWakeLock } from "../useWakeLock";
|
import { useWakeLock } from "../useWakeLock";
|
||||||
|
|
||||||
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
|
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
|
||||||
@@ -93,16 +95,25 @@ const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
|||||||
|
|
||||||
export interface ActiveCallProps extends Omit<InCallViewProps, "livekitRoom"> {
|
export interface ActiveCallProps extends Omit<InCallViewProps, "livekitRoom"> {
|
||||||
userChoices: UserChoices;
|
userChoices: UserChoices;
|
||||||
|
e2eeConfig?: E2EEConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ActiveCall(props: ActiveCallProps) {
|
export function ActiveCall(props: ActiveCallProps) {
|
||||||
const sfuConfig = useSFUConfig();
|
const sfuConfig = useSFUConfig();
|
||||||
const livekitRoom = useLiveKit(props.userChoices, sfuConfig);
|
const livekitRoom = useLiveKit(
|
||||||
|
props.userChoices,
|
||||||
|
sfuConfig,
|
||||||
|
props.e2eeConfig
|
||||||
|
);
|
||||||
|
|
||||||
if (!livekitRoom) {
|
if (!livekitRoom) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (props.e2eeConfig && !livekitRoom.isE2EEEnabled) {
|
||||||
|
livekitRoom.setE2EEEnabled(!!props.e2eeConfig);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RoomContext.Provider value={livekitRoom}>
|
<RoomContext.Provider value={livekitRoom}>
|
||||||
<InCallView {...props} livekitRoom={livekitRoom} />
|
<InCallView {...props} livekitRoom={livekitRoom} />
|
||||||
@@ -115,7 +126,7 @@ export interface InCallViewProps {
|
|||||||
groupCall: GroupCall;
|
groupCall: GroupCall;
|
||||||
livekitRoom: Room;
|
livekitRoom: Room;
|
||||||
participants: Map<RoomMember, Map<string, ParticipantInfo>>;
|
participants: Map<RoomMember, Map<string, ParticipantInfo>>;
|
||||||
onLeave: () => void;
|
onLeave: (error?: Error) => void;
|
||||||
unencryptedEventsFromUsers: Set<string>;
|
unencryptedEventsFromUsers: Set<string>;
|
||||||
hideHeader: boolean;
|
hideHeader: boolean;
|
||||||
otelGroupCallMembership?: OTelGroupCallMembership;
|
otelGroupCallMembership?: OTelGroupCallMembership;
|
||||||
@@ -190,6 +201,23 @@ export function InCallView({
|
|||||||
async (muted) => await localParticipant.setMicrophoneEnabled(!muted)
|
async (muted) => await localParticipant.setMicrophoneEnabled(!muted)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onDisconnected = useCallback(
|
||||||
|
(reason?: DisconnectReason) => {
|
||||||
|
PosthogAnalytics.instance.eventCallDisconnected.track(reason);
|
||||||
|
logger.info("Disconnected from livekit call with reason ", reason);
|
||||||
|
onLeave(
|
||||||
|
new Error("Disconnected from LiveKit call with reason " + reason)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[onLeave]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onLeavePress = useCallback(() => {
|
||||||
|
onLeave();
|
||||||
|
}, [onLeave]);
|
||||||
|
|
||||||
|
useEventEmitterThree(livekitRoom, RoomEvent.Disconnected, onDisconnected);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
widget?.api.transport.send(
|
widget?.api.transport.send(
|
||||||
layout === "freedom"
|
layout === "freedom"
|
||||||
@@ -386,7 +414,7 @@ export function InCallView({
|
|||||||
}
|
}
|
||||||
|
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<HangupButton key="6" onPress={onLeave} data-testid="incall_leave" />
|
<HangupButton key="6" onPress={onLeavePress} data-testid="incall_leave" />
|
||||||
);
|
);
|
||||||
footer = <div className={styles.footer}>{buttons}</div>;
|
footer = <div className={styles.footer}>{buttons}</div>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,3 +66,9 @@ limitations under the License.
|
|||||||
.copyButton:last-child {
|
.copyButton:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.passwordField {
|
||||||
|
width: 320px !important;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
flex: 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useRef, useEffect, useState } from "react";
|
import { useRef, useEffect, useState, useCallback, ChangeEvent } from "react";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import styles from "./LobbyView.module.css";
|
import styles from "./LobbyView.module.css";
|
||||||
@@ -25,21 +25,25 @@ import { UserMenuContainer } from "../UserMenuContainer";
|
|||||||
import { Body, Link } from "../typography/Typography";
|
import { Body, Link } from "../typography/Typography";
|
||||||
import { useLocationNavigation } from "../useLocationNavigation";
|
import { useLocationNavigation } from "../useLocationNavigation";
|
||||||
import { MatrixInfo, VideoPreview } from "./VideoPreview";
|
import { MatrixInfo, VideoPreview } from "./VideoPreview";
|
||||||
import { UserChoices } from "../livekit/useLiveKit";
|
import { E2EEConfig, UserChoices } from "../livekit/useLiveKit";
|
||||||
|
import { InputField } from "../input/Input";
|
||||||
|
import { useEnableE2EE } from "../settings/useSetting";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
matrixInfo: MatrixInfo;
|
matrixInfo: MatrixInfo;
|
||||||
|
|
||||||
onEnter: (userChoices: UserChoices) => void;
|
onEnter: (userChoices: UserChoices, e2eeConfig?: E2EEConfig) => void;
|
||||||
isEmbedded: boolean;
|
isEmbedded: boolean;
|
||||||
hideHeader: boolean;
|
hideHeader: boolean;
|
||||||
muteAudio: boolean;
|
initWithMutedAudio: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LobbyView(props: Props) {
|
export function LobbyView(props: Props) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
useLocationNavigation();
|
useLocationNavigation();
|
||||||
|
|
||||||
|
const [enableE2EE] = useEnableE2EE();
|
||||||
|
|
||||||
const joinCallButtonRef = useRef<HTMLButtonElement>(null);
|
const joinCallButtonRef = useRef<HTMLButtonElement>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (joinCallButtonRef.current) {
|
if (joinCallButtonRef.current) {
|
||||||
@@ -50,6 +54,17 @@ export function LobbyView(props: Props) {
|
|||||||
const [userChoices, setUserChoices] = useState<UserChoices | undefined>(
|
const [userChoices, setUserChoices] = useState<UserChoices | undefined>(
|
||||||
undefined
|
undefined
|
||||||
);
|
);
|
||||||
|
const [e2eeSharedKey, setE2EESharedKey] = useState<string | undefined>(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
const onE2EESharedKeyChanged = useCallback(
|
||||||
|
(event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = event.target.value;
|
||||||
|
setE2EESharedKey(value === "" ? undefined : value);
|
||||||
|
},
|
||||||
|
[setE2EESharedKey]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.room}>
|
<div className={styles.room}>
|
||||||
@@ -67,15 +82,29 @@ export function LobbyView(props: Props) {
|
|||||||
<div className={styles.joinRoomContent}>
|
<div className={styles.joinRoomContent}>
|
||||||
<VideoPreview
|
<VideoPreview
|
||||||
matrixInfo={props.matrixInfo}
|
matrixInfo={props.matrixInfo}
|
||||||
muteAudio={props.muteAudio}
|
initWithMutedAudio={props.initWithMutedAudio}
|
||||||
onUserChoicesChanged={setUserChoices}
|
onUserChoicesChanged={setUserChoices}
|
||||||
/>
|
/>
|
||||||
|
{enableE2EE && (
|
||||||
|
<InputField
|
||||||
|
className={styles.passwordField}
|
||||||
|
label={t("Password (if none, E2EE is disabled)")}
|
||||||
|
type="text"
|
||||||
|
onChange={onE2EESharedKeyChanged}
|
||||||
|
value={e2eeSharedKey}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Trans>
|
<Trans>
|
||||||
<Button
|
<Button
|
||||||
ref={joinCallButtonRef}
|
ref={joinCallButtonRef}
|
||||||
className={styles.copyButton}
|
className={styles.copyButton}
|
||||||
size="lg"
|
size="lg"
|
||||||
onPress={() => props.onEnter(userChoices!)}
|
onPress={() =>
|
||||||
|
props.onEnter(
|
||||||
|
userChoices!,
|
||||||
|
e2eeSharedKey ? { sharedKey: e2eeSharedKey } : undefined
|
||||||
|
)
|
||||||
|
}
|
||||||
data-testid="lobby_joinCall"
|
data-testid="lobby_joinCall"
|
||||||
>
|
>
|
||||||
Join call now
|
Join call now
|
||||||
|
|||||||
@@ -41,13 +41,13 @@ export type MatrixInfo = {
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
matrixInfo: MatrixInfo;
|
matrixInfo: MatrixInfo;
|
||||||
muteAudio: boolean;
|
initWithMutedAudio: boolean;
|
||||||
onUserChoicesChanged: (choices: UserChoices) => void;
|
onUserChoicesChanged: (choices: UserChoices) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function VideoPreview({
|
export function VideoPreview({
|
||||||
matrixInfo,
|
matrixInfo,
|
||||||
muteAudio,
|
initWithMutedAudio,
|
||||||
onUserChoicesChanged,
|
onUserChoicesChanged,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const { client } = useClient();
|
const { client } = useClient();
|
||||||
@@ -70,13 +70,9 @@ export function VideoPreview({
|
|||||||
|
|
||||||
// Create local media tracks.
|
// Create local media tracks.
|
||||||
const [videoEnabled, setVideoEnabled] = useState<boolean>(true);
|
const [videoEnabled, setVideoEnabled] = useState<boolean>(true);
|
||||||
const [audioEnabled, setAudioEnabled] = useState<boolean>(!muteAudio);
|
const [audioEnabled, setAudioEnabled] = useState<boolean>(
|
||||||
|
!initWithMutedAudio
|
||||||
useEffect(() => {
|
);
|
||||||
if (muteAudio) {
|
|
||||||
setAudioEnabled(false);
|
|
||||||
}
|
|
||||||
}, [muteAudio]);
|
|
||||||
|
|
||||||
// The settings are updated as soon as the device changes. We wrap the settings value in a ref to store their initial value.
|
// The settings are updated as soon as the device changes. We wrap the settings value in a ref to store their initial value.
|
||||||
// Not changing the device options prohibits the usePreviewTracks hook to recreate the tracks.
|
// Not changing the device options prohibits the usePreviewTracks hook to recreate the tracks.
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ export function useGroupCall(
|
|||||||
isScreensharing: false,
|
isScreensharing: false,
|
||||||
screenshareFeeds: [],
|
screenshareFeeds: [],
|
||||||
requestingScreenshare: false,
|
requestingScreenshare: false,
|
||||||
participants: new Map(),
|
participants: getParticipants(groupCall),
|
||||||
hasLocalParticipant: false,
|
hasLocalParticipant: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
21
src/settings/RageshakeButton.module.css
Normal file
21
src/settings/RageshakeButton.module.css
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 New Vector Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.rageshakeControl {
|
||||||
|
height: 50px;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
67
src/settings/RageshakeButton.tsx
Normal file
67
src/settings/RageshakeButton.tsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 New Vector Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
|
||||||
|
import { Button } from "../button";
|
||||||
|
import { Config } from "../config/Config";
|
||||||
|
import styles from "./RageshakeButton.module.css";
|
||||||
|
import { useSubmitRageshake } from "./submit-rageshake";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RageshakeButton = ({ description }: Props) => {
|
||||||
|
const { submitRageshake, sending, sent, error } = useSubmitRageshake();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const sendDebugLogs = useCallback(() => {
|
||||||
|
submitRageshake({
|
||||||
|
description,
|
||||||
|
sendLogs: true,
|
||||||
|
});
|
||||||
|
}, [submitRageshake, description]);
|
||||||
|
|
||||||
|
if (!Config.get().rageshake?.submit_url) return null;
|
||||||
|
|
||||||
|
let logsComponent: JSX.Element | null = null;
|
||||||
|
if (sending) {
|
||||||
|
logsComponent = <span>{t("Sending…")}</span>;
|
||||||
|
} else if (sent) {
|
||||||
|
logsComponent = <div>{t("Thanks!")}</div>;
|
||||||
|
} else {
|
||||||
|
let caption = t("Send debug logs");
|
||||||
|
if (error) {
|
||||||
|
caption = t("Retry sending logs");
|
||||||
|
}
|
||||||
|
|
||||||
|
logsComponent = (
|
||||||
|
<Button
|
||||||
|
size="lg"
|
||||||
|
variant="default"
|
||||||
|
onPress={sendDebugLogs}
|
||||||
|
className={styles.wideButton}
|
||||||
|
disabled={sending}
|
||||||
|
>
|
||||||
|
{caption}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className={styles.rageshakeControl}>{logsComponent}</div>;
|
||||||
|
};
|
||||||
@@ -34,6 +34,7 @@ import {
|
|||||||
useOptInAnalytics,
|
useOptInAnalytics,
|
||||||
useDeveloperSettingsTab,
|
useDeveloperSettingsTab,
|
||||||
useShowConnectionStats,
|
useShowConnectionStats,
|
||||||
|
useEnableE2EE,
|
||||||
} from "./useSetting";
|
} from "./useSetting";
|
||||||
import { FieldRow, InputField } from "../input/Input";
|
import { FieldRow, InputField } from "../input/Input";
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
@@ -68,6 +69,7 @@ export const SettingsModal = (props: Props) => {
|
|||||||
useDeveloperSettingsTab();
|
useDeveloperSettingsTab();
|
||||||
const [showConnectionStats, setShowConnectionStats] =
|
const [showConnectionStats, setShowConnectionStats] =
|
||||||
useShowConnectionStats();
|
useShowConnectionStats();
|
||||||
|
const [enableE2EE, setEnableE2EE] = useEnableE2EE();
|
||||||
|
|
||||||
const downloadDebugLog = useDownloadDebugLog();
|
const downloadDebugLog = useDownloadDebugLog();
|
||||||
|
|
||||||
@@ -255,6 +257,18 @@ export const SettingsModal = (props: Props) => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
|
<FieldRow>
|
||||||
|
<InputField
|
||||||
|
id="enableE2EE"
|
||||||
|
name="end-to-end-encryption"
|
||||||
|
label={t("Enable end-to-end encryption (password protected calls)")}
|
||||||
|
type="checkbox"
|
||||||
|
checked={enableE2EE}
|
||||||
|
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
|
setEnableE2EE(e.target.checked)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
<FieldRow>
|
<FieldRow>
|
||||||
<Button onPress={downloadDebugLog}>{t("Download debug logs")}</Button>
|
<Button onPress={downloadDebugLog}>{t("Download debug logs")}</Button>
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
|
|||||||
@@ -104,6 +104,9 @@ export const useDeveloperSettingsTab = () =>
|
|||||||
export const useShowConnectionStats = () =>
|
export const useShowConnectionStats = () =>
|
||||||
useSetting("show-connection-stats", false);
|
useSetting("show-connection-stats", false);
|
||||||
|
|
||||||
|
export const useEnableE2EE = () =>
|
||||||
|
useSetting("enable-end-to-end-encryption", false);
|
||||||
|
|
||||||
export const useDefaultDevices = () =>
|
export const useDefaultDevices = () =>
|
||||||
useSetting("defaultDevices", {
|
useSetting("defaultDevices", {
|
||||||
audioinput: "",
|
audioinput: "",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
import EventEmitter from "eventemitter3";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Listener,
|
Listener,
|
||||||
@@ -59,3 +60,20 @@ export const useTypedEventEmitter = <
|
|||||||
};
|
};
|
||||||
}, [emitter, eventType, listener]);
|
}, [emitter, eventType, listener]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Shortcut for registering a listener on an eventemitter3 EventEmitter (ie. what the LiveKit SDK uses)
|
||||||
|
export const useEventEmitterThree = <
|
||||||
|
EventType extends EventEmitter.ValidEventTypes,
|
||||||
|
T extends EventEmitter.EventNames<EventType>
|
||||||
|
>(
|
||||||
|
emitter: EventEmitter<EventType>,
|
||||||
|
eventType: T,
|
||||||
|
listener: EventEmitter.EventListener<EventType, T>
|
||||||
|
) => {
|
||||||
|
useEffect(() => {
|
||||||
|
emitter.on(eventType, listener);
|
||||||
|
return () => {
|
||||||
|
emitter.off(eventType, listener);
|
||||||
|
};
|
||||||
|
}, [emitter, eventType, listener]);
|
||||||
|
};
|
||||||
|
|||||||
@@ -121,6 +121,15 @@ export const VideoTile = forwardRef<HTMLDivElement, Props>(
|
|||||||
|
|
||||||
const toolbarButtons: JSX.Element[] = [];
|
const toolbarButtons: JSX.Element[] = [];
|
||||||
if (!sfuParticipant.isLocal) {
|
if (!sfuParticipant.isLocal) {
|
||||||
|
toolbarButtons.push(
|
||||||
|
<AudioButton
|
||||||
|
key="localVolume"
|
||||||
|
className={styles.button}
|
||||||
|
volume={(sfuParticipant as RemoteParticipant).getVolume() ?? 0}
|
||||||
|
onPress={onOptionsPress}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
if (content === TileContent.ScreenShare) {
|
if (content === TileContent.ScreenShare) {
|
||||||
toolbarButtons.push(
|
toolbarButtons.push(
|
||||||
<FullscreenButton
|
<FullscreenButton
|
||||||
@@ -130,16 +139,6 @@ export const VideoTile = forwardRef<HTMLDivElement, Props>(
|
|||||||
onPress={onFullscreen}
|
onPress={onFullscreen}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
// Due to the LK SDK this sadly only works for user-media atm
|
|
||||||
toolbarButtons.push(
|
|
||||||
<AudioButton
|
|
||||||
key="localVolume"
|
|
||||||
className={styles.button}
|
|
||||||
volume={(sfuParticipant as RemoteParticipant).getVolume() ?? 0}
|
|
||||||
onPress={onOptionsPress}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,29 +16,36 @@ limitations under the License.
|
|||||||
|
|
||||||
import React, { ChangeEvent, useState } from "react";
|
import React, { ChangeEvent, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { RemoteParticipant } from "livekit-client";
|
import { RemoteParticipant, Track } from "livekit-client";
|
||||||
|
|
||||||
import { FieldRow } from "../input/Input";
|
import { FieldRow } from "../input/Input";
|
||||||
import { Modal } from "../Modal";
|
import { Modal } from "../Modal";
|
||||||
import styles from "./VideoTileSettingsModal.module.css";
|
import styles from "./VideoTileSettingsModal.module.css";
|
||||||
import { VolumeIcon } from "../button/VolumeIcon";
|
import { VolumeIcon } from "../button/VolumeIcon";
|
||||||
import { ItemData } from "./VideoTile";
|
import { ItemData, TileContent } from "./VideoTile";
|
||||||
|
|
||||||
interface LocalVolumeProps {
|
interface LocalVolumeProps {
|
||||||
participant: RemoteParticipant;
|
participant: RemoteParticipant;
|
||||||
|
content: TileContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LocalVolume: React.FC<LocalVolumeProps> = ({
|
const LocalVolume: React.FC<LocalVolumeProps> = ({
|
||||||
participant,
|
participant,
|
||||||
|
content,
|
||||||
}: LocalVolumeProps) => {
|
}: LocalVolumeProps) => {
|
||||||
|
const source =
|
||||||
|
content === TileContent.UserMedia
|
||||||
|
? Track.Source.Microphone
|
||||||
|
: Track.Source.ScreenShareAudio;
|
||||||
|
|
||||||
const [localVolume, setLocalVolume] = useState<number>(
|
const [localVolume, setLocalVolume] = useState<number>(
|
||||||
participant.getVolume() ?? 0
|
participant.getVolume(source) ?? 0
|
||||||
);
|
);
|
||||||
|
|
||||||
const onLocalVolumeChanged = (event: ChangeEvent<HTMLInputElement>) => {
|
const onLocalVolumeChanged = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
const value: number = +event.target.value;
|
const value: number = +event.target.value;
|
||||||
setLocalVolume(value);
|
setLocalVolume(value);
|
||||||
participant.setVolume(value);
|
participant.setVolume(value, source);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -78,7 +85,10 @@ export const VideoTileSettingsModal = ({ data, onClose, ...rest }: Props) => {
|
|||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<LocalVolume participant={data.sfuParticipant as RemoteParticipant} />
|
<LocalVolume
|
||||||
|
participant={data.sfuParticipant as RemoteParticipant}
|
||||||
|
content={data.content}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
25
yarn.lock
25
yarn.lock
@@ -2217,7 +2217,7 @@
|
|||||||
"@react-hook/latest" "^1.0.3"
|
"@react-hook/latest" "^1.0.3"
|
||||||
clsx "^1.2.1"
|
clsx "^1.2.1"
|
||||||
|
|
||||||
"@matrix-org/matrix-sdk-crypto-js@^0.1.0":
|
"@matrix-org/matrix-sdk-crypto-js@^0.1.1":
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.4.tgz#c13c7c8c3a1d8da08e6ad195d25e5e61cc402df7"
|
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.4.tgz#c13c7c8c3a1d8da08e6ad195d25e5e61cc402df7"
|
||||||
integrity sha512-OxG84iSeR89zYLFeb+DCaFtZT+DDiIu+kTkqY8OYfhE5vpGLFX2sDVBRrAdos1IUqEoboDloDBR9+yU7hNRyog==
|
integrity sha512-OxG84iSeR89zYLFeb+DCaFtZT+DDiIu+kTkqY8OYfhE5vpGLFX2sDVBRrAdos1IUqEoboDloDBR9+yU7hNRyog==
|
||||||
@@ -6450,6 +6450,11 @@ crypto-browserify@^3.11.0:
|
|||||||
randombytes "^2.0.0"
|
randombytes "^2.0.0"
|
||||||
randomfill "^1.0.3"
|
randomfill "^1.0.3"
|
||||||
|
|
||||||
|
crypto-js@^4.1.1:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf"
|
||||||
|
integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==
|
||||||
|
|
||||||
css-blank-pseudo@^3.0.3:
|
css-blank-pseudo@^3.0.3:
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz#36523b01c12a25d812df343a32c322d2a2324561"
|
resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz#36523b01c12a25d812df343a32c322d2a2324561"
|
||||||
@@ -10751,10 +10756,10 @@ lines-and-columns@^1.1.6:
|
|||||||
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
|
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
|
||||||
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
|
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
|
||||||
|
|
||||||
livekit-client@1.12.0:
|
livekit-client@1.12.1:
|
||||||
version "1.12.0"
|
version "1.12.1"
|
||||||
resolved "https://registry.yarnpkg.com/livekit-client/-/livekit-client-1.12.0.tgz#4b4f18087331d4893adaccb148e33fe870eb9a2e"
|
resolved "https://registry.yarnpkg.com/livekit-client/-/livekit-client-1.12.1.tgz#b927b4fb07d2d64543d25a99db36ffbb7caa23e6"
|
||||||
integrity sha512-G1KHNMSaEMXtPIKxTQt+WH/uRvhkQ0tQaZ5Kkem2CvB/5I4KPrV4DSmhBKP1P+8reHcaBBiye8V9bfpD69aHyQ==
|
integrity sha512-/mob04a/Mb0D+4sIzB7/pqakpJMCORSK+Qu5oTIcuSpgL+eBYGzHPE2sutGCGoe3Ns9sITAqUTyiui5+GN3i2w==
|
||||||
dependencies:
|
dependencies:
|
||||||
eventemitter3 "^5.0.1"
|
eventemitter3 "^5.0.1"
|
||||||
loglevel "^1.8.0"
|
loglevel "^1.8.0"
|
||||||
@@ -11005,7 +11010,7 @@ matrix-events-sdk@0.0.1:
|
|||||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/b698217445318f453e0b1086364a33113eaa85d9"
|
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/b698217445318f453e0b1086364a33113eaa85d9"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.12.5"
|
"@babel/runtime" "^7.12.5"
|
||||||
"@matrix-org/matrix-sdk-crypto-js" "^0.1.0"
|
"@matrix-org/matrix-sdk-crypto-js" "^0.1.1"
|
||||||
another-json "^0.2.0"
|
another-json "^0.2.0"
|
||||||
bs58 "^5.0.0"
|
bs58 "^5.0.0"
|
||||||
content-type "^1.0.4"
|
content-type "^1.0.4"
|
||||||
@@ -11700,6 +11705,14 @@ objectorarray@^1.0.5:
|
|||||||
resolved "https://registry.yarnpkg.com/objectorarray/-/objectorarray-1.0.5.tgz#2c05248bbefabd8f43ad13b41085951aac5e68a5"
|
resolved "https://registry.yarnpkg.com/objectorarray/-/objectorarray-1.0.5.tgz#2c05248bbefabd8f43ad13b41085951aac5e68a5"
|
||||||
integrity sha512-eJJDYkhJFFbBBAxeh8xW+weHlkI28n2ZdQV/J/DNfWfSKlGEf2xcfAbZTv3riEXHAhL9SVOTs2pRmXiSTf78xg==
|
integrity sha512-eJJDYkhJFFbBBAxeh8xW+weHlkI28n2ZdQV/J/DNfWfSKlGEf2xcfAbZTv3riEXHAhL9SVOTs2pRmXiSTf78xg==
|
||||||
|
|
||||||
|
oidc-client-ts@^2.2.4:
|
||||||
|
version "2.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/oidc-client-ts/-/oidc-client-ts-2.2.4.tgz#7d86b5efe2248f3637a6f3a0ee1af86764aea125"
|
||||||
|
integrity sha512-nOZwIomju+AmXObl5Oq5PjrES/qTt8bLsENJCIydVgi9TEWk7SCkOU6X3RNkY7yfySRM1OJJvDKdREZdmnDT2g==
|
||||||
|
dependencies:
|
||||||
|
crypto-js "^4.1.1"
|
||||||
|
jwt-decode "^3.1.2"
|
||||||
|
|
||||||
on-finished@2.4.1:
|
on-finished@2.4.1:
|
||||||
version "2.4.1"
|
version "2.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
|
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
|
||||||
|
|||||||
Reference in New Issue
Block a user