Merge branch 'voip-team/rebased-multiSFU' into valere/multi-sfu/connection_states

This commit is contained in:
Valere
2025-10-10 15:01:49 +02:00
17 changed files with 118 additions and 89 deletions

View File

@@ -10,7 +10,7 @@ jobs:
pull-requests: read pull-requests: read
steps: steps:
- name: Add notice - name: Add notice
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
if: contains(github.event.pull_request.labels.*.name, 'X-Blocked') if: contains(github.event.pull_request.labels.*.name, 'X-Blocked')
with: with:
script: | script: |

View File

@@ -23,7 +23,7 @@ jobs:
packages: write packages: write
steps: steps:
- name: Check it out - name: Check it out
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- name: 📥 Download artifact - name: 📥 Download artifact
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
@@ -34,7 +34,7 @@ jobs:
path: dist path: dist
- name: Log in to container registry - name: Log in to container registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
@@ -42,7 +42,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker - name: Extract metadata (tags, labels) for Docker
id: meta id: meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0 uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
with: with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: ${{ inputs.docker_tags}} tags: ${{ inputs.docker_tags}}
@@ -50,10 +50,10 @@ jobs:
org.opencontainers.image.licenses=AGPL-3.0-only OR LicenseRef-Element-Commercial org.opencontainers.image.licenses=AGPL-3.0-only OR LicenseRef-Element-Commercial
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0 uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64

View File

@@ -32,7 +32,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- name: Enable Corepack - name: Enable Corepack
run: corepack enable run: corepack enable
- name: Yarn cache - name: Yarn cache

View File

@@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- name: Enable Corepack - name: Enable Corepack
run: corepack enable run: corepack enable
- name: Yarn cache - name: Yarn cache

View File

@@ -85,7 +85,7 @@ jobs:
run: find ${{ env.FILENAME_PREFIX }} -type f -print0 | sort -z | xargs -0 sha256sum | tee ${{ env.FILENAME_PREFIX }}.sha256 run: find ${{ env.FILENAME_PREFIX }} -type f -print0 | sort -z | xargs -0 sha256sum | tee ${{ env.FILENAME_PREFIX }}.sha256
- name: Upload - name: Upload
if: ${{ needs.versioning.outputs.DRY_RUN == 'false' }} if: ${{ needs.versioning.outputs.DRY_RUN == 'false' }}
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2 uses: softprops/action-gh-release@aec2ec56f94eb8180ceec724245f64ef008b89f5 # v2
with: with:
files: | files: |
${{ env.FILENAME_PREFIX }}.tar.gz ${{ env.FILENAME_PREFIX }}.tar.gz
@@ -103,7 +103,7 @@ jobs:
id-token: write # required for the provenance flag on npm publish id-token: write # required for the provenance flag on npm publish
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- name: 📥 Download built element-call artifact - name: 📥 Download built element-call artifact
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
@@ -144,7 +144,7 @@ jobs:
contents: read contents: read
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- name: 📥 Download built element-call artifact - name: 📥 Download built element-call artifact
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
@@ -199,7 +199,7 @@ jobs:
contents: read contents: read
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with: with:
path: element-call path: element-call
@@ -212,7 +212,7 @@ jobs:
path: element-call/embedded/ios/Sources/dist path: element-call/embedded/ios/Sources/dist
- name: Checkout element-call-swift - name: Checkout element-call-swift
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
with: with:
repository: element-hq/element-call-swift repository: element-hq/element-call-swift
path: element-call-swift path: element-call-swift
@@ -264,7 +264,7 @@ jobs:
echo "iOS: ${{ needs.publish_ios.outputs.ARTIFACT_VERSION }}" echo "iOS: ${{ needs.publish_ios.outputs.ARTIFACT_VERSION }}"
- name: Add release notes - name: Add release notes
if: ${{ needs.versioning.outputs.DRY_RUN == 'false' }} if: ${{ needs.versioning.outputs.DRY_RUN == 'false' }}
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2 uses: softprops/action-gh-release@aec2ec56f94eb8180ceec724245f64ef008b89f5 # v2
with: with:
append_body: true append_body: true
body: | body: |

View File

@@ -42,7 +42,7 @@ jobs:
- name: Create Checksum - name: Create Checksum
run: find ${{ env.FILENAME_PREFIX }} -type f -print0 | sort -z | xargs -0 sha256sum | tee ${{ env.FILENAME_PREFIX }}.sha256 run: find ${{ env.FILENAME_PREFIX }} -type f -print0 | sort -z | xargs -0 sha256sum | tee ${{ env.FILENAME_PREFIX }}.sha256
- name: Upload - name: Upload
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2 uses: softprops/action-gh-release@aec2ec56f94eb8180ceec724245f64ef008b89f5 # v2
with: with:
files: | files: |
${{ env.FILENAME_PREFIX }}.tar.gz ${{ env.FILENAME_PREFIX }}.tar.gz
@@ -68,7 +68,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Add release note - name: Add release note
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2 uses: softprops/action-gh-release@aec2ec56f94eb8180ceec724245f64ef008b89f5 # v2
with: with:
append_body: true append_body: true
body: | body: |

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- name: Enable Corepack - name: Enable Corepack
run: corepack enable run: corepack enable
- name: Yarn cache - name: Yarn cache
@@ -22,7 +22,7 @@ jobs:
- name: Vitest - name: Vitest
run: "yarn run test:coverage" run: "yarn run test:coverage"
- name: Upload to codecov - name: Upload to codecov
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5 uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5
env: env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with: with:
@@ -33,7 +33,7 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- name: Enable Corepack - name: Enable Corepack
run: corepack enable run: corepack enable
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4

View File

@@ -13,7 +13,7 @@ jobs:
steps: steps:
- name: Checkout the code - name: Checkout the code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- name: Enable Corepack - name: Enable Corepack
run: corepack enable run: corepack enable

View File

@@ -14,7 +14,7 @@ jobs:
steps: steps:
- name: Checkout the code - name: Checkout the code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- name: Upload - name: Upload
uses: localazy/upload@27e6b5c0fddf4551596b42226b1c24124335d24a # v1 uses: localazy/upload@27e6b5c0fddf4551596b42226b1c24124335d24a # v1

View File

@@ -109,7 +109,7 @@
"livekit-client": "^2.13.0", "livekit-client": "^2.13.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"loglevel": "^1.9.1", "loglevel": "^1.9.1",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#head=voip-team/multi-SFU", "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#head=toger5/use-relation-based-CallMembership-create-ts",
"matrix-widget-api": "^1.13.0", "matrix-widget-api": "^1.13.0",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"observable-hooks": "^4.2.3", "observable-hooks": "^4.2.3",

View File

@@ -20,6 +20,7 @@ import {
MatrixRTCSessionManagerEvents, MatrixRTCSessionManagerEvents,
type MatrixRTCSession, type MatrixRTCSession,
} from "matrix-js-sdk/lib/matrixrtc"; } from "matrix-js-sdk/lib/matrixrtc";
import { logger } from "matrix-js-sdk/lib/logger";
import { getKeyForRoom } from "../e2ee/sharedKeyManagement"; import { getKeyForRoom } from "../e2ee/sharedKeyManagement";
@@ -139,22 +140,24 @@ export function useGroupCallRooms(client: MatrixClient): GroupCallRoom[] {
.filter(roomHasCallMembershipEvents) .filter(roomHasCallMembershipEvents)
.filter(roomIsJoinable); .filter(roomIsJoinable);
const sortedRooms = sortRooms(client, rooms); const sortedRooms = sortRooms(client, rooms);
const items = sortedRooms.map((room) => { Promise.all(
const session = client.matrixRTC.getRoomSession(room); sortedRooms.map(async (room) => {
return { const session = await client.matrixRTC.getRoomSession(room);
roomAlias: room.getCanonicalAlias() ?? undefined, return {
roomName: room.name, roomAlias: room.getCanonicalAlias() ?? undefined,
avatarUrl: room.getMxcAvatarUrl()!, roomName: room.name,
room, avatarUrl: room.getMxcAvatarUrl()!,
session, room,
participants: session.memberships session,
.filter((m) => m.sender) participants: session.memberships
.map((m) => room.getMember(m.sender!)) .filter((m) => m.sender)
.filter((m) => m) as RoomMember[], .map((m) => room.getMember(m.sender!))
}; .filter((m) => m) as RoomMember[],
}); };
}),
setRooms(items); )
.then((items) => setRooms(items))
.catch(logger.error);
} }
updateRooms(); updateRooms();

View File

@@ -436,7 +436,10 @@ export const GroupCallView: FC<Props> = ({
client={client} client={client}
matrixInfo={matrixInfo} matrixInfo={matrixInfo}
muteStates={muteStates} muteStates={muteStates}
onEnter={() => setJoined(true)} onEnter={async () => {
setJoined(true);
return Promise.resolve();
}}
confineToRoom={confineToRoom} confineToRoom={confineToRoom}
hideHeader={header === HeaderStyle.None} hideHeader={header === HeaderStyle.None}
participantCount={participantCount} participantCount={participantCount}

View File

@@ -57,7 +57,7 @@ interface Props {
client: MatrixClient; client: MatrixClient;
matrixInfo: MatrixInfo; matrixInfo: MatrixInfo;
muteStates: MuteStates; muteStates: MuteStates;
onEnter: () => void; onEnter: () => Promise<void>;
enterLabel?: JSX.Element | string; enterLabel?: JSX.Element | string;
confineToRoom: boolean; confineToRoom: boolean;
hideHeader: boolean; hideHeader: boolean;
@@ -183,6 +183,14 @@ export const LobbyView: FC<Props> = ({
useTrackProcessorSync(videoTrack); useTrackProcessorSync(videoTrack);
const [waitingToEnter, setWaitingToEnter] = useState(false);
const onEnterCall = useCallback(() => {
setWaitingToEnter(true);
void onEnter().finally(() => setWaitingToEnter(false));
}, [onEnter]);
const waiting = waitingForInvite || waitingToEnter;
// TODO: Unify this component with InCallView, so we can get slick joining // TODO: Unify this component with InCallView, so we can get slick joining
// animations and don't have to feel bad about reusing its CSS // animations and don't have to feel bad about reusing its CSS
return ( return (
@@ -212,11 +220,12 @@ export const LobbyView: FC<Props> = ({
> >
<Button <Button
className={classNames(styles.join, { className={classNames(styles.join, {
[styles.wait]: waitingForInvite, [styles.wait]: waiting,
})} })}
size={waitingForInvite ? "sm" : "lg"} size={waiting ? "sm" : "lg"}
disabled={waiting}
onClick={() => { onClick={() => {
if (!waitingForInvite) onEnter(); if (!waiting) onEnterCall();
}} }}
data-testid="lobby_joinCall" data-testid="lobby_joinCall"
> >

View File

@@ -150,35 +150,34 @@ export const RoomPage: FC = () => {
</> </>
); );
return ( return (
muteStates && ( muteStates && <LobbyView
<LobbyView client={client!}
client={client!} matrixInfo={{
matrixInfo={{ userId: client!.getUserId() ?? "",
userId: client!.getUserId() ?? "", displayName: userDisplayName ?? "",
displayName: userDisplayName ?? "", avatarUrl: avatarUrl ?? "",
avatarUrl: avatarUrl ?? "", roomAlias: null,
roomAlias: null, roomId: groupCallState.roomSummary.room_id,
roomId: groupCallState.roomSummary.room_id, roomName: groupCallState.roomSummary.name ?? "",
roomName: groupCallState.roomSummary.name ?? "", roomAvatar: groupCallState.roomSummary.avatar_url ?? null,
roomAvatar: groupCallState.roomSummary.avatar_url ?? null, e2eeSystem: {
e2eeSystem: { kind: groupCallState.roomSummary["im.nheko.summary.encryption"]
kind: groupCallState.roomSummary[ ? E2eeType.PER_PARTICIPANT
"im.nheko.summary.encryption" : E2eeType.NONE,
] },
? E2eeType.PER_PARTICIPANT }}
: E2eeType.NONE, onEnter={async (): Promise<void> => {
}, knock?.();
}} return Promise.resolve();
onEnter={(): void => knock?.()} }}
enterLabel={label} enterLabel={label}
waitingForInvite={groupCallState.kind === "waitForInvite"} waitingForInvite={groupCallState.kind === "waitForInvite"}
confineToRoom={confineToRoom} confineToRoom={confineToRoom}
hideHeader={header !== "standard"} hideHeader={header !== "standard"}
participantCount={null} participantCount={null}
muteStates={muteStates} muteStates={muteStates}
onShareClick={null} onShareClick={null}
/> />
)
); );
} }
case "loading": case "loading":

View File

@@ -528,10 +528,12 @@ export class CallViewModel extends ViewModel {
multiSfu.value$, multiSfu.value$,
], ],
(preferred, memberships, multiSfu) => { (preferred, memberships, multiSfu) => {
const oldestMembership =
this.matrixRTCSession.getOldestMembership();
const remote = memberships.flatMap((m) => { const remote = memberships.flatMap((m) => {
if (m.sender === this.userId && m.deviceId === this.deviceId) if (m.sender === this.userId && m.deviceId === this.deviceId)
return []; return [];
const t = this.matrixRTCSession.resolveActiveFocus(m); const t = m.getTransport(oldestMembership ?? m);
return t && isLivekitTransport(t) return t && isLivekitTransport(t)
? [{ membership: m, transport: t }] ? [{ membership: m, transport: t }]
: []; : [];
@@ -633,10 +635,11 @@ export class CallViewModel extends ViewModel {
// Until the local transport becomes ready we have no idea which // Until the local transport becomes ready we have no idea which
// transports will actually need a dedicated remote connection // transports will actually need a dedicated remote connection
if (transports?.local.state === "ready") { if (transports?.local.state === "ready") {
const oldestMembership = this.matrixRTCSession.getOldestMembership();
const localServiceUrl = transports.local.value.livekit_service_url; const localServiceUrl = transports.local.value.livekit_service_url;
const remoteServiceUrls = new Set( const remoteServiceUrls = new Set(
transports.remote.flatMap(({ membership, transport }) => { transports.remote.flatMap(({ membership, transport }) => {
const t = this.matrixRTCSession.resolveActiveFocus(membership); const t = membership.getTransport(oldestMembership ?? membership);
return t && return t &&
isLivekitTransport(t) && isLivekitTransport(t) &&
t.livekit_service_url !== localServiceUrl t.livekit_service_url !== localServiceUrl

View File

@@ -99,7 +99,19 @@ export class ObservableScope {
.subscribe(callback); .subscribe(callback);
} }
// TODO-MULTI-SFU Dear Future Robin, please document this. Love, Past Robin. /**
* For the duration of the scope, sync some external state with the value of
* the provided Behavior by way of an async function which attempts to update
* (reconcile) the external state. The reconciliation function may return a
* clean-up callback which will be called and awaited before the next change
* in value (or the end of the scope).
*
* All calls to the function and its clean-up callbacks are serialized. If the
* value changes faster than the handlers can keep up with, intermediate
* values may be skipped.
*
* Basically, this is like React's useEffect but async and for Behaviors.
*/
public reconcile<T>( public reconcile<T>(
value$: Behavior<T>, value$: Behavior<T>,
callback: (value: T) => Promise<(() => Promise<void>) | undefined>, callback: (value: T) => Promise<(() => Promise<void>) | undefined>,
@@ -107,27 +119,27 @@ export class ObservableScope {
let latestValue: T | typeof nothing = nothing; let latestValue: T | typeof nothing = nothing;
let reconciledValue: T | typeof nothing = nothing; let reconciledValue: T | typeof nothing = nothing;
let cleanUp: (() => Promise<void>) | undefined = undefined; let cleanUp: (() => Promise<void>) | undefined = undefined;
let callbackPromise: Promise<(() => Promise<void>) | undefined>;
value$ value$
.pipe( .pipe(
catchError(() => EMPTY), catchError(() => EMPTY), // Ignore errors
this.bind(), this.bind(), // Limit to the duration of the scope
endWith(nothing), endWith(nothing), // Clean up when the scope ends
) )
.subscribe((value) => { .subscribe((value) => {
void (async (): Promise<void> => { void (async (): Promise<void> => {
if (latestValue === nothing) { if (latestValue === nothing) {
latestValue = value; latestValue = value;
while (latestValue !== reconciledValue) { while (latestValue !== reconciledValue) {
await cleanUp?.(); await cleanUp?.(); // Call the previous value's clean-up handler
reconciledValue = latestValue; reconciledValue = latestValue;
if (latestValue !== nothing) { if (latestValue !== nothing)
callbackPromise = callback(latestValue); cleanUp = await callback(latestValue); // Sync current value
cleanUp = await callbackPromise;
}
} }
// Reset to signal that reconciliation is done for now
latestValue = nothing; latestValue = nothing;
} else { } else {
// There's already an instance of the above 'while' loop running
// concurrently. Just update the latest value and let it be handled.
latestValue = value; latestValue = value;
} }
})(); })();

View File

@@ -7545,7 +7545,7 @@ __metadata:
livekit-client: "npm:^2.13.0" livekit-client: "npm:^2.13.0"
lodash-es: "npm:^4.17.21" lodash-es: "npm:^4.17.21"
loglevel: "npm:^1.9.1" loglevel: "npm:^1.9.1"
matrix-js-sdk: "github:matrix-org/matrix-js-sdk#head=voip-team/multi-SFU" matrix-js-sdk: "github:matrix-org/matrix-js-sdk#head=toger5/use-relation-based-CallMembership-create-ts"
matrix-widget-api: "npm:^1.13.0" matrix-widget-api: "npm:^1.13.0"
normalize.css: "npm:^8.0.1" normalize.css: "npm:^8.0.1"
observable-hooks: "npm:^4.2.3" observable-hooks: "npm:^4.2.3"
@@ -10335,9 +10335,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#head=voip-team/multi-SFU": "matrix-js-sdk@github:matrix-org/matrix-js-sdk#head=toger5/use-relation-based-CallMembership-create-ts":
version: 38.3.0 version: 38.4.0
resolution: "matrix-js-sdk@https://github.com/matrix-org/matrix-js-sdk.git#commit=4643844597f8bd0196714ecc1c7fafd3f3f6669d" resolution: "matrix-js-sdk@https://github.com/matrix-org/matrix-js-sdk.git#commit=4608506288c6beaa252982d224e996e23e51f681"
dependencies: dependencies:
"@babel/runtime": "npm:^7.12.5" "@babel/runtime": "npm:^7.12.5"
"@matrix-org/matrix-sdk-crypto-wasm": "npm:^15.3.0" "@matrix-org/matrix-sdk-crypto-wasm": "npm:^15.3.0"
@@ -10353,7 +10353,7 @@ __metadata:
sdp-transform: "npm:^2.14.1" sdp-transform: "npm:^2.14.1"
unhomoglyph: "npm:^1.0.6" unhomoglyph: "npm:^1.0.6"
uuid: "npm:13" uuid: "npm:13"
checksum: 10c0/90d6feb7c5214b2fce7b8d6394c88d39a538224dac464e2a5315fb63388126999da28284bc9b7443e035ad0f24c21c1c7d9e1ad4245ee854595e73a390f48c2a checksum: 10c0/2e896d6a92cb3bbb47c120a39dd1a0030b4bf02289cb914f6c848b564208f421ada605e8efb68f6d9d55a0d2e3f86698b6076cb029e9bab2bac0f70f7250dd17
languageName: node languageName: node
linkType: hard linkType: hard