Compare commits

...

2 Commits

Author SHA1 Message Date
David Baker
9ecab1d61c Merge pull request #1896 from vector-im/dbkr/revert-1772
Revert per-sender keys
2023-11-14 15:56:06 +00:00
David Baker
930cb49589 Revert per-sender keys
This reverts https://github.com/vector-im/element-call/pull/1772
on to the 1.5.11 release branch so we can test without per-sender
keys and see if we still get the same sporadic failures.
2023-11-14 15:35:52 +00:00
13 changed files with 56 additions and 195 deletions

View File

@@ -58,7 +58,7 @@
"i18next-http-backend": "^2.0.0", "i18next-http-backend": "^2.0.0",
"livekit-client": "^1.12.3", "livekit-client": "^1.12.3",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#bf81c4bfebd52532d67d30a66e651e3658c8aaad", "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#4ce837b20e638a185f9002b2388fbaf48975ee6e",
"matrix-widget-api": "^1.3.1", "matrix-widget-api": "^1.3.1",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"pako": "^2.0.4", "pako": "^2.0.4",

View File

@@ -115,10 +115,6 @@ interface UrlParams {
* E2EE password * E2EE password
*/ */
password: string | null; password: string | null;
/**
* Whether we the app should use per participant keys for E2EE.
*/
perParticipantE2EE: boolean;
/** /**
* Setting this flag skips the lobby and brings you in the call directly. * Setting this flag skips the lobby and brings you in the call directly.
* In the widget this can be combined with preload to pass the device settings * In the widget this can be combined with preload to pass the device settings
@@ -221,7 +217,6 @@ export const getUrlParams = (
fontScale: Number.isNaN(fontScale) ? null : fontScale, fontScale: Number.isNaN(fontScale) ? null : fontScale,
analyticsID: parser.getParam("analyticsID"), analyticsID: parser.getParam("analyticsID"),
allowIceFallback: parser.getFlagParam("allowIceFallback"), allowIceFallback: parser.getFlagParam("allowIceFallback"),
perParticipantE2EE: parser.getFlagParam("perParticipantE2EE"),
skipLobby: parser.getFlagParam("skipLobby"), skipLobby: parser.getFlagParam("skipLobby"),
}; };
}; };

View File

@@ -1,21 +0,0 @@
/*
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.
*/
export enum E2eeType {
NONE = 0,
PER_PARTICIPANT = 1,
SHARED_KEY = 2,
}

View File

@@ -1,73 +0,0 @@
/*
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 { BaseKeyProvider, createKeyMaterialFromBuffer } from "livekit-client";
import { logger } from "matrix-js-sdk/src/logger";
import {
MatrixRTCSession,
MatrixRTCSessionEvent,
} from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
export class MatrixKeyProvider extends BaseKeyProvider {
private rtcSession?: MatrixRTCSession;
public constructor() {
super({ ratchetWindowSize: 0 });
}
public setRTCSession(rtcSession: MatrixRTCSession): void {
if (this.rtcSession) {
this.rtcSession.off(
MatrixRTCSessionEvent.EncryptionKeyChanged,
this.onEncryptionKeyChanged,
);
}
this.rtcSession = rtcSession;
this.rtcSession.on(
MatrixRTCSessionEvent.EncryptionKeyChanged,
this.onEncryptionKeyChanged,
);
// The new session could be aware of keys of which the old session wasn't,
// so emit a key changed event.
for (const [
participant,
encryptionKeys,
] of this.rtcSession.getEncryptionKeys()) {
for (const [index, encryptionKey] of encryptionKeys.entries()) {
this.onEncryptionKeyChanged(encryptionKey, index, participant);
}
}
}
private onEncryptionKeyChanged = async (
encryptionKey: Uint8Array,
encryptionKeyIndex: number,
participantId: string,
): Promise<void> => {
this.onSetEncryptionKey(
await createKeyMaterialFromBuffer(encryptionKey),
participantId,
encryptionKeyIndex,
);
logger.debug(
`Sent new key to livekit room=${this.rtcSession?.room.roomId} participantId=${participantId} encryptionKeyIndex=${encryptionKeyIndex}`,
);
};
}

View File

@@ -40,7 +40,6 @@ import { Caption } from "../typography/Typography";
import { Form } from "../form/Form"; import { Form } from "../form/Form";
import { useOptInAnalytics } from "../settings/useSetting"; import { useOptInAnalytics } from "../settings/useSetting";
import { AnalyticsNotice } from "../analytics/AnalyticsNotice"; import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
import { E2eeType } from "../e2ee/e2eeType";
interface Props { interface Props {
client: MatrixClient; client: MatrixClient;
@@ -73,11 +72,7 @@ export const RegisteredView: FC<Props> = ({ client }) => {
setError(undefined); setError(undefined);
setLoading(true); setLoading(true);
const createRoomResult = await createRoom( const createRoomResult = await createRoom(client, roomName, true);
client,
roomName,
E2eeType.SHARED_KEY,
);
history.push( history.push(
getRelativeRoomUrl( getRelativeRoomUrl(

View File

@@ -43,7 +43,6 @@ import { generateRandomName } from "../auth/generateRandomName";
import { AnalyticsNotice } from "../analytics/AnalyticsNotice"; import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
import { useOptInAnalytics } from "../settings/useSetting"; import { useOptInAnalytics } from "../settings/useSetting";
import { Config } from "../config/Config"; import { Config } from "../config/Config";
import { E2eeType } from "../e2ee/e2eeType";
export const UnauthenticatedView: FC = () => { export const UnauthenticatedView: FC = () => {
const { setClient } = useClient(); const { setClient } = useClient();
@@ -85,11 +84,7 @@ export const UnauthenticatedView: FC = () => {
let createRoomResult; let createRoomResult;
try { try {
createRoomResult = await createRoom( createRoomResult = await createRoom(client, roomName, true);
client,
roomName,
E2eeType.SHARED_KEY,
);
} catch (error) { } catch (error) {
if (!setClient) { if (!setClient) {
throw error; throw error;

View File

@@ -26,7 +26,6 @@ import { useLiveKitRoom } from "@livekit/components-react";
import { useEffect, useMemo, useRef, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import E2EEWorker from "livekit-client/e2ee-worker?worker"; import E2EEWorker from "livekit-client/e2ee-worker?worker";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
import { defaultLiveKitOptions } from "./options"; import { defaultLiveKitOptions } from "./options";
import { SFUConfig } from "./openIDSFU"; import { SFUConfig } from "./openIDSFU";
@@ -40,12 +39,9 @@ import {
ECConnectionState, ECConnectionState,
useECConnectionState, useECConnectionState,
} from "./useECConnectionState"; } from "./useECConnectionState";
import { MatrixKeyProvider } from "../e2ee/matrixKeyProvider";
import { E2eeType } from "../e2ee/e2eeType";
export type E2EEConfig = { export type E2EEConfig = {
mode: E2eeType; sharedKey: string;
sharedKey?: string;
}; };
interface UseLivekitResult { interface UseLivekitResult {
@@ -54,44 +50,26 @@ interface UseLivekitResult {
} }
export function useLiveKit( export function useLiveKit(
rtcSession: MatrixRTCSession,
muteStates: MuteStates, muteStates: MuteStates,
sfuConfig?: SFUConfig, sfuConfig?: SFUConfig,
e2eeConfig?: E2EEConfig, e2eeConfig?: E2EEConfig,
): UseLivekitResult { ): UseLivekitResult {
const e2eeOptions = useMemo((): E2EEOptions | undefined => { const e2eeOptions = useMemo(() => {
if (!e2eeConfig || e2eeConfig.mode === E2eeType.NONE) return undefined; if (!e2eeConfig?.sharedKey) return undefined;
if (e2eeConfig.mode === E2eeType.PER_PARTICIPANT) {
return {
keyProvider: new MatrixKeyProvider(),
worker: new E2EEWorker(),
};
} else if (
e2eeConfig.mode === E2eeType.SHARED_KEY &&
e2eeConfig.sharedKey
) {
return { return {
keyProvider: new ExternalE2EEKeyProvider(), keyProvider: new ExternalE2EEKeyProvider(),
worker: new E2EEWorker(), worker: new E2EEWorker(),
}; } as E2EEOptions;
}
}, [e2eeConfig]); }, [e2eeConfig]);
useEffect(() => { useEffect(() => {
if (!e2eeConfig || !e2eeOptions) return; if (!e2eeConfig?.sharedKey || !e2eeOptions) return;
if (e2eeConfig.mode === E2eeType.PER_PARTICIPANT) {
(e2eeOptions.keyProvider as MatrixKeyProvider).setRTCSession(rtcSession);
} else if (
e2eeConfig.mode === E2eeType.SHARED_KEY &&
e2eeConfig.sharedKey
) {
(e2eeOptions.keyProvider as ExternalE2EEKeyProvider).setKey( (e2eeOptions.keyProvider as ExternalE2EEKeyProvider).setKey(
e2eeConfig.sharedKey, e2eeConfig?.sharedKey,
); );
} }, [e2eeOptions, e2eeConfig?.sharedKey]);
}, [e2eeOptions, e2eeConfig, rtcSession]);
const initialMuteStates = useRef<MuteStates>(muteStates); const initialMuteStates = useRef<MuteStates>(muteStates);
const devices = useMediaDevices(); const devices = useMediaDevices();

View File

@@ -28,7 +28,6 @@ import {
GroupCallIntent, GroupCallIntent,
GroupCallType, GroupCallType,
} from "matrix-js-sdk/src/webrtc/groupCall"; } from "matrix-js-sdk/src/webrtc/groupCall";
import { secureRandomBase64Url } from "matrix-js-sdk/src/randomstring";
import type { MatrixClient } from "matrix-js-sdk/src/client"; import type { MatrixClient } from "matrix-js-sdk/src/client";
import type { Room } from "matrix-js-sdk/src/models/room"; import type { Room } from "matrix-js-sdk/src/models/room";
@@ -38,7 +37,6 @@ import { loadOlm } from "./olm";
import { Config } from "./config/Config"; import { Config } from "./config/Config";
import { setLocalStorageItem } from "./useLocalStorage"; import { setLocalStorageItem } from "./useLocalStorage";
import { getRoomSharedKeyLocalStorageKey } from "./e2ee/sharedKeyManagement"; import { getRoomSharedKeyLocalStorageKey } from "./e2ee/sharedKeyManagement";
import { E2eeType } from "./e2ee/e2eeType";
export const fallbackICEServerAllowed = export const fallbackICEServerAllowed =
import.meta.env.VITE_FALLBACK_STUN_ALLOWED === "true"; import.meta.env.VITE_FALLBACK_STUN_ALLOWED === "true";
@@ -75,6 +73,23 @@ function waitForSync(client: MatrixClient): Promise<void> {
}); });
} }
function secureRandomString(entropyBytes: number): string {
const key = new Uint8Array(entropyBytes);
crypto.getRandomValues(key);
// encode to base64url as this value goes into URLs
// base64url is just base64 with thw two non-alphanum characters swapped out for
// ones that can be put in a URL without encoding. Browser JS has a native impl
// for base64 encoding but only a string (there isn't one that takes a UInt8Array
// yet) so just use the built-in one and convert, replace the chars and strip the
// padding from the end (otherwise we'd need to pull in another dependency).
return btoa(
key.reduce((acc, current) => acc + String.fromCharCode(current), ""),
)
.replace("+", "-")
.replace("/", "_")
.replace(/=*$/, "");
}
/** /**
* Initialises and returns a new standalone Matrix Client. * Initialises and returns a new standalone Matrix Client.
* If true is passed for the 'restore' parameter, a check will be made * If true is passed for the 'restore' parameter, a check will be made
@@ -279,20 +294,10 @@ interface CreateRoomResult {
password?: string; password?: string;
} }
/**
* Create a new room ready for calls
*
* @param client Matrix client to use
* @param name The name of the room
* @param e2ee The type of e2ee call to create. Note that we would currently never
* create a room for per-participant e2ee calls: since it's used in
* embedded mode, we use the existing room.
* @returns Object holding information about the new room
*/
export async function createRoom( export async function createRoom(
client: MatrixClient, client: MatrixClient,
name: string, name: string,
e2ee: E2eeType, e2ee: boolean,
): Promise<CreateRoomResult> { ): Promise<CreateRoomResult> {
logger.log(`Creating room for group call`); logger.log(`Creating room for group call`);
const createPromise = client.createRoom({ const createPromise = client.createRoom({
@@ -357,8 +362,8 @@ export async function createRoom(
); );
let password; let password;
if (e2ee == E2eeType.SHARED_KEY) { if (e2ee) {
password = secureRandomBase64Url(16); password = secureRandomString(16);
setLocalStorageItem( setLocalStorageItem(
getRoomSharedKeyLocalStorageKey(result.room_id), getRoomSharedKeyLocalStorageKey(result.room_id),
password, password,

View File

@@ -44,9 +44,6 @@ import { useRoomAvatar } from "./useRoomAvatar";
import { useRoomName } from "./useRoomName"; import { useRoomName } from "./useRoomName";
import { useJoinRule } from "./useJoinRule"; import { useJoinRule } from "./useJoinRule";
import { InviteModal } from "./InviteModal"; import { InviteModal } from "./InviteModal";
import { E2EEConfig } from "../livekit/useLiveKit";
import { useUrlParams } from "../UrlParams";
import { E2eeType } from "../e2ee/e2eeType";
declare global { declare global {
interface Window { interface Window {
@@ -90,7 +87,6 @@ export const GroupCallView: FC<Props> = ({
const roomName = useRoomName(rtcSession.room); const roomName = useRoomName(rtcSession.room);
const roomAvatar = useRoomAvatar(rtcSession.room); const roomAvatar = useRoomAvatar(rtcSession.room);
const roomEncrypted = useIsRoomE2EE(rtcSession.room.roomId)!; const roomEncrypted = useIsRoomE2EE(rtcSession.room.roomId)!;
const { perParticipantE2EE } = useUrlParams();
const matrixInfo = useMemo((): MatrixInfo => { const matrixInfo = useMemo((): MatrixInfo => {
return { return {
@@ -186,7 +182,7 @@ export const GroupCallView: FC<Props> = ({
ev: CustomEvent<IWidgetApiRequest>, ev: CustomEvent<IWidgetApiRequest>,
): Promise<void> => { ): Promise<void> => {
defaultDeviceSetup(ev.detail.data as unknown as JoinCallData); defaultDeviceSetup(ev.detail.data as unknown as JoinCallData);
enterRTCSession(rtcSession, perParticipantE2EE); enterRTCSession(rtcSession);
await Promise.all([ await Promise.all([
widget!.api.setAlwaysOnScreen(true), widget!.api.setAlwaysOnScreen(true),
widget!.api.transport.reply(ev.detail, {}), widget!.api.transport.reply(ev.detail, {}),
@@ -199,9 +195,9 @@ export const GroupCallView: FC<Props> = ({
} else { } else {
// if we don't use preload and only skipLobby we enter the rtc session right away // if we don't use preload and only skipLobby we enter the rtc session right away
defaultDeviceSetup({ audioInput: null, videoInput: null }); defaultDeviceSetup({ audioInput: null, videoInput: null });
enterRTCSession(rtcSession, perParticipantE2EE); enterRTCSession(rtcSession);
} }
}, [rtcSession, preload, skipLobby, perParticipantE2EE]); }, [rtcSession, preload, skipLobby]);
const [left, setLeft] = useState(false); const [left, setLeft] = useState(false);
const [leaveError, setLeaveError] = useState<Error | undefined>(undefined); const [leaveError, setLeaveError] = useState<Error | undefined>(undefined);
@@ -249,19 +245,16 @@ export const GroupCallView: FC<Props> = ({
} }
}, [isJoined, rtcSession]); }, [isJoined, rtcSession]);
const e2eeConfig = useMemo((): E2EEConfig | undefined => { const e2eeConfig = useMemo(
if (perParticipantE2EE) { () => (e2eeSharedKey ? { sharedKey: e2eeSharedKey } : undefined),
return { mode: E2eeType.PER_PARTICIPANT }; [e2eeSharedKey],
} else if (e2eeSharedKey) { );
return { mode: E2eeType.SHARED_KEY, sharedKey: e2eeSharedKey };
}
}, [perParticipantE2EE, e2eeSharedKey]);
const onReconnect = useCallback(() => { const onReconnect = useCallback(() => {
setLeft(false); setLeft(false);
setLeaveError(undefined); setLeaveError(undefined);
enterRTCSession(rtcSession, perParticipantE2EE); enterRTCSession(rtcSession);
}, [rtcSession, perParticipantE2EE]); }, [rtcSession]);
const joinRule = useJoinRule(rtcSession.room); const joinRule = useJoinRule(rtcSession.room);
@@ -287,7 +280,7 @@ export const GroupCallView: FC<Props> = ({
const { t } = useTranslation(); const { t } = useTranslation();
if (isRoomE2EE && !perParticipantE2EE && !e2eeSharedKey) { if (isRoomE2EE && !e2eeSharedKey) {
return ( return (
<ErrorView <ErrorView
error={ error={
@@ -377,7 +370,7 @@ export const GroupCallView: FC<Props> = ({
client={client} client={client}
matrixInfo={matrixInfo} matrixInfo={matrixInfo}
muteStates={muteStates} muteStates={muteStates}
onEnter={(): void => enterRTCSession(rtcSession, perParticipantE2EE)} onEnter={(): void => enterRTCSession(rtcSession)}
confineToRoom={confineToRoom} confineToRoom={confineToRoom}
hideHeader={hideHeader} hideHeader={hideHeader}
participantCount={participantCount} participantCount={participantCount}

View File

@@ -101,7 +101,6 @@ export interface ActiveCallProps
export const ActiveCall: FC<ActiveCallProps> = (props) => { export const ActiveCall: FC<ActiveCallProps> = (props) => {
const sfuConfig = useOpenIDSFU(props.client, props.rtcSession); const sfuConfig = useOpenIDSFU(props.client, props.rtcSession);
const { livekitRoom, connState } = useLiveKit( const { livekitRoom, connState } = useLiveKit(
props.rtcSession,
props.muteStates, props.muteStates,
sfuConfig, sfuConfig,
props.e2eeConfig, props.e2eeConfig,

View File

@@ -34,10 +34,7 @@ function makeFocus(livekitAlias: string): LivekitFocus {
}; };
} }
export function enterRTCSession( export function enterRTCSession(rtcSession: MatrixRTCSession): void {
rtcSession: MatrixRTCSession,
encryptMedia: boolean,
): void {
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date()); PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
PosthogAnalytics.instance.eventCallStarted.track(rtcSession.room.roomId); PosthogAnalytics.instance.eventCallStarted.track(rtcSession.room.roomId);
@@ -48,7 +45,7 @@ export function enterRTCSession(
// right now we assume everything is a room-scoped call // right now we assume everything is a room-scoped call
const livekitAlias = rtcSession.room.roomId; const livekitAlias = rtcSession.room.roomId;
rtcSession.joinRoomSession([makeFocus(livekitAlias)], encryptMedia); rtcSession.joinRoomSession([makeFocus(livekitAlias)]);
} }
const widgetPostHangupProcedure = async ( const widgetPostHangupProcedure = async (

View File

@@ -77,8 +77,6 @@ export const widget = ((): WidgetHelpers | null => {
logger.info("Widget API is available"); logger.info("Widget API is available");
const api = new WidgetApi(widgetId, parentOrigin); const api = new WidgetApi(widgetId, parentOrigin);
api.requestCapability(MatrixCapabilities.AlwaysOnScreen); api.requestCapability(MatrixCapabilities.AlwaysOnScreen);
api.requestCapabilityToSendEvent(EventType.CallEncryptionKeysPrefix);
api.requestCapabilityToReceiveEvent(EventType.CallEncryptionKeysPrefix);
// Set up the lazy action emitter, but only for select actions that we // Set up the lazy action emitter, but only for select actions that we
// intend for the app to handle // intend for the app to handle

View File

@@ -2010,10 +2010,10 @@
clsx "^2.0.0" clsx "^2.0.0"
usehooks-ts "^2.9.1" usehooks-ts "^2.9.1"
"@matrix-org/matrix-sdk-crypto-wasm@^2.2.0": "@matrix-org/matrix-sdk-crypto-wasm@^1.2.3-alpha.0":
version "2.2.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-2.2.0.tgz#7c60afe01915281a6b71502821bc8e01afbfa70d" resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-1.3.0.tgz#f098c72701801334eeb7049ca7074fe6eb3686d6"
integrity sha512-txmvaTiZpVV0/kWCRcE7tZvRESCEc1ynLJDVh9OUsFlaXfl13c7qdD3E6IJEJ8YiPMIn+PHogdfBZsO84reaMg== integrity sha512-vQ5PVppKu1PY7xy7QDw+RJLYLGFKhJyxLqjXHr0uEUJwfvz2IH2njTLXzrz77dOo9qacxJ9/YNOTe0Hl+98N0A==
"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz": "@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz":
version "3.2.14" version "3.2.14"
@@ -7101,12 +7101,12 @@ 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#bf81c4bfebd52532d67d30a66e651e3658c8aaad": "matrix-js-sdk@github:matrix-org/matrix-js-sdk#4ce837b20e638a185f9002b2388fbaf48975ee6e":
version "29.1.0" version "29.0.0"
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/bf81c4bfebd52532d67d30a66e651e3658c8aaad" resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/4ce837b20e638a185f9002b2388fbaf48975ee6e"
dependencies: dependencies:
"@babel/runtime" "^7.12.5" "@babel/runtime" "^7.12.5"
"@matrix-org/matrix-sdk-crypto-wasm" "^2.2.0" "@matrix-org/matrix-sdk-crypto-wasm" "^1.2.3-alpha.0"
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"