Merge pull request #3467 from element-hq/toger5/call-pickup-state-decline-event
View model for decline logic
This commit is contained in:
@@ -158,7 +158,8 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
|||||||
};
|
};
|
||||||
}, [livekitRoom]);
|
}, [livekitRoom]);
|
||||||
|
|
||||||
const { autoLeaveWhenOthersLeft } = useUrlParams();
|
const { autoLeaveWhenOthersLeft, sendNotificationType, waitForCallPickup } =
|
||||||
|
useUrlParams();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (livekitRoom !== undefined) {
|
if (livekitRoom !== undefined) {
|
||||||
@@ -171,6 +172,8 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
|||||||
{
|
{
|
||||||
encryptionSystem: props.e2eeSystem,
|
encryptionSystem: props.e2eeSystem,
|
||||||
autoLeaveWhenOthersLeft,
|
autoLeaveWhenOthersLeft,
|
||||||
|
waitForCallPickup:
|
||||||
|
waitForCallPickup && sendNotificationType === "ring",
|
||||||
},
|
},
|
||||||
connStateObservable$,
|
connStateObservable$,
|
||||||
reactionsReader.raisedHands$,
|
reactionsReader.raisedHands$,
|
||||||
@@ -190,6 +193,8 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
|||||||
props.e2eeSystem,
|
props.e2eeSystem,
|
||||||
connStateObservable$,
|
connStateObservable$,
|
||||||
autoLeaveWhenOthersLeft,
|
autoLeaveWhenOthersLeft,
|
||||||
|
sendNotificationType,
|
||||||
|
waitForCallPickup,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (livekitRoom === undefined || vm === null) return null;
|
if (livekitRoom === undefined || vm === null) return null;
|
||||||
|
|||||||
@@ -18,7 +18,16 @@ import {
|
|||||||
of,
|
of,
|
||||||
switchMap,
|
switchMap,
|
||||||
} from "rxjs";
|
} from "rxjs";
|
||||||
import { ClientEvent, SyncState, type MatrixClient } from "matrix-js-sdk";
|
import {
|
||||||
|
ClientEvent,
|
||||||
|
SyncState,
|
||||||
|
type MatrixClient,
|
||||||
|
RoomEvent as MatrixRoomEvent,
|
||||||
|
MatrixEvent,
|
||||||
|
type IRoomTimelineData,
|
||||||
|
EventType,
|
||||||
|
type IEvent,
|
||||||
|
} from "matrix-js-sdk";
|
||||||
import {
|
import {
|
||||||
ConnectionState,
|
ConnectionState,
|
||||||
type LocalParticipant,
|
type LocalParticipant,
|
||||||
@@ -237,6 +246,23 @@ function summarizeLayout$(l$: Observable<Layout>): Observable<LayoutSummary> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mockRingEvent(
|
||||||
|
eventId: string,
|
||||||
|
lifetimeMs: number | undefined,
|
||||||
|
sender = local.userId,
|
||||||
|
): { event_id: string } & IRTCNotificationContent {
|
||||||
|
return {
|
||||||
|
event_id: eventId,
|
||||||
|
...(lifetimeMs === undefined ? {} : { lifetime: lifetimeMs }),
|
||||||
|
notification_type: "ring",
|
||||||
|
sender,
|
||||||
|
} as unknown as { event_id: string } & IRTCNotificationContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The app doesn't really care about the content of these legacy events, we just
|
||||||
|
// need a value to fill in for them when emitting notifications
|
||||||
|
const mockLegacyRingEvent = {} as { event_id: string } & ICallNotifyContent;
|
||||||
|
|
||||||
interface CallViewModelInputs {
|
interface CallViewModelInputs {
|
||||||
remoteParticipants$: Behavior<RemoteParticipant[]>;
|
remoteParticipants$: Behavior<RemoteParticipant[]>;
|
||||||
rtcMembers$: Behavior<Partial<CallMembership>[]>;
|
rtcMembers$: Behavior<Partial<CallMembership>[]>;
|
||||||
@@ -1205,10 +1231,8 @@ describe("waitForCallPickup$", () => {
|
|||||||
r: () => {
|
r: () => {
|
||||||
rtcSession.emit(
|
rtcSession.emit(
|
||||||
MatrixRTCSessionEvent.DidSendCallNotification,
|
MatrixRTCSessionEvent.DidSendCallNotification,
|
||||||
{ lifetime: 30 } as unknown as {
|
mockRingEvent("$notif1", 30),
|
||||||
event_id: string;
|
mockLegacyRingEvent,
|
||||||
} & IRTCNotificationContent,
|
|
||||||
{} as unknown as { event_id: string } & ICallNotifyContent,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1247,12 +1271,8 @@ describe("waitForCallPickup$", () => {
|
|||||||
r: () => {
|
r: () => {
|
||||||
rtcSession.emit(
|
rtcSession.emit(
|
||||||
MatrixRTCSessionEvent.DidSendCallNotification,
|
MatrixRTCSessionEvent.DidSendCallNotification,
|
||||||
{ lifetime: 100 } as unknown as {
|
mockRingEvent("$notif2", 100),
|
||||||
event_id: string;
|
mockLegacyRingEvent,
|
||||||
} & IRTCNotificationContent,
|
|
||||||
{} as unknown as {
|
|
||||||
event_id: string;
|
|
||||||
} & ICallNotifyContent,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1290,12 +1310,8 @@ describe("waitForCallPickup$", () => {
|
|||||||
r: () => {
|
r: () => {
|
||||||
rtcSession.emit(
|
rtcSession.emit(
|
||||||
MatrixRTCSessionEvent.DidSendCallNotification,
|
MatrixRTCSessionEvent.DidSendCallNotification,
|
||||||
{ lifetime: 50 } as unknown as {
|
mockRingEvent("$notif3", 50),
|
||||||
event_id: string;
|
mockLegacyRingEvent,
|
||||||
} & IRTCNotificationContent,
|
|
||||||
{} as unknown as {
|
|
||||||
event_id: string;
|
|
||||||
} & ICallNotifyContent,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1321,12 +1337,8 @@ describe("waitForCallPickup$", () => {
|
|||||||
r: () => {
|
r: () => {
|
||||||
rtcSession.emit(
|
rtcSession.emit(
|
||||||
MatrixRTCSessionEvent.DidSendCallNotification,
|
MatrixRTCSessionEvent.DidSendCallNotification,
|
||||||
{} as unknown as {
|
mockRingEvent("$notif4", undefined),
|
||||||
event_id: string;
|
mockLegacyRingEvent,
|
||||||
} & IRTCNotificationContent, // no lifetime
|
|
||||||
{} as unknown as {
|
|
||||||
event_id: string;
|
|
||||||
} & ICallNotifyContent,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1361,12 +1373,8 @@ describe("waitForCallPickup$", () => {
|
|||||||
r: () => {
|
r: () => {
|
||||||
rtcSession.emit(
|
rtcSession.emit(
|
||||||
MatrixRTCSessionEvent.DidSendCallNotification,
|
MatrixRTCSessionEvent.DidSendCallNotification,
|
||||||
{ lifetime: 30 } as unknown as {
|
mockRingEvent("$notif5", 30),
|
||||||
event_id: string;
|
mockLegacyRingEvent,
|
||||||
} & IRTCNotificationContent,
|
|
||||||
{} as unknown as {
|
|
||||||
event_id: string;
|
|
||||||
} & ICallNotifyContent,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1381,6 +1389,149 @@ describe("waitForCallPickup$", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("decline before timeout window ends -> decline", () => {
|
||||||
|
withTestScheduler(({ schedule, expectObservable }) => {
|
||||||
|
withCallViewModel(
|
||||||
|
{},
|
||||||
|
(vm, rtcSession) => {
|
||||||
|
// Notify at 10ms with 50ms lifetime, decline at 40ms with matching id
|
||||||
|
schedule(" 10ms r 29ms d", {
|
||||||
|
r: () => {
|
||||||
|
rtcSession.emit(
|
||||||
|
MatrixRTCSessionEvent.DidSendCallNotification,
|
||||||
|
mockRingEvent("$decl1", 50),
|
||||||
|
mockLegacyRingEvent,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
d: () => {
|
||||||
|
// Emit decline timeline event with id matching the notification
|
||||||
|
rtcSession.room.emit(
|
||||||
|
MatrixRoomEvent.Timeline,
|
||||||
|
new MatrixEvent({
|
||||||
|
type: EventType.RTCDecline,
|
||||||
|
content: {
|
||||||
|
"m.relates_to": {
|
||||||
|
rel_type: "m.reference",
|
||||||
|
event_id: "$decl1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
rtcSession.room,
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
{} as IRoomTimelineData,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expectObservable(vm.callPickupState$).toBe("a 9ms b 29ms e", {
|
||||||
|
a: "unknown",
|
||||||
|
b: "ringing",
|
||||||
|
e: "decline",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
waitForCallPickup: true,
|
||||||
|
encryptionSystem: { kind: E2eeType.PER_PARTICIPANT },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("decline after timeout window ends -> stays timeout", () => {
|
||||||
|
withTestScheduler(({ schedule, expectObservable }) => {
|
||||||
|
withCallViewModel(
|
||||||
|
{},
|
||||||
|
(vm, rtcSession) => {
|
||||||
|
// Notify at 10ms with 20ms lifetime (timeout at 30ms), decline at 40ms
|
||||||
|
schedule(" 10ms r 20ms t 10ms d", {
|
||||||
|
r: () => {
|
||||||
|
rtcSession.emit(
|
||||||
|
MatrixRTCSessionEvent.DidSendCallNotification,
|
||||||
|
mockRingEvent("$decl2", 20),
|
||||||
|
mockLegacyRingEvent,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
t: () => {},
|
||||||
|
d: () => {
|
||||||
|
rtcSession.room.emit(
|
||||||
|
MatrixRoomEvent.Timeline,
|
||||||
|
new MatrixEvent({ event_id: "$decl2", type: "m.rtc.decline" }),
|
||||||
|
rtcSession.room,
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
{} as IRoomTimelineData,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expectObservable(vm.callPickupState$).toBe("a 9ms b 19ms c", {
|
||||||
|
a: "unknown",
|
||||||
|
b: "ringing",
|
||||||
|
c: "timeout",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
waitForCallPickup: true,
|
||||||
|
encryptionSystem: { kind: E2eeType.PER_PARTICIPANT },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function testStaysRinging(declineEvent: Partial<IEvent>): void {
|
||||||
|
withTestScheduler(({ schedule, expectObservable }) => {
|
||||||
|
withCallViewModel(
|
||||||
|
{},
|
||||||
|
(vm, rtcSession) => {
|
||||||
|
// Notify at 10ms with id A, decline arrives at 20ms with id B
|
||||||
|
schedule(" 10ms r 10ms d", {
|
||||||
|
r: () => {
|
||||||
|
rtcSession.emit(
|
||||||
|
MatrixRTCSessionEvent.DidSendCallNotification,
|
||||||
|
mockRingEvent("$right", 50),
|
||||||
|
mockLegacyRingEvent,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
d: () => {
|
||||||
|
rtcSession.room.emit(
|
||||||
|
MatrixRoomEvent.Timeline,
|
||||||
|
new MatrixEvent(declineEvent),
|
||||||
|
rtcSession.room,
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
{} as IRoomTimelineData,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// We assert up to 21ms to see the ringing at 10ms and no change at 20ms
|
||||||
|
expectObservable(vm.callPickupState$, "21ms !").toBe("a 9ms b", {
|
||||||
|
a: "unknown",
|
||||||
|
b: "ringing",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
waitForCallPickup: true,
|
||||||
|
encryptionSystem: { kind: E2eeType.PER_PARTICIPANT },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test("decline with wrong id is ignored (stays ringing)", () => {
|
||||||
|
testStaysRinging({
|
||||||
|
event_id: "$wrong",
|
||||||
|
type: "m.rtc.decline",
|
||||||
|
sender: local.userId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("decline with sender being the local user is ignored (stays ringing)", () => {
|
||||||
|
testStaysRinging({
|
||||||
|
event_id: "$right",
|
||||||
|
type: "m.rtc.decline",
|
||||||
|
sender: alice.userId,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("audio output changes when toggling earpiece mode", () => {
|
test("audio output changes when toggling earpiece mode", () => {
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ import {
|
|||||||
} from "livekit-client";
|
} from "livekit-client";
|
||||||
import {
|
import {
|
||||||
ClientEvent,
|
ClientEvent,
|
||||||
|
type EventTimelineSetHandlerMap,
|
||||||
|
EventType,
|
||||||
|
RoomEvent,
|
||||||
RoomStateEvent,
|
RoomStateEvent,
|
||||||
SyncState,
|
SyncState,
|
||||||
type Room as MatrixRoom,
|
type Room as MatrixRoom,
|
||||||
@@ -33,6 +36,7 @@ import {
|
|||||||
combineLatest,
|
combineLatest,
|
||||||
concat,
|
concat,
|
||||||
distinctUntilChanged,
|
distinctUntilChanged,
|
||||||
|
endWith,
|
||||||
filter,
|
filter,
|
||||||
forkJoin,
|
forkJoin,
|
||||||
fromEvent,
|
fromEvent,
|
||||||
@@ -58,9 +62,9 @@ import {
|
|||||||
import { logger } from "matrix-js-sdk/lib/logger";
|
import { logger } from "matrix-js-sdk/lib/logger";
|
||||||
import {
|
import {
|
||||||
type CallMembership,
|
type CallMembership,
|
||||||
type IRTCNotificationContent,
|
|
||||||
type MatrixRTCSession,
|
type MatrixRTCSession,
|
||||||
MatrixRTCSessionEvent,
|
MatrixRTCSessionEvent,
|
||||||
|
type MatrixRTCSessionEventHandlerMap,
|
||||||
MembershipManagerEvent,
|
MembershipManagerEvent,
|
||||||
Status,
|
Status,
|
||||||
} from "matrix-js-sdk/lib/matrixrtc";
|
} from "matrix-js-sdk/lib/matrixrtc";
|
||||||
@@ -887,17 +891,60 @@ export class CallViewModel extends ViewModel {
|
|||||||
: NEVER;
|
: NEVER;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emits whenever the RTC session tells us that it intends to ring for a given
|
* Whenever the RTC session tells us that it intends to ring the remote
|
||||||
* duration.
|
* participant's devices, this emits an Observable tracking the current state of
|
||||||
|
* that ringing process.
|
||||||
*/
|
*/
|
||||||
private readonly beginRingingForMs$ = (
|
private readonly ring$: Observable<
|
||||||
|
Observable<"ringing" | "timeout" | "decline">
|
||||||
|
> = (
|
||||||
fromEvent(
|
fromEvent(
|
||||||
this.matrixRTCSession,
|
this.matrixRTCSession,
|
||||||
MatrixRTCSessionEvent.DidSendCallNotification,
|
MatrixRTCSessionEvent.DidSendCallNotification,
|
||||||
) as Observable<[IRTCNotificationContent]>
|
) as Observable<
|
||||||
)
|
Parameters<
|
||||||
// event.lifetime is expected to be in ms
|
MatrixRTCSessionEventHandlerMap[MatrixRTCSessionEvent.DidSendCallNotification]
|
||||||
.pipe(map(([notificationEvent]) => notificationEvent?.lifetime ?? 0));
|
>
|
||||||
|
>
|
||||||
|
).pipe(
|
||||||
|
filter(
|
||||||
|
([notificationEvent]) => notificationEvent.notification_type === "ring",
|
||||||
|
),
|
||||||
|
map(([notificationEvent]) => {
|
||||||
|
const lifetimeMs = notificationEvent?.lifetime ?? 0;
|
||||||
|
return concat(
|
||||||
|
lifetimeMs === 0
|
||||||
|
? // If no lifetime, skip the ring state
|
||||||
|
EMPTY
|
||||||
|
: // Ring until lifetime ms have passed
|
||||||
|
timer(lifetimeMs).pipe(
|
||||||
|
ignoreElements(),
|
||||||
|
startWith("ringing" as const),
|
||||||
|
),
|
||||||
|
// The notification lifetime has timed out, meaning ringing has likely
|
||||||
|
// stopped on all receiving clients.
|
||||||
|
of("timeout" as const),
|
||||||
|
NEVER,
|
||||||
|
).pipe(
|
||||||
|
takeUntil(
|
||||||
|
(
|
||||||
|
fromEvent(this.matrixRoom, RoomEvent.Timeline) as Observable<
|
||||||
|
Parameters<EventTimelineSetHandlerMap[RoomEvent.Timeline]>
|
||||||
|
>
|
||||||
|
).pipe(
|
||||||
|
filter(
|
||||||
|
([event]) =>
|
||||||
|
event.getType() === EventType.RTCDecline &&
|
||||||
|
event.getRelation()?.rel_type === "m.reference" &&
|
||||||
|
event.getRelation()?.event_id === notificationEvent.event_id &&
|
||||||
|
event.getSender() !== this.userId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
endWith("decline" as const),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether some Matrix user other than ourself is joined to the call.
|
* Whether some Matrix user other than ourself is joined to the call.
|
||||||
@@ -917,35 +964,21 @@ export class CallViewModel extends ViewModel {
|
|||||||
* - null: EC is configured to never show any waiting for answer state.
|
* - null: EC is configured to never show any waiting for answer state.
|
||||||
*/
|
*/
|
||||||
public readonly callPickupState$ = this.options.waitForCallPickup
|
public readonly callPickupState$ = this.options.waitForCallPickup
|
||||||
? this.scope.behavior<"unknown" | "ringing" | "timeout" | "success">(
|
? this.scope.behavior<
|
||||||
concat(
|
"unknown" | "ringing" | "timeout" | "decline" | "success"
|
||||||
concat(
|
>(
|
||||||
// We don't know if the RTC session decides to send a notify event
|
this.someoneElseJoined$.pipe(
|
||||||
// yet. It will only be known once we sent our own membership and
|
switchMap((someoneElseJoined) =>
|
||||||
// know we were the first one to join.
|
someoneElseJoined
|
||||||
of("unknown" as const),
|
? of("success" as const)
|
||||||
// Once we get the signal to begin ringing:
|
: // Show the ringing state of the most recent ringing attempt.
|
||||||
this.beginRingingForMs$.pipe(
|
this.ring$.pipe(switchAll()),
|
||||||
take(1),
|
|
||||||
switchMap((lifetime) =>
|
|
||||||
lifetime === 0
|
|
||||||
? // If no lifetime, skip the ring state
|
|
||||||
EMPTY
|
|
||||||
: // Ring until lifetime ms have passed
|
|
||||||
timer(lifetime).pipe(
|
|
||||||
ignoreElements(),
|
|
||||||
startWith("ringing" as const),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// The notification lifetime has timed out, meaning ringing has
|
|
||||||
// likely stopped on all receiving clients.
|
|
||||||
of("timeout" as const),
|
|
||||||
NEVER,
|
|
||||||
).pipe(
|
|
||||||
takeUntil(this.someoneElseJoined$.pipe(filter((joined) => joined))),
|
|
||||||
),
|
),
|
||||||
of("success" as const),
|
// The state starts as 'unknown' because we don't know if the RTC
|
||||||
|
// session will actually send a notify event yet. It will only be
|
||||||
|
// known once we send our own membership and see that we were the
|
||||||
|
// first one to join.
|
||||||
|
startWith("unknown" as const),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: constant(null);
|
: constant(null);
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import {
|
|||||||
type RoomAndToDeviceEventsHandlerMap,
|
type RoomAndToDeviceEventsHandlerMap,
|
||||||
} from "matrix-js-sdk/lib/matrixrtc/RoomAndToDeviceKeyTransport";
|
} from "matrix-js-sdk/lib/matrixrtc/RoomAndToDeviceKeyTransport";
|
||||||
import { type TrackReference } from "@livekit/components-core";
|
import { type TrackReference } from "@livekit/components-core";
|
||||||
|
import EventEmitter from "events";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
LocalUserMediaViewModel,
|
LocalUserMediaViewModel,
|
||||||
@@ -144,26 +145,25 @@ export function withTestScheduler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface EmitterMock<T> {
|
interface EmitterMock<T> {
|
||||||
on: () => T;
|
on: (...args: unknown[]) => T;
|
||||||
off: () => T;
|
off: (...args: unknown[]) => T;
|
||||||
addListener: () => T;
|
addListener: (...args: unknown[]) => T;
|
||||||
removeListener: () => T;
|
removeListener: (...args: unknown[]) => T;
|
||||||
|
emit: (event: string | symbol, ...args: unknown[]) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mockEmitter<T>(): EmitterMock<T> {
|
export function mockEmitter<T>(): EmitterMock<T> {
|
||||||
|
const ee = new EventEmitter();
|
||||||
return {
|
return {
|
||||||
on(): T {
|
on: ee.on.bind(ee) as unknown as (...args: unknown[]) => T,
|
||||||
return this as T;
|
off: ee.off.bind(ee) as unknown as (...args: unknown[]) => T,
|
||||||
},
|
addListener: ee.addListener.bind(ee) as unknown as (
|
||||||
off(): T {
|
...args: unknown[]
|
||||||
return this as T;
|
) => T,
|
||||||
},
|
removeListener: ee.removeListener.bind(ee) as unknown as (
|
||||||
addListener(): T {
|
...args: unknown[]
|
||||||
return this as T;
|
) => T,
|
||||||
},
|
emit: ee.emit.bind(ee),
|
||||||
removeListener(): T {
|
|
||||||
return this as T;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10280,7 +10280,7 @@ __metadata:
|
|||||||
|
|
||||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#head=develop":
|
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#head=develop":
|
||||||
version: 37.13.0
|
version: 37.13.0
|
||||||
resolution: "matrix-js-sdk@https://github.com/matrix-org/matrix-js-sdk.git#commit=c4c7f945141e142e6f846b243c33c4af97a9a44b"
|
resolution: "matrix-js-sdk@https://github.com/matrix-org/matrix-js-sdk.git#commit=2f1d654f14be8dd03896e9e76f12017b6f9eec1c"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime": "npm:^7.12.5"
|
"@babel/runtime": "npm:^7.12.5"
|
||||||
"@matrix-org/matrix-sdk-crypto-wasm": "npm:^15.1.0"
|
"@matrix-org/matrix-sdk-crypto-wasm": "npm:^15.1.0"
|
||||||
@@ -10296,7 +10296,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:11"
|
uuid: "npm:11"
|
||||||
checksum: 10c0/caa4b8a6d924ac36a21773dc2c8be6cb6b658a9feaabccdb24426719c563ac2cfe4778abb86f0889854ae36fc7ba02a6ed39acdbc0b73fdc31ce9a9789e7f36a
|
checksum: 10c0/ecd019c677c272c5598617dcde407dbe4b1b11460863b2a577e33f3fd8732c9d9073ec0221b471ec1eb24e2839eec20728db7f92c9348be83126547286e50805
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user