Merge pull request #1270 from vector-im/SimonBrandner/feat/e2ee-pw
This commit is contained in:
@@ -60,7 +60,7 @@
|
|||||||
"i18next-http-backend": "^1.4.4",
|
"i18next-http-backend": "^1.4.4",
|
||||||
"livekit-client": "1.12.0",
|
"livekit-client": "1.12.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#d79d9ae69c3220c02406706d4a1ec52c22c44fbd",
|
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#b698217445318f453e0b1086364a33113eaa85d9",
|
||||||
"matrix-widget-api": "^1.3.1",
|
"matrix-widget-api": "^1.3.1",
|
||||||
"mermaid": "^8.13.8",
|
"mermaid": "^8.13.8",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
|
|||||||
@@ -38,6 +38,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",
|
||||||
@@ -72,6 +73,7 @@
|
|||||||
"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",
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ import {
|
|||||||
TrackPublishDefaults,
|
TrackPublishDefaults,
|
||||||
VideoPreset,
|
VideoPreset,
|
||||||
VideoPresets,
|
VideoPresets,
|
||||||
ExternalE2EEKeyProvider,
|
|
||||||
} from "livekit-client";
|
} from "livekit-client";
|
||||||
import E2EEWorker from "livekit-client/e2ee-worker?worker";
|
|
||||||
|
|
||||||
const defaultLiveKitPublishOptions: TrackPublishDefaults = {
|
const defaultLiveKitPublishOptions: TrackPublishDefaults = {
|
||||||
audioPreset: AudioPresets.music,
|
audioPreset: AudioPresets.music,
|
||||||
@@ -24,16 +22,7 @@ const defaultLiveKitPublishOptions: TrackPublishDefaults = {
|
|||||||
backupCodec: { codec: "vp8", encoding: VideoPresets.h720.encoding },
|
backupCodec: { codec: "vp8", encoding: VideoPresets.h720.encoding },
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const e2eeWorker = new E2EEWorker();
|
|
||||||
const e2eeKeyProvider = new ExternalE2EEKeyProvider();
|
|
||||||
e2eeKeyProvider.setKey("not secret password");
|
|
||||||
|
|
||||||
export const defaultLiveKitOptions: RoomOptions = {
|
export const defaultLiveKitOptions: RoomOptions = {
|
||||||
e2ee: {
|
|
||||||
keyProvider: e2eeKeyProvider,
|
|
||||||
worker: e2eeWorker,
|
|
||||||
},
|
|
||||||
|
|
||||||
// automatically manage subscribed video quality
|
// automatically manage subscribed video quality
|
||||||
adaptiveStream: true,
|
adaptiveStream: true,
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { Room, RoomOptions } from "livekit-client";
|
import {
|
||||||
|
E2EEOptions,
|
||||||
|
ExternalE2EEKeyProvider,
|
||||||
|
Room,
|
||||||
|
RoomOptions,
|
||||||
|
} 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,10 +21,32 @@ export type DeviceChoices = {
|
|||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type E2EEConfig = {
|
||||||
|
sharedKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
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 = {
|
||||||
@@ -29,8 +57,11 @@ export function useLiveKit(
|
|||||||
...options.audioCaptureDefaults,
|
...options.audioCaptureDefaults,
|
||||||
deviceId: userChoices.audio?.selectedId,
|
deviceId: userChoices.audio?.selectedId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
options.e2ee = e2eeOptions;
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}, [userChoices.video, userChoices.audio]);
|
}, [userChoices.video, userChoices.audio, e2eeOptions]);
|
||||||
|
|
||||||
const roomWithoutProps = useMemo(() => new Room(roomOptions), [roomOptions]);
|
const roomWithoutProps = useMemo(() => new Room(roomOptions), [roomOptions]);
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ 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";
|
||||||
@@ -218,10 +218,12 @@ 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 livekitServiceURL =
|
const livekitServiceURL =
|
||||||
groupCall.foci[0]?.livekitServiceUrl ??
|
groupCall.livekitServiceURL ?? Config.get().livekit?.livekit_service_url;
|
||||||
Config.get().livekit?.livekit_service_url;
|
|
||||||
if (!livekitServiceURL) {
|
if (!livekitServiceURL) {
|
||||||
return <ErrorView error={new Error("No livekit_service_url defined")} />;
|
return <ErrorView error={new Error("No livekit_service_url defined")} />;
|
||||||
}
|
}
|
||||||
@@ -243,6 +245,7 @@ export function GroupCallView({
|
|||||||
unencryptedEventsFromUsers={unencryptedEventsFromUsers}
|
unencryptedEventsFromUsers={unencryptedEventsFromUsers}
|
||||||
hideHeader={hideHeader}
|
hideHeader={hideHeader}
|
||||||
userChoices={userChoices}
|
userChoices={userChoices}
|
||||||
|
e2eeConfig={e2eeConfig}
|
||||||
otelGroupCallMembership={otelGroupCallMembership}
|
otelGroupCallMembership={otelGroupCallMembership}
|
||||||
/>
|
/>
|
||||||
</OpenIDLoader>
|
</OpenIDLoader>
|
||||||
@@ -283,8 +286,9 @@ 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();
|
||||||
}}
|
}}
|
||||||
isEmbedded={isEmbedded}
|
isEmbedded={isEmbedded}
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ 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";
|
||||||
@@ -92,11 +92,16 @@ 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;
|
||||||
|
|||||||
@@ -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,12 +25,14 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -39,6 +41,8 @@ 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) {
|
||||||
@@ -49,6 +53,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}>
|
||||||
@@ -68,12 +83,26 @@ export function LobbyView(props: Props) {
|
|||||||
matrixInfo={props.matrixInfo}
|
matrixInfo={props.matrixInfo}
|
||||||
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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
@@ -249,6 +251,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: "",
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ export const widget: WidgetHelpers | null = (() => {
|
|||||||
// message so that we can use the widget API in less racy mode, but we need to change
|
// message so that we can use the widget API in less racy mode, but we need to change
|
||||||
// element-web to use waitForIFrameLoad=false. Once that change has rolled out,
|
// element-web to use waitForIFrameLoad=false. Once that change has rolled out,
|
||||||
// we can just start the client after we've fetched the config.
|
// we can just start the client after we've fetched the config.
|
||||||
foci: [],
|
livekitServiceURL: undefined,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -178,9 +178,7 @@ export const widget: WidgetHelpers | null = (() => {
|
|||||||
// Now we've fetched the config, be evil and use the getter to inject the focus
|
// Now we've fetched the config, be evil and use the getter to inject the focus
|
||||||
// into the client (see above XXX).
|
// into the client (see above XXX).
|
||||||
if (focus) {
|
if (focus) {
|
||||||
client.getFoci().push({
|
client.setLivekitServiceURL(livekit.livekit_service_url);
|
||||||
livekitServiceUrl: livekit.livekit_service_url,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
await client.startClient();
|
await client.startClient();
|
||||||
resolve(client);
|
resolve(client);
|
||||||
|
|||||||
@@ -11012,9 +11012,9 @@ matrix-events-sdk@0.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd"
|
resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd"
|
||||||
integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==
|
integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==
|
||||||
|
|
||||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#d79d9ae69c3220c02406706d4a1ec52c22c44fbd":
|
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#b698217445318f453e0b1086364a33113eaa85d9":
|
||||||
version "26.2.0"
|
version "26.2.0"
|
||||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/d79d9ae69c3220c02406706d4a1ec52c22c44fbd"
|
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.1"
|
"@matrix-org/matrix-sdk-crypto-js" "^0.1.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user