start logging in view model and pass it on to model methods (calls+room)

This commit is contained in:
Bruno Windels 2023-01-17 12:23:20 +01:00
parent 0dbb7d4e50
commit e33209b747
5 changed files with 151 additions and 128 deletions

View File

@ -102,9 +102,9 @@ export class CallViewModel extends ErrorReportViewModel<Options> {
}
async hangup() {
this.logAndCatch("Call.hangup", async log => {
this.logAndCatch("CallViewModel.hangup", async log => {
if (this.call.hasJoined) {
await this.call.leave();
await this.call.leave(log);
}
});
}

View File

@ -24,7 +24,6 @@ import {ErrorReportViewModel} from "../../ErrorReportViewModel";
import {ViewModel} from "../../ViewModel";
import {imageToInfo} from "../common.js";
import {LocalMedia} from "../../../matrix/calls/LocalMedia";
import {ErrorViewModel} from "../../ErrorViewModel";
// TODO: remove fallback so default isn't included in bundle for SDK users that have their custom tileClassForEntry
// this is a breaking SDK change though to make this option mandatory
import {tileClassForEntry as defaultTileClassForEntry} from "./timeline/tiles/index";
@ -74,7 +73,7 @@ export class RoomViewModel extends ErrorReportViewModel {
}
async load() {
this.logAndCatch("Room.load", async log => {
this.logAndCatch("RoomViewModel.load", async log => {
this._room.on("change", this._onRoomChange);
const timeline = await this._room.openTimeline(log);
this._tileOptions = this.childOptions({
@ -99,7 +98,7 @@ export class RoomViewModel extends ErrorReportViewModel {
this._clearUnreadTimout = this.clock.createTimeout(2000);
try {
await this._clearUnreadTimout.elapsed();
await this._room.clearUnread();
await this._room.clearUnread(log);
this._clearUnreadTimout = null;
} catch (err) {
if (err.name === "AbortError") {
@ -111,7 +110,9 @@ export class RoomViewModel extends ErrorReportViewModel {
}
focus() {
this._clearUnreadAfterDelay();
this.logAndCatch("RoomViewModel.focus", async log => {
this._clearUnreadAfterDelay(log);
});
}
dispose() {
@ -191,7 +192,7 @@ export class RoomViewModel extends ErrorReportViewModel {
}
_sendMessage(message, replyingTo) {
return this.logAndCatch("sendMessage", async log => {
return this.logAndCatch("RoomViewModel.sendMessage", async log => {
let success = false;
if (!this._room.isArchived && message) {
let msgtype = "m.text";
@ -214,7 +215,7 @@ export class RoomViewModel extends ErrorReportViewModel {
}
_pickAndSendFile() {
return this.logAndCatch("sendFile", async log => {
return this.logAndCatch("RoomViewModel.sendFile", async log => {
const file = await this.platform.openFile();
if (!file) {
log.set("cancelled", true);
@ -235,7 +236,7 @@ export class RoomViewModel extends ErrorReportViewModel {
}
_pickAndSendVideo() {
return this.logAndCatch("sendVideo", async log => {
return this.logAndCatch("RoomViewModel.sendVideo", async log => {
if (!this.platform.hasReadPixelPermission()) {
throw new Error("Please allow canvas image data access, so we can scale your images down.");
}
@ -277,7 +278,7 @@ export class RoomViewModel extends ErrorReportViewModel {
}
async _pickAndSendPicture() {
this.logAndCatch("sendPicture", async log => {
this.logAndCatch("RoomViewModel.sendPicture", async log => {
if (!this.platform.hasReadPixelPermission()) {
alert("Please allow canvas image data access, so we can scale your images down.");
return;
@ -341,7 +342,8 @@ export class RoomViewModel extends ErrorReportViewModel {
}
startCall() {
return this.logAndCatch("startCall", async log => {
return this.logAndCatch("RoomViewModel.startCall", async log => {
log.set("roomId", this._room.id);
let localMedia;
try {
const stream = await this.platform.mediaDevices.getMediaTracks(false, true);
@ -353,12 +355,18 @@ export class RoomViewModel extends ErrorReportViewModel {
let call;
try {
// this will set the callViewModel above as a call will be added to callHandler.calls
call = await session.callHandler.createCall(this._room.id, "m.video", "A call " + Math.round(this.platform.random() * 100));
call = await session.callHandler.createCall(
this._room.id,
"m.video",
"A call " + Math.round(this.platform.random() * 100),
undefined,
log
);
} catch (err) {
throw new Error(`Could not create call: ${err.message}`);
}
try {
await call.join(localMedia);
await call.join(localMedia, log);
} catch (err) {
throw new Error(`Could not join call: ${err.message}`);
}

View File

@ -16,7 +16,6 @@ limitations under the License.
import {SimpleTile} from "./SimpleTile.js";
import {LocalMedia} from "../../../../../matrix/calls/LocalMedia";
import {ErrorViewModel} from "../../../../ErrorViewModel"
// TODO: timeline entries for state events with the same state key and type
// should also update previous entries in the timeline, so we can update the name of the call, whether it is terminated, etc ...
@ -73,19 +72,19 @@ export class CallTile extends SimpleTile {
}
async join() {
await this.logAndCatch("Call.join", async log => {
await this.logAndCatch("CallTile.join", async log => {
if (this.canJoin) {
const stream = await this.platform.mediaDevices.getMediaTracks(false, true);
const localMedia = new LocalMedia().withUserMedia(stream);
await this._call.join(localMedia);
await this._call.join(localMedia, log);
}
});
}
async leave() {
await this.logAndCatch("Call.leave", async log => {
await this.logAndCatch("CallTile.leave", async log => {
if (this.canLeave) {
await this._call.leave();
await this._call.leave(log);
}
});
}

View File

@ -65,16 +65,26 @@ export class CallHandler implements RoomStateHandler {
});
}
async loadCalls(intent: CallIntent = CallIntent.Ring) {
const txn = await this._getLoadTxn();
const callEntries = await txn.calls.getByIntent(intent);
this._loadCallEntries(callEntries, txn);
loadCalls(intent?: CallIntent, log?: ILogItem) {
return this.options.logger.wrapOrRun(log, "CallHandler.loadCalls", async log => {
if (!intent) {
intent = CallIntent.Ring;
}
log.set("intent", intent);
const txn = await this._getLoadTxn();
const callEntries = await txn.calls.getByIntent(intent);
await this._loadCallEntries(callEntries, txn, log);
});
}
async loadCallsForRoom(intent: CallIntent, roomId: string) {
const txn = await this._getLoadTxn();
const callEntries = await txn.calls.getByIntentAndRoom(intent, roomId);
this._loadCallEntries(callEntries, txn);
loadCallsForRoom(intent: CallIntent, roomId: string, log?: ILogItem) {
return this.options.logger.wrapOrRun(log, "CallHandler.loadCallsForRoom", async log => {
log.set("intent", intent);
log.set("roomId", roomId);
const txn = await this._getLoadTxn();
const callEntries = await txn.calls.getByIntentAndRoom(intent, roomId);
await this._loadCallEntries(callEntries, txn, log);
});
}
private async _getLoadTxn(): Promise<Transaction> {
@ -86,68 +96,71 @@ export class CallHandler implements RoomStateHandler {
return txn;
}
private async _loadCallEntries(callEntries: CallEntry[], txn: Transaction): Promise<void> {
return this.options.logger.run({l: "loading calls", t: CALL_LOG_TYPE}, async log => {
log.set("entries", callEntries.length);
await Promise.all(callEntries.map(async callEntry => {
if (this._calls.get(callEntry.callId)) {
return;
private async _loadCallEntries(callEntries: CallEntry[], txn: Transaction, log: ILogItem): Promise<void> {
log.set("entries", callEntries.length);
await Promise.all(callEntries.map(async callEntry => {
if (this._calls.get(callEntry.callId)) {
return;
}
const event = await txn.roomState.get(callEntry.roomId, EventType.GroupCall, callEntry.callId);
if (event) {
const call = new GroupCall(event.event.state_key, false, event.event.content, event.roomId, this.groupCallOptions);
this._calls.set(call.id, call);
}
}));
const roomIds = Array.from(new Set(callEntries.map(e => e.roomId)));
await Promise.all(roomIds.map(async roomId => {
// TODO: don't load all members until we need them
const callsMemberEvents = await txn.roomState.getAllForType(roomId, EventType.GroupCallMember);
await Promise.all(callsMemberEvents.map(async entry => {
const userId = entry.event.sender;
const roomMemberState = await txn.roomState.get(roomId, MEMBER_EVENT_TYPE, userId);
let roomMember;
if (roomMemberState) {
roomMember = RoomMember.fromMemberEvent(roomMemberState.event);
}
const event = await txn.roomState.get(callEntry.roomId, EventType.GroupCall, callEntry.callId);
if (event) {
const call = new GroupCall(event.event.state_key, false, event.event.content, event.roomId, this.groupCallOptions);
this._calls.set(call.id, call);
if (!roomMember) {
// we'll be missing the member here if we received a call and it's members
// as pre-gap state and the members weren't active in the timeline we got.
roomMember = RoomMember.fromUserId(roomId, userId, "join");
}
this.handleCallMemberEvent(entry.event, roomMember, roomId, log);
}));
const roomIds = Array.from(new Set(callEntries.map(e => e.roomId)));
await Promise.all(roomIds.map(async roomId => {
// TODO: don't load all members until we need them
const callsMemberEvents = await txn.roomState.getAllForType(roomId, EventType.GroupCallMember);
await Promise.all(callsMemberEvents.map(async entry => {
const userId = entry.event.sender;
const roomMemberState = await txn.roomState.get(roomId, MEMBER_EVENT_TYPE, userId);
let roomMember;
if (roomMemberState) {
roomMember = RoomMember.fromMemberEvent(roomMemberState.event);
}
if (!roomMember) {
// we'll be missing the member here if we received a call and it's members
// as pre-gap state and the members weren't active in the timeline we got.
roomMember = RoomMember.fromUserId(roomId, userId, "join");
}
this.handleCallMemberEvent(entry.event, roomMember, roomId, log);
}));
}));
log.set("newSize", this._calls.size);
});
}));
log.set("newSize", this._calls.size);
}
async createCall(roomId: string, type: "m.video" | "m.voice", name: string, intent: CallIntent = CallIntent.Ring): Promise<GroupCall> {
const call = new GroupCall(makeId("conf-"), true, {
"m.name": name,
"m.intent": intent
}, roomId, this.groupCallOptions);
this._calls.set(call.id, call);
createCall(roomId: string, type: "m.video" | "m.voice", name: string, intent?: CallIntent, log?: ILogItem): Promise<GroupCall> {
return this.options.logger.wrapOrRun(log, "CallHandler.createCall", async log => {
if (!intent) {
intent = CallIntent.Ring;
}
const call = new GroupCall(makeId("conf-"), true, {
"m.name": name,
"m.intent": intent
}, roomId, this.groupCallOptions);
this._calls.set(call.id, call);
try {
await call.create(type);
// store call info so it will ring again when reopening the app
const txn = await this.options.storage.readWriteTxn([this.options.storage.storeNames.calls]);
txn.calls.add({
intent: call.intent,
callId: call.id,
timestamp: this.options.clock.now(),
roomId: roomId
});
await txn.complete();
} catch (err) {
//if (err.name === "ConnectionError") {
// if we're offline, give up and remove the call again
this._calls.remove(call.id);
//}
throw err;
}
return call;
try {
await call.create(type, log);
// store call info so it will ring again when reopening the app
const txn = await this.options.storage.readWriteTxn([this.options.storage.storeNames.calls]);
txn.calls.add({
intent: call.intent,
callId: call.id,
timestamp: this.options.clock.now(),
roomId: roomId
});
await txn.complete();
} catch (err) {
//if (err.name === "ConnectionError") {
// if we're offline, give up and remove the call again
this._calls.remove(call.id);
//}
throw err;
}
return call;
});
}
get calls(): BaseObservableMap<string, GroupCall> { return this._calls; }

View File

@ -162,45 +162,48 @@ export class GroupCall extends EventEmitter<{change: never}> {
return this.errorBoundary.error;
}
async join(localMedia: LocalMedia): Promise<void> {
if (this._state !== GroupCallState.Created || this.joinedData) {
return;
}
const logItem = this.options.logger.child({
l: "answer call",
t: CALL_LOG_TYPE,
id: this.id,
ownSessionId: this.options.sessionId
});
const turnServer = await this.options.turnServerSource.getSettings(logItem);
const membersLogItem = logItem.child("member connections");
const localMuteSettings = new MuteSettings();
localMuteSettings.updateTrackInfo(localMedia.userMedia);
const localPreviewMedia = localMedia.asPreview();
const joinedData = new JoinedData(
logItem,
membersLogItem,
localMedia,
localPreviewMedia,
localMuteSettings,
turnServer
);
this.joinedData = joinedData;
await joinedData.logItem.wrap("join", async log => {
this._state = GroupCallState.Joining;
this.emitChange();
await log.wrap("update member state", async log => {
const memberContent = await this._createMemberPayload(true);
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();
});
// send invite to all members that are < my userId
for (const [,member] of this._members) {
this.connectToMember(member, joinedData, log);
join(localMedia: LocalMedia, log?: ILogItem): Promise<void> {
return this.options.logger.wrapOrRun(log, "Call.join", async joinLog => {
if (this._state !== GroupCallState.Created || this.joinedData) {
return;
}
const logItem = this.options.logger.child({
l: "Call.connection",
t: CALL_LOG_TYPE,
id: this.id,
ownSessionId: this.options.sessionId
});
const turnServer = await this.options.turnServerSource.getSettings(logItem);
const membersLogItem = logItem.child("member connections");
const localMuteSettings = new MuteSettings();
localMuteSettings.updateTrackInfo(localMedia.userMedia);
const localPreviewMedia = localMedia.asPreview();
const joinedData = new JoinedData(
logItem,
membersLogItem,
localMedia,
localPreviewMedia,
localMuteSettings,
turnServer
);
this.joinedData = joinedData;
await joinedData.logItem.wrap("join", async log => {
joinLog.refDetached(log);
this._state = GroupCallState.Joining;
this.emitChange();
await log.wrap("update member state", async log => {
const memberContent = await this._createMemberPayload(true);
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();
});
// send invite to all members that are < my userId
for (const [,member] of this._members) {
this.connectToMember(member, joinedData, log);
}
});
});
}
@ -263,12 +266,12 @@ export class GroupCall extends EventEmitter<{change: never}> {
return this._state === GroupCallState.Joining || this._state === GroupCallState.Joined;
}
async leave(): Promise<void> {
const {joinedData} = this;
if (!joinedData) {
return;
}
await joinedData.logItem.wrap("leave", async log => {
async leave(log?: ILogItem): Promise<void> {
await this.options.logger.wrapOrRun(log, "Call.leave", async log => {
const {joinedData} = this;
if (!joinedData) {
return;
}
try {
joinedData.renewMembershipTimeout?.dispose();
joinedData.renewMembershipTimeout = undefined;
@ -310,8 +313,8 @@ export class GroupCall extends EventEmitter<{change: never}> {
}
/** @internal */
create(type: "m.video" | "m.voice", log?: ILogItem): Promise<void> {
return this.options.logger.wrapOrRun(log, {l: "create call", t: CALL_LOG_TYPE}, async log => {
create(type: "m.video" | "m.voice", log: ILogItem): Promise<void> {
return log.wrap({l: "create call", t: CALL_LOG_TYPE}, async log => {
if (this._state !== GroupCallState.Fledgling) {
return;
}