Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ecab1d61c | ||
|
|
930cb49589 |
@@ -58,7 +58,7 @@
|
||||
"i18next-http-backend": "^2.0.0",
|
||||
"livekit-client": "^1.12.3",
|
||||
"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",
|
||||
"normalize.css": "^8.0.1",
|
||||
"pako": "^2.0.4",
|
||||
|
||||
@@ -115,10 +115,6 @@ interface UrlParams {
|
||||
* E2EE password
|
||||
*/
|
||||
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.
|
||||
* 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,
|
||||
analyticsID: parser.getParam("analyticsID"),
|
||||
allowIceFallback: parser.getFlagParam("allowIceFallback"),
|
||||
perParticipantE2EE: parser.getFlagParam("perParticipantE2EE"),
|
||||
skipLobby: parser.getFlagParam("skipLobby"),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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}`,
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -40,7 +40,6 @@ import { Caption } from "../typography/Typography";
|
||||
import { Form } from "../form/Form";
|
||||
import { useOptInAnalytics } from "../settings/useSetting";
|
||||
import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
|
||||
import { E2eeType } from "../e2ee/e2eeType";
|
||||
|
||||
interface Props {
|
||||
client: MatrixClient;
|
||||
@@ -73,11 +72,7 @@ export const RegisteredView: FC<Props> = ({ client }) => {
|
||||
setError(undefined);
|
||||
setLoading(true);
|
||||
|
||||
const createRoomResult = await createRoom(
|
||||
client,
|
||||
roomName,
|
||||
E2eeType.SHARED_KEY,
|
||||
);
|
||||
const createRoomResult = await createRoom(client, roomName, true);
|
||||
|
||||
history.push(
|
||||
getRelativeRoomUrl(
|
||||
|
||||
@@ -43,7 +43,6 @@ import { generateRandomName } from "../auth/generateRandomName";
|
||||
import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
|
||||
import { useOptInAnalytics } from "../settings/useSetting";
|
||||
import { Config } from "../config/Config";
|
||||
import { E2eeType } from "../e2ee/e2eeType";
|
||||
|
||||
export const UnauthenticatedView: FC = () => {
|
||||
const { setClient } = useClient();
|
||||
@@ -85,11 +84,7 @@ export const UnauthenticatedView: FC = () => {
|
||||
|
||||
let createRoomResult;
|
||||
try {
|
||||
createRoomResult = await createRoom(
|
||||
client,
|
||||
roomName,
|
||||
E2eeType.SHARED_KEY,
|
||||
);
|
||||
createRoomResult = await createRoom(client, roomName, true);
|
||||
} catch (error) {
|
||||
if (!setClient) {
|
||||
throw error;
|
||||
|
||||
@@ -26,7 +26,6 @@ import { useLiveKitRoom } from "@livekit/components-react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import E2EEWorker from "livekit-client/e2ee-worker?worker";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||
|
||||
import { defaultLiveKitOptions } from "./options";
|
||||
import { SFUConfig } from "./openIDSFU";
|
||||
@@ -40,12 +39,9 @@ import {
|
||||
ECConnectionState,
|
||||
useECConnectionState,
|
||||
} from "./useECConnectionState";
|
||||
import { MatrixKeyProvider } from "../e2ee/matrixKeyProvider";
|
||||
import { E2eeType } from "../e2ee/e2eeType";
|
||||
|
||||
export type E2EEConfig = {
|
||||
mode: E2eeType;
|
||||
sharedKey?: string;
|
||||
sharedKey: string;
|
||||
};
|
||||
|
||||
interface UseLivekitResult {
|
||||
@@ -54,44 +50,26 @@ interface UseLivekitResult {
|
||||
}
|
||||
|
||||
export function useLiveKit(
|
||||
rtcSession: MatrixRTCSession,
|
||||
muteStates: MuteStates,
|
||||
sfuConfig?: SFUConfig,
|
||||
e2eeConfig?: E2EEConfig,
|
||||
): UseLivekitResult {
|
||||
const e2eeOptions = useMemo((): E2EEOptions | undefined => {
|
||||
if (!e2eeConfig || e2eeConfig.mode === E2eeType.NONE) return undefined;
|
||||
const e2eeOptions = useMemo(() => {
|
||||
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 {
|
||||
keyProvider: new ExternalE2EEKeyProvider(),
|
||||
worker: new E2EEWorker(),
|
||||
};
|
||||
}
|
||||
return {
|
||||
keyProvider: new ExternalE2EEKeyProvider(),
|
||||
worker: new E2EEWorker(),
|
||||
} as E2EEOptions;
|
||||
}, [e2eeConfig]);
|
||||
|
||||
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(
|
||||
e2eeConfig.sharedKey,
|
||||
);
|
||||
}
|
||||
}, [e2eeOptions, e2eeConfig, rtcSession]);
|
||||
(e2eeOptions.keyProvider as ExternalE2EEKeyProvider).setKey(
|
||||
e2eeConfig?.sharedKey,
|
||||
);
|
||||
}, [e2eeOptions, e2eeConfig?.sharedKey]);
|
||||
|
||||
const initialMuteStates = useRef<MuteStates>(muteStates);
|
||||
const devices = useMediaDevices();
|
||||
|
||||
@@ -28,7 +28,6 @@ import {
|
||||
GroupCallIntent,
|
||||
GroupCallType,
|
||||
} 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 { Room } from "matrix-js-sdk/src/models/room";
|
||||
@@ -38,7 +37,6 @@ import { loadOlm } from "./olm";
|
||||
import { Config } from "./config/Config";
|
||||
import { setLocalStorageItem } from "./useLocalStorage";
|
||||
import { getRoomSharedKeyLocalStorageKey } from "./e2ee/sharedKeyManagement";
|
||||
import { E2eeType } from "./e2ee/e2eeType";
|
||||
|
||||
export const fallbackICEServerAllowed =
|
||||
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.
|
||||
* If true is passed for the 'restore' parameter, a check will be made
|
||||
@@ -279,20 +294,10 @@ interface CreateRoomResult {
|
||||
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(
|
||||
client: MatrixClient,
|
||||
name: string,
|
||||
e2ee: E2eeType,
|
||||
e2ee: boolean,
|
||||
): Promise<CreateRoomResult> {
|
||||
logger.log(`Creating room for group call`);
|
||||
const createPromise = client.createRoom({
|
||||
@@ -357,8 +362,8 @@ export async function createRoom(
|
||||
);
|
||||
|
||||
let password;
|
||||
if (e2ee == E2eeType.SHARED_KEY) {
|
||||
password = secureRandomBase64Url(16);
|
||||
if (e2ee) {
|
||||
password = secureRandomString(16);
|
||||
setLocalStorageItem(
|
||||
getRoomSharedKeyLocalStorageKey(result.room_id),
|
||||
password,
|
||||
|
||||
@@ -44,9 +44,6 @@ import { useRoomAvatar } from "./useRoomAvatar";
|
||||
import { useRoomName } from "./useRoomName";
|
||||
import { useJoinRule } from "./useJoinRule";
|
||||
import { InviteModal } from "./InviteModal";
|
||||
import { E2EEConfig } from "../livekit/useLiveKit";
|
||||
import { useUrlParams } from "../UrlParams";
|
||||
import { E2eeType } from "../e2ee/e2eeType";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -90,7 +87,6 @@ export const GroupCallView: FC<Props> = ({
|
||||
const roomName = useRoomName(rtcSession.room);
|
||||
const roomAvatar = useRoomAvatar(rtcSession.room);
|
||||
const roomEncrypted = useIsRoomE2EE(rtcSession.room.roomId)!;
|
||||
const { perParticipantE2EE } = useUrlParams();
|
||||
|
||||
const matrixInfo = useMemo((): MatrixInfo => {
|
||||
return {
|
||||
@@ -186,7 +182,7 @@ export const GroupCallView: FC<Props> = ({
|
||||
ev: CustomEvent<IWidgetApiRequest>,
|
||||
): Promise<void> => {
|
||||
defaultDeviceSetup(ev.detail.data as unknown as JoinCallData);
|
||||
enterRTCSession(rtcSession, perParticipantE2EE);
|
||||
enterRTCSession(rtcSession);
|
||||
await Promise.all([
|
||||
widget!.api.setAlwaysOnScreen(true),
|
||||
widget!.api.transport.reply(ev.detail, {}),
|
||||
@@ -199,9 +195,9 @@ export const GroupCallView: FC<Props> = ({
|
||||
} else {
|
||||
// if we don't use preload and only skipLobby we enter the rtc session right away
|
||||
defaultDeviceSetup({ audioInput: null, videoInput: null });
|
||||
enterRTCSession(rtcSession, perParticipantE2EE);
|
||||
enterRTCSession(rtcSession);
|
||||
}
|
||||
}, [rtcSession, preload, skipLobby, perParticipantE2EE]);
|
||||
}, [rtcSession, preload, skipLobby]);
|
||||
|
||||
const [left, setLeft] = useState(false);
|
||||
const [leaveError, setLeaveError] = useState<Error | undefined>(undefined);
|
||||
@@ -249,19 +245,16 @@ export const GroupCallView: FC<Props> = ({
|
||||
}
|
||||
}, [isJoined, rtcSession]);
|
||||
|
||||
const e2eeConfig = useMemo((): E2EEConfig | undefined => {
|
||||
if (perParticipantE2EE) {
|
||||
return { mode: E2eeType.PER_PARTICIPANT };
|
||||
} else if (e2eeSharedKey) {
|
||||
return { mode: E2eeType.SHARED_KEY, sharedKey: e2eeSharedKey };
|
||||
}
|
||||
}, [perParticipantE2EE, e2eeSharedKey]);
|
||||
const e2eeConfig = useMemo(
|
||||
() => (e2eeSharedKey ? { sharedKey: e2eeSharedKey } : undefined),
|
||||
[e2eeSharedKey],
|
||||
);
|
||||
|
||||
const onReconnect = useCallback(() => {
|
||||
setLeft(false);
|
||||
setLeaveError(undefined);
|
||||
enterRTCSession(rtcSession, perParticipantE2EE);
|
||||
}, [rtcSession, perParticipantE2EE]);
|
||||
enterRTCSession(rtcSession);
|
||||
}, [rtcSession]);
|
||||
|
||||
const joinRule = useJoinRule(rtcSession.room);
|
||||
|
||||
@@ -287,7 +280,7 @@ export const GroupCallView: FC<Props> = ({
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (isRoomE2EE && !perParticipantE2EE && !e2eeSharedKey) {
|
||||
if (isRoomE2EE && !e2eeSharedKey) {
|
||||
return (
|
||||
<ErrorView
|
||||
error={
|
||||
@@ -377,7 +370,7 @@ export const GroupCallView: FC<Props> = ({
|
||||
client={client}
|
||||
matrixInfo={matrixInfo}
|
||||
muteStates={muteStates}
|
||||
onEnter={(): void => enterRTCSession(rtcSession, perParticipantE2EE)}
|
||||
onEnter={(): void => enterRTCSession(rtcSession)}
|
||||
confineToRoom={confineToRoom}
|
||||
hideHeader={hideHeader}
|
||||
participantCount={participantCount}
|
||||
|
||||
@@ -101,7 +101,6 @@ export interface ActiveCallProps
|
||||
export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
||||
const sfuConfig = useOpenIDSFU(props.client, props.rtcSession);
|
||||
const { livekitRoom, connState } = useLiveKit(
|
||||
props.rtcSession,
|
||||
props.muteStates,
|
||||
sfuConfig,
|
||||
props.e2eeConfig,
|
||||
|
||||
@@ -34,10 +34,7 @@ function makeFocus(livekitAlias: string): LivekitFocus {
|
||||
};
|
||||
}
|
||||
|
||||
export function enterRTCSession(
|
||||
rtcSession: MatrixRTCSession,
|
||||
encryptMedia: boolean,
|
||||
): void {
|
||||
export function enterRTCSession(rtcSession: MatrixRTCSession): void {
|
||||
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
|
||||
PosthogAnalytics.instance.eventCallStarted.track(rtcSession.room.roomId);
|
||||
|
||||
@@ -48,7 +45,7 @@ export function enterRTCSession(
|
||||
// right now we assume everything is a room-scoped call
|
||||
const livekitAlias = rtcSession.room.roomId;
|
||||
|
||||
rtcSession.joinRoomSession([makeFocus(livekitAlias)], encryptMedia);
|
||||
rtcSession.joinRoomSession([makeFocus(livekitAlias)]);
|
||||
}
|
||||
|
||||
const widgetPostHangupProcedure = async (
|
||||
|
||||
@@ -77,8 +77,6 @@ export const widget = ((): WidgetHelpers | null => {
|
||||
logger.info("Widget API is available");
|
||||
const api = new WidgetApi(widgetId, parentOrigin);
|
||||
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
|
||||
// intend for the app to handle
|
||||
|
||||
16
yarn.lock
16
yarn.lock
@@ -2010,10 +2010,10 @@
|
||||
clsx "^2.0.0"
|
||||
usehooks-ts "^2.9.1"
|
||||
|
||||
"@matrix-org/matrix-sdk-crypto-wasm@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-2.2.0.tgz#7c60afe01915281a6b71502821bc8e01afbfa70d"
|
||||
integrity sha512-txmvaTiZpVV0/kWCRcE7tZvRESCEc1ynLJDVh9OUsFlaXfl13c7qdD3E6IJEJ8YiPMIn+PHogdfBZsO84reaMg==
|
||||
"@matrix-org/matrix-sdk-crypto-wasm@^1.2.3-alpha.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-1.3.0.tgz#f098c72701801334eeb7049ca7074fe6eb3686d6"
|
||||
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":
|
||||
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"
|
||||
integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==
|
||||
|
||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#bf81c4bfebd52532d67d30a66e651e3658c8aaad":
|
||||
version "29.1.0"
|
||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/bf81c4bfebd52532d67d30a66e651e3658c8aaad"
|
||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#4ce837b20e638a185f9002b2388fbaf48975ee6e":
|
||||
version "29.0.0"
|
||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/4ce837b20e638a185f9002b2388fbaf48975ee6e"
|
||||
dependencies:
|
||||
"@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"
|
||||
bs58 "^5.0.0"
|
||||
content-type "^1.0.4"
|
||||
|
||||
Reference in New Issue
Block a user