Move restore/register/login into ConferenceCallManager

This commit is contained in:
Robert Long 2021-07-27 11:40:19 -07:00
parent f456265f0c
commit 02d511c0b2
3 changed files with 324 additions and 287 deletions

View file

@ -25,14 +25,17 @@ import {
Link, Link,
Redirect, Redirect,
} from "react-router-dom"; } from "react-router-dom";
import { ConferenceCall } from "./ConferenceCall"; import {
useConferenceCallManager,
useVideoRoom,
} from "./ConferenceCallManagerHooks";
export default function App() { export default function App() {
const { protocol, host } = window.location; const { protocol, host } = window.location;
// Assume homeserver is hosted on same domain (proxied in development by vite) // Assume homeserver is hosted on same domain (proxied in development by vite)
const homeserverUrl = `${protocol}//${host}`; const homeserverUrl = `${protocol}//${host}`;
const { loading, authenticated, error, client, login, register } = const { loading, authenticated, error, manager, login, register } =
useClient(homeserverUrl); useConferenceCallManager(homeserverUrl);
return ( return (
<Router> <Router>
@ -44,7 +47,7 @@ export default function App() {
<Switch> <Switch>
<Route exact path="/"> <Route exact path="/">
{authenticated ? ( {authenticated ? (
<JoinOrCreateRoom client={client} /> <JoinOrCreateRoom manager={manager} />
) : ( ) : (
<> <>
<div className={styles.page}> <div className={styles.page}>
@ -56,7 +59,11 @@ export default function App() {
)} )}
</Route> </Route>
<Route path="/room/:roomId"> <Route path="/room/:roomId">
{!authenticated ? <Redirect to="/" /> : <Room client={client} />} {!authenticated ? (
<Redirect to="/" />
) : (
<Room manager={manager} />
)}
</Route> </Route>
</Switch> </Switch>
)} )}
@ -65,166 +72,6 @@ export default function App() {
); );
} }
function waitForSync(client) {
return new Promise((resolve, reject) => {
const onSync = (state) => {
if (state === "PREPARED") {
resolve();
client.removeListener("sync", onSync);
}
};
client.on("sync", onSync);
});
}
function useClient(homeserverUrl) {
const [{ loading, authenticated, client, error }, setState] = useState({
loading: true,
authenticated: false,
client: undefined,
error: undefined,
});
useEffect(() => {
async function restoreClient() {
try {
const authStore = localStorage.getItem("matrix-auth-store");
if (authStore) {
const { user_id, device_id, access_token } = JSON.parse(authStore);
const client = matrixcs.createClient({
baseUrl: homeserverUrl,
accessToken: access_token,
userId: user_id,
deviceId: device_id,
});
await client.startClient();
await waitForSync(client);
setState({
client,
loading: false,
authenticated: true,
error: undefined,
});
} else {
setState({
client: undefined,
loading: false,
authenticated: false,
error: undefined,
});
}
} catch (err) {
console.error(err);
localStorage.removeItem("matrix-auth-store");
setState({
client: undefined,
loading: false,
authenticated: false,
error: err,
});
}
}
restoreClient();
}, []);
const login = useCallback(async (username, password) => {
try {
setState((prevState) => ({
...prevState,
authenticated: false,
error: undefined,
}));
const registrationClient = matrixcs.createClient(homeserverUrl);
const { user_id, device_id, access_token } =
await registrationClient.loginWithPassword(username, password);
const client = matrixcs.createClient({
baseUrl: homeserverUrl,
accessToken: access_token,
userId: user_id,
deviceId: device_id,
});
await client.startClient();
localStorage.setItem(
"matrix-auth-store",
JSON.stringify({ user_id, device_id, access_token })
);
setState({
client,
loading: false,
authenticated: true,
error: undefined,
});
} catch (err) {
console.error(err);
localStorage.removeItem("matrix-auth-store");
setState({
client: undefined,
loading: false,
authenticated: false,
error: err,
});
}
}, []);
const register = useCallback(async (username, password) => {
try {
setState((prevState) => ({
...prevState,
authenticated: false,
error: undefined,
}));
const registrationClient = matrixcs.createClient(homeserverUrl);
const { user_id, device_id, access_token } =
await registrationClient.register(username, password, null, {
type: "m.login.dummy",
});
const client = matrixcs.createClient({
baseUrl: homeserverUrl,
accessToken: access_token,
userId: user_id,
deviceId: device_id,
});
await client.startClient();
localStorage.setItem(
"matrix-auth-store",
JSON.stringify({ user_id, device_id, access_token })
);
setState({
client,
loading: false,
authenticated: true,
error: undefined,
});
} catch (err) {
localStorage.removeItem("matrix-auth-store");
setState({
client: undefined,
loading: false,
authenticated: false,
error: err,
});
}
}, []);
return { loading, authenticated, client, error, login, register };
}
function Register({ onRegister }) { function Register({ onRegister }) {
const usernameRef = useRef(); const usernameRef = useRef();
const passwordRef = useRef(); const passwordRef = useRef();
@ -267,7 +114,7 @@ function Login({ onLogin }) {
); );
} }
function JoinOrCreateRoom({ client }) { function JoinOrCreateRoom({ manager }) {
const history = useHistory(); const history = useHistory();
const roomNameRef = useRef(); const roomNameRef = useRef();
const roomIdRef = useRef(); const roomIdRef = useRef();
@ -277,15 +124,15 @@ function JoinOrCreateRoom({ client }) {
useEffect(() => { useEffect(() => {
function updateRooms() { function updateRooms() {
setRooms(client.getRooms()); setRooms(manager.client.getRooms());
} }
updateRooms(); updateRooms();
client.on("Room", updateRooms); manager.client.on("Room", updateRooms);
return () => { return () => {
client.removeListener("Room", updateRooms); manager.client.removeListener("Room", updateRooms);
}; };
}, []); }, []);
@ -294,7 +141,7 @@ function JoinOrCreateRoom({ client }) {
e.preventDefault(); e.preventDefault();
setCreateRoomError(undefined); setCreateRoomError(undefined);
client manager.client
.createRoom({ .createRoom({
visibility: "private", visibility: "private",
preset: "public_chat", preset: "public_chat",
@ -305,7 +152,7 @@ function JoinOrCreateRoom({ client }) {
}) })
.catch(setCreateRoomError); .catch(setCreateRoomError);
}, },
[client] [manager]
); );
const onJoinRoom = useCallback( const onJoinRoom = useCallback(
@ -313,14 +160,14 @@ function JoinOrCreateRoom({ client }) {
e.preventDefault(); e.preventDefault();
setJoinRoomError(undefined); setJoinRoomError(undefined);
client manager.client
.joinRoom(roomIdRef.current.value) .joinRoom(roomIdRef.current.value)
.then(({ roomId }) => { .then(({ roomId }) => {
history.push(`/room/${roomId}`); history.push(`/room/${roomId}`);
}) })
.catch(setJoinRoomError); .catch(setJoinRoomError);
}, },
[client] [manager]
); );
return ( return (
@ -366,117 +213,10 @@ function JoinOrCreateRoom({ client }) {
); );
} }
function useVideoRoom(client, roomId, timeout = 5000) { function Room({ manager }) {
const [
{ loading, joined, room, conferenceCall, participants, error },
setState,
] = useState({
loading: true,
joined: false,
room: undefined,
participants: [],
error: undefined,
conferenceCall: null,
});
useEffect(() => {
const conferenceCall = new ConferenceCall(client, roomId);
setState((prevState) => ({
...prevState,
conferenceCall,
loading: true,
room: undefined,
error: undefined,
}));
client.joinRoom(roomId).catch((err) => {
setState((prevState) => ({ ...prevState, loading: false, error: err }));
});
let initialRoom = client.getRoom(roomId);
if (initialRoom) {
setState((prevState) => ({
...prevState,
loading: false,
room: initialRoom,
error: undefined,
}));
return;
}
let timeoutId;
function roomCallback(room) {
if (room && room.roomId === roomId) {
clearTimeout(timeoutId);
client.removeListener("Room", roomCallback);
setState((prevState) => ({
...prevState,
loading: false,
room,
error: undefined,
}));
}
}
client.on("Room", roomCallback);
timeoutId = setTimeout(() => {
setState((prevState) => ({
...prevState,
loading: false,
room: undefined,
error: new Error("Room could not be found."),
}));
client.removeListener("Room", roomCallback);
}, timeout);
return () => {
client.removeListener("Room", roomCallback);
clearTimeout(timeoutId);
};
}, [roomId]);
const joinCall = useCallback(() => {
const onParticipantsChanged = () => {
setState((prevState) => ({
...prevState,
participants: conferenceCall.participants,
}));
};
conferenceCall.on("participants_changed", onParticipantsChanged);
conferenceCall.join();
setState((prevState) => ({
...prevState,
joined: true,
}));
return () => {
conferenceCall.removeListener(
"participants_changed",
onParticipantsChanged
);
setState((prevState) => ({
...prevState,
joined: false,
participants: [],
}));
};
}, [client, conferenceCall, roomId]);
return { loading, joined, room, participants, error, joinCall };
}
function Room({ client }) {
const { roomId } = useParams(); const { roomId } = useParams();
const { loading, joined, room, participants, error, joinCall } = useVideoRoom( const { loading, joined, room, participants, error, joinCall } = useVideoRoom(
client, manager,
roomId roomId
); );
@ -486,7 +226,7 @@ function Room({ client }) {
<div className={styles.header}> <div className={styles.header}>
<h3>{room.name}</h3> <h3>{room.name}</h3>
<div className={styles.userNav}> <div className={styles.userNav}>
<h5>{client.getUserId()}</h5> <h5>{manager.client.getUserId()}</h5>
</div> </div>
</div> </div>
)} )}

View file

@ -4,11 +4,118 @@ const CONF_ROOM = "me.robertlong.conf";
const CONF_PARTICIPANT = "me.robertlong.conf.participant"; const CONF_PARTICIPANT = "me.robertlong.conf.participant";
const PARTICIPANT_TIMEOUT = 1000 * 5; const PARTICIPANT_TIMEOUT = 1000 * 5;
export class ConferenceCall extends EventEmitter { function waitForSync(client) {
constructor(client, roomId) { return new Promise((resolve, reject) => {
const onSync = (state) => {
if (state === "PREPARED") {
resolve();
client.removeListener("sync", onSync);
}
};
client.on("sync", onSync);
});
}
export class ConferenceCallManager extends EventEmitter {
static async restore(homeserverUrl) {
try {
const authStore = localStorage.getItem("matrix-auth-store");
if (authStore) {
const { user_id, device_id, access_token } = JSON.parse(authStore);
const client = matrixcs.createClient({
baseUrl: homeserverUrl,
accessToken: access_token,
userId: user_id,
deviceId: device_id,
});
const manager = new ConferenceCallManager(client);
await client.startClient();
await waitForSync(client);
return manager;
}
} catch (err) {
localStorage.removeItem("matrix-auth-store");
throw err;
}
}
static async login(homeserverUrl, username, password) {
try {
const registrationClient = matrixcs.createClient(homeserverUrl);
const { user_id, device_id, access_token } =
await registrationClient.loginWithPassword(username, password);
const client = matrixcs.createClient({
baseUrl: homeserverUrl,
accessToken: access_token,
userId: user_id,
deviceId: device_id,
});
localStorage.setItem(
"matrix-auth-store",
JSON.stringify({ user_id, device_id, access_token })
);
const manager = new ConferenceCallManager(client);
await client.startClient();
await waitForSync(client);
return manager;
} catch (err) {
localStorage.removeItem("matrix-auth-store");
throw err;
}
}
static async register(homeserverUrl, username, password) {
try {
const registrationClient = matrixcs.createClient(homeserverUrl);
const { user_id, device_id, access_token } =
await registrationClient.register(username, password, null, {
type: "m.login.dummy",
});
const client = matrixcs.createClient({
baseUrl: homeserverUrl,
accessToken: access_token,
userId: user_id,
deviceId: device_id,
});
localStorage.setItem(
"matrix-auth-store",
JSON.stringify({ user_id, device_id, access_token })
);
const manager = new ConferenceCallManager(client);
await client.startClient();
await waitForSync(client);
return manager;
} catch (err) {
localStorage.removeItem("matrix-auth-store");
throw err;
}
}
constructor(client) {
super(); super();
this.client = client; this.client = client;
this.roomId = roomId;
this.joined = false; this.joined = false;
this.room = null; this.room = null;
this.localParticipant = { this.localParticipant = {
@ -25,7 +132,7 @@ export class ConferenceCall extends EventEmitter {
this.client.on("Call.incoming", this._onIncomingCall); this.client.on("Call.incoming", this._onIncomingCall);
} }
join() { join(roomId) {
console.debug( console.debug(
"join", "join",
`Local user ${this.client.getUserId()} joining room ${this.roomId}` `Local user ${this.client.getUserId()} joining room ${this.roomId}`
@ -33,6 +140,7 @@ export class ConferenceCall extends EventEmitter {
this.joined = true; this.joined = true;
this.roomId = roomId;
this.room = this.client.getRoom(this.roomId); this.room = this.client.getRoom(this.roomId);
const activeConf = this.room.currentState const activeConf = this.room.currentState

View file

@ -0,0 +1,189 @@
import { useCallback, useEffect, useState } from "react";
import { ConferenceCallManager } from "./ConferenceCallManager";
export function useConferenceCallManager(homeserverUrl) {
const [{ loading, authenticated, manager, error }, setState] = useState({
loading: true,
authenticated: false,
manager: undefined,
error: undefined,
});
useEffect(() => {
ConferenceCallManager.restore(homeserverUrl)
.then((manager) => {
console.log(manager);
setState({
manager,
loading: false,
authenticated: !!manager,
error: undefined,
});
})
.catch((err) => {
console.error(err);
setState({
manager: undefined,
loading: false,
authenticated: false,
error: err,
});
});
}, []);
const login = useCallback(async (username, password) => {
setState((prevState) => ({
...prevState,
authenticated: false,
error: undefined,
}));
ConferenceCallManager.login(homeserverUrl, username, password)
.then((manager) => {
setState({
manager,
loading: false,
authenticated: true,
error: undefined,
});
})
.catch((err) => {
console.error(err);
setState({
manager: undefined,
loading: false,
authenticated: false,
error: err,
});
});
}, []);
const register = useCallback(async (username, password) => {
setState((prevState) => ({
...prevState,
authenticated: false,
error: undefined,
}));
ConferenceCallManager.register(homeserverUrl, username, password)
.then((manager) => {
setState({
manager,
loading: false,
authenticated: true,
error: undefined,
});
})
.catch((err) => {
console.error(err);
setState({
manager: undefined,
loading: false,
authenticated: false,
error: err,
});
});
}, []);
return { loading, authenticated, manager, error, login, register };
}
export function useVideoRoom(manager, roomId, timeout = 5000) {
const [{ loading, joined, room, participants, error }, setState] = useState({
loading: true,
joined: false,
room: undefined,
participants: [],
error: undefined,
});
useEffect(() => {
setState((prevState) => ({
...prevState,
loading: true,
room: undefined,
error: undefined,
}));
manager.client.joinRoom(roomId).catch((err) => {
setState((prevState) => ({ ...prevState, loading: false, error: err }));
});
let initialRoom = manager.client.getRoom(roomId);
if (initialRoom) {
setState((prevState) => ({
...prevState,
loading: false,
room: initialRoom,
error: undefined,
}));
return;
}
let timeoutId;
function roomCallback(room) {
if (room && room.roomId === roomId) {
clearTimeout(timeoutId);
manager.client.removeListener("Room", roomCallback);
setState((prevState) => ({
...prevState,
loading: false,
room,
error: undefined,
}));
}
}
manager.client.on("Room", roomCallback);
timeoutId = setTimeout(() => {
setState((prevState) => ({
...prevState,
loading: false,
room: undefined,
error: new Error("Room could not be found."),
}));
manager.client.removeListener("Room", roomCallback);
}, timeout);
return () => {
manager.client.removeListener("Room", roomCallback);
clearTimeout(timeoutId);
};
}, [roomId]);
const joinCall = useCallback(() => {
const onParticipantsChanged = () => {
setState((prevState) => ({
...prevState,
participants: manager.participants,
}));
};
manager.on("participants_changed", onParticipantsChanged);
manager.join(roomId);
setState((prevState) => ({
...prevState,
joined: true,
}));
return () => {
manager.removeListener("participants_changed", onParticipantsChanged);
setState((prevState) => ({
...prevState,
joined: false,
participants: [],
}));
};
}, [manager, roomId]);
return { loading, joined, room, participants, error, joinCall };
}