Add support for m.expires_ts WIP

This commit is contained in:
Robert Long 2022-09-18 16:42:52 -07:00
parent 1d1103a3c7
commit af433aa762
4 changed files with 66 additions and 4 deletions

View File

@ -503,6 +503,7 @@ export class Session {
room.dispose();
}
this._rooms = undefined;
this.callHandler.dispose();
}
/**

View File

@ -54,6 +54,7 @@ export class CallHandler implements RoomStateHandler {
private roomMemberToCallIds: Map<string, Set<string>> = new Map();
private groupCallOptions: GroupCallOptions;
private sessionId = makeId("s");
private memberStateExpirationTimers: Map<string, number> = new Map();
constructor(private readonly options: Options) {
this.groupCallOptions = Object.assign({}, this.options, {
@ -217,6 +218,29 @@ export class CallHandler implements RoomStateHandler {
const userId = event.state_key;
const roomMemberKey = getRoomMemberKey(roomId, userId)
const calls = event.content["m.calls"] ?? [];
const now = Date.now();
const expiresAt = typeof event.content["m.expires_ts"] === "number" ? event.content["m.expires_ts"] : -Infinity;
console.log(`expiresAt ${expiresAt} now ${now}`, event.content);
if (expiresAt <= now) {
console.log(`Initial ${roomId} ${member.userId} expired`);
return;
}
clearTimeout(this.memberStateExpirationTimers.get(member.userId));
this.memberStateExpirationTimers.set(member.userId, setTimeout(() => {
console.log(`Timeout ${roomId} ${userId} expired`)
for (const callId of calls.map(call => call["m.call_id"])) {
const groupCall = this._calls.get(callId);
groupCall?.removeMembership(userId, log);
}
this.roomMemberToCallIds.delete(roomMemberKey);
}, event.content["m.expires_ts"] - Date.now()));
const eventTimestamp = event.origin_server_ts;
for (const call of calls) {
const callId = call["m.call_id"];
@ -242,5 +266,12 @@ export class CallHandler implements RoomStateHandler {
this.roomMemberToCallIds.set(roomMemberKey, newCallIdsMemberOf);
}
}
dispose() {
for (const [userId] of this.memberStateExpirationTimers) {
clearTimeout(this.memberStateExpirationTimers.get(userId));
this.memberStateExpirationTimers.delete(userId);
}
}
}

View File

@ -34,6 +34,7 @@ export interface CallMembership {
export interface CallMemberContent {
["m.calls"]: CallMembership[];
["m.expires_ts"]?: number
}
export enum SDPStreamMetadataPurpose {

View File

@ -33,6 +33,8 @@ import type {EncryptedMessage} from "../../e2ee/olm/Encryption";
import type {ILogItem, ILogger} from "../../../logging/types";
import type {Storage} from "../../storage/idb/Storage";
const CALL_MEMBER_STATE_TIMEOUT = 1000 * 60 * 60; // 1 hour
export enum GroupCallState {
Fledgling = "fledgling",
Creating = "creating",
@ -83,6 +85,7 @@ export class GroupCall extends EventEmitter<{change: never}> {
private _deviceIndex?: number;
private _eventTimestamp?: number;
private resendMemberStateTimer?: number;
constructor(
public readonly id: string,
@ -161,9 +164,11 @@ export class GroupCall extends EventEmitter<{change: never}> {
this._state = GroupCallState.Joining;
this.emitChange();
await log.wrap("update member state", async log => {
const memberContent = await this._createJoinPayload();
const memberContent = await this._createMemberStateEventPayload();
this._eventTimestamp = Date.now();
log.set("payload", memberContent);
// send m.call.member state event
console.log("sending", memberContent);
const request = this.options.hsApi.sendState(this.roomId, EventType.GroupCallMember, this.options.ownUserId, memberContent, {log});
await request.response();
this.emitChange();
@ -173,6 +178,24 @@ export class GroupCall extends EventEmitter<{change: never}> {
this.connectToMember(member, joinedData, log);
}
});
clearTimeout(this.resendMemberStateTimer);
this.resendMemberStateTimer = setTimeout(this.resendMemberStateEvent, CALL_MEMBER_STATE_TIMEOUT * 3 / 4);
}
async resendMemberStateEvent() {
await this.options.logger.run("resend member state event", async log => {
const memberContent = await this._createMemberStateEventPayload();
this._eventTimestamp = Date.now();
log.set("payload", memberContent);
// send m.call.member state event
const request = this.options.hsApi.sendState(this.roomId, EventType.GroupCallMember, this.options.ownUserId, memberContent, {log});
await request.response();
this.emitChange();
});
this.resendMemberStateTimer = setTimeout(this.resendMemberStateEvent, CALL_MEMBER_STATE_TIMEOUT * 3 / 4);
}
async setMedia(localMedia: LocalMedia): Promise<void> {
@ -425,6 +448,7 @@ export class GroupCall extends EventEmitter<{change: never}> {
}
this._state = GroupCallState.Created;
}
clearTimeout(this.resendMemberStateTimer);
this.joinedData?.dispose();
this.joinedData = undefined;
this.emitChange();
@ -476,13 +500,17 @@ export class GroupCall extends EventEmitter<{change: never}> {
}
}
private async _createJoinPayload() {
private async _createMemberStateEventPayload() {
const {storage} = this.options;
const txn = await storage.readTxn([storage.storeNames.roomState]);
const stateEvent = await txn.roomState.get(this.roomId, EventType.GroupCallMember, this.options.ownUserId);
const expiresAt = Date.now() + CALL_MEMBER_STATE_TIMEOUT;
const stateContent = stateEvent?.event?.content ?? {
["m.calls"]: []
["m.calls"]: [],
};
stateContent["m.expires_ts"] = expiresAt;
const callsInfo = stateContent["m.calls"];
let callInfo = callsInfo.find(c => c["m.call_id"] === this.id);
if (!callInfo) {
@ -500,7 +528,8 @@ export class GroupCall extends EventEmitter<{change: never}> {
});
this._deviceIndex = callInfo["m.devices"].length;
this._eventTimestamp = Date.now();
console.log(`sent member state event expiresAt ${expiresAt}`);
return stateContent;
}