From 661e3dd98eb36f9536913fb57c5b0b2c91a2fb6b Mon Sep 17 00:00:00 2001 From: Robert Long Date: Fri, 30 Jul 2021 16:55:25 -0700 Subject: [PATCH] Group events by calls or users --- package-lock.json | 11 ++ package.json | 1 + src/ConferenceCallManager.js | 168 +++++++++++++----------------- src/ConferenceCallManagerHooks.js | 4 +- src/DevTools.jsx | 163 +++++++++++++++++------------ src/DevTools.module.css | 26 ++++- 6 files changed, 211 insertions(+), 162 deletions(-) diff --git a/package-lock.json b/package-lock.json index eaf8dbdd..2d8f52d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "version": "0.0.0", "dependencies": { + "classnames": "^2.3.1", "color-hash": "^2.0.1", "events": "^3.3.0", "matrix-js-sdk": "^12.0.1", @@ -588,6 +589,11 @@ "node": ">=4" } }, + "node_modules/classnames": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", + "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -2039,6 +2045,11 @@ "supports-color": "^5.3.0" } }, + "classnames": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", + "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", diff --git a/package.json b/package.json index b084fbcb..5e4612b2 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "serve": "vite preview" }, "dependencies": { + "classnames": "^2.3.1", "color-hash": "^2.0.1", "events": "^3.3.0", "matrix-js-sdk": "^12.0.1", diff --git a/src/ConferenceCallManager.js b/src/ConferenceCallManager.js index 8a5c18cc..09090a03 100644 --- a/src/ConferenceCallManager.js +++ b/src/ConferenceCallManager.js @@ -134,9 +134,10 @@ export class ConferenceCallManager extends EventEmitter { this.client = client; this.joined = false; this.room = null; + const localUserId = client.getUserId(); this.localParticipant = { local: true, - userId: client.getUserId(), + userId: localUserId, feed: null, call: null, muted: true, @@ -144,20 +145,24 @@ export class ConferenceCallManager extends EventEmitter { this.participants = [this.localParticipant]; this.pendingCalls = []; this.callUserMap = new Map(); - this.debugState = new Map(); - this._setDebugState(client.getUserId(), "you"); + this.debugState = { + users: new Map(), + calls: new Map(), + }; this.client.on("event", this._onEvent); this.client.on("RoomState.members", this._onMemberChanged); this.client.on("Call.incoming", this._onIncomingCall); } - join(roomId) { - this.joined = true; - - this._addDebugEvent(this.client.getUserId(), "joined call"); - + setRoom(roomId) { this.roomId = roomId; this.room = this.client.getRoom(this.roomId); + } + + join() { + this.joined = true; + + this._setDebugState(this.client.getUserId(), null, "you"); const activeConf = this.room.currentState .getStateEvents(CONF_ROOM, "") @@ -169,18 +174,12 @@ export class ConferenceCallManager extends EventEmitter { const roomMemberIds = this.room.getMembers().map(({ userId }) => userId); - for (const userId of this.debugState.keys()) { - if (roomMemberIds.indexOf(userId) === -1) { - this.debugState.delete(userId); - } - } - roomMemberIds.forEach((userId) => { this._processMember(userId); }); for (const { call, onHangup, onReplaced } of this.pendingCalls) { - if (call.roomId !== roomId) { + if (call.roomId !== this.roomId) { continue; } @@ -200,31 +199,66 @@ export class ConferenceCallManager extends EventEmitter { const roomId = event.getRoomId(); const type = event.getType(); - if (type.startsWith("m.call.") || type.startsWith("me.robertlong.conf")) { - const content = event.getContent(); - const details = { content: event.toJSON(), roomId }; + if (roomId === this.roomId && type.startsWith("m.call.")) { + const sender = event.getSender(); + const { call_id } = event.getContent(); - if (content.invitee && content.call_id) { - this.callUserMap.set(content.call_id, content.invitee); - details.to = content.invitee; - } else if (content.call_id) { - details.to = this.callUserMap.get(content.call_id); + if (call_id) { + if (this.debugState.calls.has(call_id)) { + const callState = this.debugState.calls.get(call_id); + callState.events.push(event); + } else { + this.debugState.calls.set(call_id, { + state: "unknown", + events: [event], + }); + } } - switch (type) { - case "m.call.invite": - case "m.call.candidates": - case "m.call.answer": - case "m.call.hangup": - case "m.call.select_answer": - details.callId = content.call_id; - break; + if (this.debugState.users.has(sender)) { + const userState = this.debugState.users.get(sender); + userState.events.push(event); + } else { + this.debugState.users.set(sender, { + state: "unknown", + events: [event], + }); } - this._addDebugEvent(event.getSender(), type, details); + this.emit("debug"); } }; + _setDebugState(userId, callId, state) { + if (userId) { + const userState = this.debugState.users.get(userId); + + if (userState) { + userState.state = state; + } else { + this.debugState.users.set(userId, { + state, + events: [], + }); + } + } + + if (callId) { + const callState = this.debugState.calls.get(callId); + + if (callState) { + callState.state = state; + } else { + this.debugState.calls.set(callId, { + state, + events: [], + }); + } + } + + this.emit("debug"); + } + _updateParticipantState = () => { const userId = this.client.getUserId(); const currentMemberState = this.room.currentState.getStateEvents( @@ -286,20 +320,19 @@ export class ConferenceCallManager extends EventEmitter { new Date().getTime() - participantTimeout > PARTICIPANT_TIMEOUT ) { // Member is inactive so don't call them. - this._setDebugState(userId, "inactive"); + this._setDebugState(userId, null, "inactive"); return; } // Only initiate a call with a user who has a userId that is lexicographically // less than your own. Otherwise, that user will call you. if (userId < localUserId) { - this._setDebugState(userId, "waiting for invite"); + this._setDebugState(userId, null, "waiting for invite"); return; } const call = this.client.createCall(this.roomId, userId); this._addCall(call, userId); - this._setDebugState(userId, "calling"); call.placeVideoCall(); } @@ -342,7 +375,6 @@ export class ConferenceCallManager extends EventEmitter { const userId = call.opponentMember.userId; this._addCall(call, userId); - this._setDebugState(userId, "answered"); call.answer(); }; @@ -365,8 +397,9 @@ export class ConferenceCallManager extends EventEmitter { call, }); - this._setDebugCallId(userId, call.callId); - + call.on("state", (state) => + this._setDebugState(userId, call.callId, state) + ); call.on("feeds_changed", () => this._onCallFeedsChanged(call)); call.on("hangup", () => this._onCallHangup(call)); @@ -388,7 +421,6 @@ export class ConferenceCallManager extends EventEmitter { if (!this.localParticipant.feed && localFeeds.length > 0) { this.localParticipant.call = call; - this._setDebugCallId(this.localParticipant.userId, call.callId); this.localParticipant.feed = localFeeds[0]; participantsChanged = true; } @@ -400,7 +432,6 @@ export class ConferenceCallManager extends EventEmitter { if (remoteFeeds.length > 0 && remoteParticipant.feed !== remoteFeeds[0]) { remoteParticipant.feed = remoteFeeds[0]; - this._setDebugState(call.opponentMember.userId, "streaming"); participantsChanged = true; } @@ -410,16 +441,14 @@ export class ConferenceCallManager extends EventEmitter { }; _onCallHangup = (call) => { - if (call.hangupReason === "replaced") { - return; - } - - this._setDebugState(call.opponentMember.userId, "hungup"); - const participantIndex = this.participants.findIndex( (p) => !p.local && p.call === call ); + if (call.hangupReason === "replaced") { + return; + } + if (participantIndex === -1) { return; } @@ -436,16 +465,13 @@ export class ConferenceCallManager extends EventEmitter { if (localFeeds.length > 0) { this.localParticipant.call = call; - this._setDebugCallId(this.localParticipant.userId, call.callId); this.localParticipant.feed = localFeeds[0]; } else { this.localParticipant.call = null; - this._setDebugCallId(this.localParticipant.userId, null); this.localParticipant.feed = null; } } else { this.localParticipant.call = null; - this._setDebugCallId(this.localParticipant.userId, null); this.localParticipant.feed = null; } } @@ -454,17 +480,11 @@ export class ConferenceCallManager extends EventEmitter { }; _onCallReplaced = (call, newCall) => { - this._addDebugEvent(call.opponentMember.userId, "replaced", { - callId: call.callId, - newCallId: newCall.callId, - }); - const remoteParticipant = this.participants.find( (p) => !p.local && p.call === call ); remoteParticipant.call = newCall; - this._setDebugCallId(remoteParticipant.userId, newCall.callId); newCall.on("feeds_changed", () => this._onCallFeedsChanged(newCall)); newCall.on("hangup", () => this._onCallHangup(newCall)); @@ -507,48 +527,10 @@ export class ConferenceCallManager extends EventEmitter { this.participants = [this.localParticipant]; this.localParticipant.feed = null; this.localParticipant.call = null; - this._setDebugCallId(this.localParticipant.userId, null); this.emit("participants_changed"); } - _addDebugEvent(sender, type, content) { - if (!this.debugState.has(sender)) { - this.debugState.set(sender, { - callId: null, - state: "unknown", - events: [{ type, ...content }], - }); - } else { - const { events } = this.debugState.get(sender); - events.push({ type, roomId: this.roomId, ...content }); - } - - this.emit("debug"); - } - - _setDebugState(userId, state) { - if (!this.debugState.has(userId)) { - this.debugState.set(userId, { state, callId: null, events: [] }); - } else { - const userState = this.debugState.get(userId); - userState.state = state; - } - - this.emit("debug"); - } - - _setDebugCallId(userId, callId) { - if (!this.debugState.has(userId)) { - this.debugState.set(userId, { state: "unknown", callId, events: [] }); - } else { - const userState = this.debugState.get(userId); - userState.callId = callId; - } - - this.emit("debug"); - } - logout() { localStorage.removeItem("matrix-auth-store"); } diff --git a/src/ConferenceCallManagerHooks.js b/src/ConferenceCallManagerHooks.js index d4dd31b9..e7ff576d 100644 --- a/src/ConferenceCallManagerHooks.js +++ b/src/ConferenceCallManagerHooks.js @@ -136,6 +136,8 @@ export function useVideoRoom(manager, roomId, timeout = 5000) { error: undefined, })); + manager.setRoom(roomId); + manager.client.joinRoom(roomId).catch((err) => { setState((prevState) => ({ ...prevState, loading: false, error: err })); }); @@ -196,7 +198,7 @@ export function useVideoRoom(manager, roomId, timeout = 5000) { manager.on("participants_changed", onParticipantsChanged); - manager.join(roomId); + manager.join(); setState((prevState) => ({ ...prevState, diff --git a/src/DevTools.jsx b/src/DevTools.jsx index 51c76897..8c264897 100644 --- a/src/DevTools.jsx +++ b/src/DevTools.jsx @@ -1,5 +1,6 @@ import React, { useCallback, useEffect, useRef, useState } from "react"; import ColorHash from "color-hash"; +import classNames from "classnames"; import styles from "./DevTools.module.css"; const colorHash = new ColorHash({ lightness: 0.8 }); @@ -25,13 +26,27 @@ function CallId({ callId, ...rest }) { ); } +function sortEntries(a, b) { + const aInactive = a[1].state === "inactive"; + const bInactive = b[1].state === "inactive"; + + if (aInactive && !bInactive) { + return 1; + } else if (bInactive && !aInactive) { + return -1; + } else { + return a[0] < b[0] ? -1 : 1; + } +} + export function DevTools({ manager }) { const [debugState, setDebugState] = useState(manager.debugState); const [selectedEvent, setSelectedEvent] = useState(); + const [activeTab, setActiveTab] = useState("users"); useEffect(() => { function onRoomDebug() { - setDebugState(manager.debugState); + setDebugState({ ...manager.debugState }); } manager.on("debug", onRoomDebug); @@ -47,15 +62,50 @@ export function DevTools({ manager }) { return (
- {Array.from(debugState.entries()).map(([userId, props]) => ( - - ))} +
+
setActiveTab("users")} + > + Users +
+
setActiveTab("calls")} + > + Calls +
+
+
+ {activeTab === "users" && + Array.from(debugState.users.entries()) + .sort(sortEntries) + .map(([userId, props]) => ( + } + {...props} + onSelect={setSelectedEvent} + /> + ))} + {activeTab === "calls" && + Array.from(debugState.calls.entries()) + .sort(sortEntries) + .map(([callId, props]) => ( + } + {...props} + onSelect={setSelectedEvent} + /> + ))} +
{selectedEvent && (
- + {title} {`(${state})`} - {callId && }
- {events - .filter((e) => e.roomId === roomId) - .map((event, idx) => ( -
onSelectEvent(event)} - > - {event.type} - {event.callId && ( - - )} - {event.newCallId && ( - <> - {"->"} - - - )} - {event.to && ( - - )} - {event.reason && ( - {event.reason} - )} -
- ))} + {events.map((event, idx) => ( + + ))}
); } +function EventItem({ event, showCallId, showSender, onSelect }) { + const type = event.getType(); + const sender = event.getSender(); + const { call_id, invitee } = event.getContent(); + + return ( +
onSelect(event)}> + {showSender && sender && ( + + )} + {type} + {showCallId && call_id && ( + + )} + {invitee && } +
+ ); +} + function EventViewer({ event, onClose }) { + const type = event.getType(); + const sender = event.getSender(); + const { call_id, invitee } = event.getContent(); + const json = event.toJSON(); + return (
-

Event Type: {event.type}

- {event.callId && ( +

Event Type: {type}

+

Sender: {sender}

+ {call_id && (

- Call Id: + Call Id:

)} - {event.newCallId && ( + {invitee && (

- New Call Id: - + Invitee:

)} - {event.to && ( -

- To: -

- )} - {event.reason && ( -

- Reason: {event.reason} -

- )} - {event.content && ( - <> -

Content:

-
-            {JSON.stringify(event.content, undefined, 2)}
-          
- - )} +

Raw Event:

+
{JSON.stringify(json, undefined, 2)}
); diff --git a/src/DevTools.module.css b/src/DevTools.module.css index 7aa4a672..c96a982f 100644 --- a/src/DevTools.module.css +++ b/src/DevTools.module.css @@ -1,9 +1,31 @@ .devTools { display: flex; - height: 250px; + flex-direction: column; border-top: 2px solid #111; - gap: 2px; background-color: #111; +} + +.toolbar { + display: flex; + background-color: #222; +} + +.tab { + vertical-align: middle; + padding: 4px 8px; + background-color: #444; + margin: 0 2px; + cursor: pointer; +} + +.activeTab { + background-color: #666; +} + +.devToolsContainer { + display: flex; + height: 250px; + gap: 2px; overflow-x: auto; }