Passwordless user flow
This commit is contained in:
parent
20350e66a2
commit
fc3960ce63
12 changed files with 589 additions and 369 deletions
84
src/App.jsx
84
src/App.jsx
|
@ -15,86 +15,42 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
|
||||||
BrowserRouter as Router,
|
|
||||||
Switch,
|
|
||||||
Route,
|
|
||||||
Redirect,
|
|
||||||
useLocation,
|
|
||||||
} from "react-router-dom";
|
|
||||||
import * as Sentry from "@sentry/react";
|
import * as Sentry from "@sentry/react";
|
||||||
import { useClient } from "./ConferenceCallManagerHooks";
|
|
||||||
import { Home } from "./Home";
|
|
||||||
import { Room } from "./Room";
|
|
||||||
import { RegisterPage } from "./RegisterPage";
|
|
||||||
import { LoginPage } from "./LoginPage";
|
|
||||||
import { Center } from "./Layout";
|
|
||||||
import { GuestAuthPage } from "./GuestAuthPage";
|
|
||||||
import { OverlayProvider } from "@react-aria/overlays";
|
import { OverlayProvider } from "@react-aria/overlays";
|
||||||
|
import { Home } from "./Home";
|
||||||
|
import { LoginPage } from "./LoginPage";
|
||||||
|
import { RegisterPage } from "./RegisterPage";
|
||||||
|
import { Room } from "./Room";
|
||||||
|
import { ClientProvider } from "./ConferenceCallManagerHooks";
|
||||||
|
|
||||||
const SentryRoute = Sentry.withSentryRouting(Route);
|
const SentryRoute = Sentry.withSentryRouting(Route);
|
||||||
|
|
||||||
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,
|
|
||||||
client,
|
|
||||||
login,
|
|
||||||
logout,
|
|
||||||
registerGuest,
|
|
||||||
register,
|
|
||||||
} = useClient(homeserverUrl);
|
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<OverlayProvider>
|
<ClientProvider homeserverUrl={homeserverUrl}>
|
||||||
{loading ? (
|
<OverlayProvider>
|
||||||
<Center>
|
|
||||||
<p>Loading...</p>
|
|
||||||
</Center>
|
|
||||||
) : (
|
|
||||||
<Switch>
|
<Switch>
|
||||||
<AuthenticatedRoute authenticated={authenticated} exact path="/">
|
<SentryRoute exact path="/">
|
||||||
<Home client={client} onLogout={logout} />
|
<Home />
|
||||||
</AuthenticatedRoute>
|
</SentryRoute>
|
||||||
<SentryRoute exact path="/login">
|
<SentryRoute exact path="/login">
|
||||||
<LoginPage onLogin={login} />
|
<LoginPage />
|
||||||
</SentryRoute>
|
</SentryRoute>
|
||||||
<SentryRoute exact path="/register">
|
<SentryRoute exact path="/register">
|
||||||
<RegisterPage onRegister={register} />
|
<RegisterPage />
|
||||||
</SentryRoute>
|
</SentryRoute>
|
||||||
<SentryRoute path="/room/:roomId?">
|
<SentryRoute path="/room/:roomId?">
|
||||||
{authenticated ? (
|
<Room />
|
||||||
<Room client={client} onLogout={logout} />
|
|
||||||
) : (
|
|
||||||
<GuestAuthPage onLoginAsGuest={registerGuest} />
|
|
||||||
)}
|
|
||||||
</SentryRoute>
|
</SentryRoute>
|
||||||
</Switch>
|
</Switch>
|
||||||
)}
|
</OverlayProvider>
|
||||||
</OverlayProvider>
|
</ClientProvider>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AuthenticatedRoute({ authenticated, children, ...rest }) {
|
|
||||||
const location = useLocation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SentryRoute {...rest}>
|
|
||||||
{authenticated ? (
|
|
||||||
children
|
|
||||||
) : (
|
|
||||||
<Redirect
|
|
||||||
to={{
|
|
||||||
pathname: "/login",
|
|
||||||
state: { from: location },
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</SentryRoute>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,9 +4,28 @@ import { CopyButton } from "./button";
|
||||||
import { Facepile } from "./Facepile";
|
import { Facepile } from "./Facepile";
|
||||||
import { Avatar } from "./Avatar";
|
import { Avatar } from "./Avatar";
|
||||||
import { ReactComponent as VideoIcon } from "./icons/Video.svg";
|
import { ReactComponent as VideoIcon } from "./icons/Video.svg";
|
||||||
import styles from "./CallTile.module.css";
|
import styles from "./CallList.module.css";
|
||||||
|
|
||||||
export function CallTile({ name, avatarUrl, roomUrl, participants }) {
|
export function CallList({ title, rooms }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h3>{title}</h3>
|
||||||
|
<div className={styles.callList}>
|
||||||
|
{rooms.map(({ roomId, roomName, roomUrl, avatarUrl, participants }) => (
|
||||||
|
<CallTile
|
||||||
|
key={roomId}
|
||||||
|
name={roomName}
|
||||||
|
avatarUrl={avatarUrl}
|
||||||
|
roomUrl={roomUrl}
|
||||||
|
participants={participants}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CallTile({ name, avatarUrl, roomUrl, participants }) {
|
||||||
return (
|
return (
|
||||||
<Link to={roomUrl} className={styles.callTile}>
|
<Link to={roomUrl} className={styles.callTile}>
|
||||||
<Avatar
|
<Avatar
|
|
@ -52,3 +52,11 @@
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.callList {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 24px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
|
@ -14,8 +14,23 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
createContext,
|
||||||
|
useMemo,
|
||||||
|
useContext,
|
||||||
|
} from "react";
|
||||||
import matrix from "matrix-js-sdk/src/browser-index";
|
import matrix from "matrix-js-sdk/src/browser-index";
|
||||||
|
import {
|
||||||
|
GroupCallIntent,
|
||||||
|
GroupCallType,
|
||||||
|
} from "matrix-js-sdk/src/browser-index";
|
||||||
|
import { useHistory } from "react-router-dom";
|
||||||
|
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||||
|
|
||||||
|
const ClientContext = createContext();
|
||||||
|
|
||||||
function waitForSync(client) {
|
function waitForSync(client) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
@ -86,12 +101,16 @@ export async function fetchGroupCall(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useClient(homeserverUrl) {
|
export function ClientProvider({ homeserverUrl, children }) {
|
||||||
const [{ loading, authenticated, client }, setState] = useState({
|
const history = useHistory();
|
||||||
loading: true,
|
const [{ loading, isAuthenticated, isGuest, client, userName }, setState] =
|
||||||
authenticated: false,
|
useState({
|
||||||
client: undefined,
|
loading: true,
|
||||||
});
|
isAuthenticated: false,
|
||||||
|
isGuest: false,
|
||||||
|
client: undefined,
|
||||||
|
userName: null,
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function restore() {
|
async function restore() {
|
||||||
|
@ -117,8 +136,10 @@ export function useClient(homeserverUrl) {
|
||||||
JSON.stringify({ user_id, device_id, access_token, guest })
|
JSON.stringify({ user_id, device_id, access_token, guest })
|
||||||
);
|
);
|
||||||
|
|
||||||
return client;
|
return { client, guest };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { client: undefined, guest: false };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
localStorage.removeItem("matrix-auth-store");
|
localStorage.removeItem("matrix-auth-store");
|
||||||
throw err;
|
throw err;
|
||||||
|
@ -126,15 +147,23 @@ export function useClient(homeserverUrl) {
|
||||||
}
|
}
|
||||||
|
|
||||||
restore()
|
restore()
|
||||||
.then((client) => {
|
.then(({ client, guest }) => {
|
||||||
if (client) {
|
setState({
|
||||||
setState({ client, loading: false, authenticated: true });
|
client,
|
||||||
} else {
|
loading: false,
|
||||||
setState({ client: undefined, loading: false, authenticated: false });
|
isAuthenticated: !!client,
|
||||||
}
|
isGuest: guest,
|
||||||
|
userName: client?.getUserIdLocalpart(),
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setState({ client: undefined, loading: false, authenticated: false });
|
setState({
|
||||||
|
client: undefined,
|
||||||
|
loading: false,
|
||||||
|
isAuthenticated: false,
|
||||||
|
isGuest: false,
|
||||||
|
userName: null,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -176,10 +205,22 @@ export function useClient(homeserverUrl) {
|
||||||
JSON.stringify({ user_id, device_id, access_token })
|
JSON.stringify({ user_id, device_id, access_token })
|
||||||
);
|
);
|
||||||
|
|
||||||
setState({ client, loading: false, authenticated: true });
|
setState({
|
||||||
|
client,
|
||||||
|
loading: false,
|
||||||
|
isAuthenticated: true,
|
||||||
|
isGuest: false,
|
||||||
|
userName: client.getUserIdLocalpart(),
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
localStorage.removeItem("matrix-auth-store");
|
localStorage.removeItem("matrix-auth-store");
|
||||||
setState({ client: undefined, loading: false, authenticated: false });
|
setState({
|
||||||
|
client: undefined,
|
||||||
|
loading: false,
|
||||||
|
isAuthenticated: false,
|
||||||
|
isGuest: false,
|
||||||
|
userName: null,
|
||||||
|
});
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -210,10 +251,22 @@ export function useClient(homeserverUrl) {
|
||||||
JSON.stringify({ user_id, device_id, access_token, guest: true })
|
JSON.stringify({ user_id, device_id, access_token, guest: true })
|
||||||
);
|
);
|
||||||
|
|
||||||
setState({ client, loading: false, authenticated: true });
|
setState({
|
||||||
|
client,
|
||||||
|
loading: false,
|
||||||
|
isAuthenticated: true,
|
||||||
|
isGuest: true,
|
||||||
|
userName: client.getUserIdLocalpart(),
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
localStorage.removeItem("matrix-auth-store");
|
localStorage.removeItem("matrix-auth-store");
|
||||||
setState({ client: undefined, loading: false, authenticated: false });
|
setState({
|
||||||
|
client: undefined,
|
||||||
|
loading: false,
|
||||||
|
isAuthenticated: false,
|
||||||
|
isGuest: false,
|
||||||
|
userName: null,
|
||||||
|
});
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -239,10 +292,25 @@ export function useClient(homeserverUrl) {
|
||||||
JSON.stringify({ user_id, device_id, access_token })
|
JSON.stringify({ user_id, device_id, access_token })
|
||||||
);
|
);
|
||||||
|
|
||||||
setState({ client, loading: false, authenticated: true });
|
setState({
|
||||||
|
client,
|
||||||
|
loading: false,
|
||||||
|
isGuest: false,
|
||||||
|
isAuthenticated: true,
|
||||||
|
isGuest: false,
|
||||||
|
userName: client.getUserIdLocalpart(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return client;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
localStorage.removeItem("matrix-auth-store");
|
localStorage.removeItem("matrix-auth-store");
|
||||||
setState({ client: undefined, loading: false, authenticated: false });
|
setState({
|
||||||
|
client: undefined,
|
||||||
|
loading: false,
|
||||||
|
isGuest: false,
|
||||||
|
isAuthenticated: false,
|
||||||
|
userName: null,
|
||||||
|
});
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -252,17 +320,164 @@ export function useClient(homeserverUrl) {
|
||||||
window.location = "/";
|
window.location = "/";
|
||||||
}, [history]);
|
}, [history]);
|
||||||
|
|
||||||
|
const context = useMemo(
|
||||||
|
() => ({
|
||||||
|
loading,
|
||||||
|
isAuthenticated,
|
||||||
|
isGuest,
|
||||||
|
client,
|
||||||
|
login,
|
||||||
|
registerGuest,
|
||||||
|
register,
|
||||||
|
logout,
|
||||||
|
userName,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
loading,
|
||||||
|
isAuthenticated,
|
||||||
|
isGuest,
|
||||||
|
client,
|
||||||
|
login,
|
||||||
|
registerGuest,
|
||||||
|
register,
|
||||||
|
logout,
|
||||||
|
userName,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ClientContext.Provider value={context}>{children}</ClientContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useClient() {
|
||||||
|
return useContext(ClientContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
function roomAliasFromRoomName(roomName) {
|
||||||
|
return roomName
|
||||||
|
.trim()
|
||||||
|
.replace(/\s/g, "-")
|
||||||
|
.replace(/[^\w-]/g, "")
|
||||||
|
.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createRoom(client, name) {
|
||||||
|
const { room_id, room_alias } = await client.createRoom({
|
||||||
|
visibility: "private",
|
||||||
|
preset: "public_chat",
|
||||||
|
name,
|
||||||
|
room_alias_name: roomAliasFromRoomName(name),
|
||||||
|
power_level_content_override: {
|
||||||
|
invite: 100,
|
||||||
|
kick: 100,
|
||||||
|
ban: 100,
|
||||||
|
redact: 50,
|
||||||
|
state_default: 0,
|
||||||
|
events_default: 0,
|
||||||
|
users_default: 0,
|
||||||
|
events: {
|
||||||
|
"m.room.power_levels": 100,
|
||||||
|
"m.room.history_visibility": 100,
|
||||||
|
"m.room.tombstone": 100,
|
||||||
|
"m.room.encryption": 100,
|
||||||
|
"m.room.name": 50,
|
||||||
|
"m.room.message": 0,
|
||||||
|
"m.room.encrypted": 50,
|
||||||
|
"m.sticker": 50,
|
||||||
|
"org.matrix.msc3401.call.member": 0,
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
[client.getUserId()]: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.setGuestAccess(room_id, {
|
||||||
|
allowJoin: true,
|
||||||
|
allowRead: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.createGroupCall(
|
||||||
|
room_id,
|
||||||
|
GroupCallType.Video,
|
||||||
|
GroupCallIntent.Prompt
|
||||||
|
);
|
||||||
|
|
||||||
|
return room_alias || room_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCreateRoom(client) {
|
||||||
|
const [creatingRoom, setCreatingRoom] = useState(false);
|
||||||
|
const [createRoomError, setCreateRoomError] = useState();
|
||||||
|
|
||||||
|
const onCreateRoom = useCallback(
|
||||||
|
(roomName) => {
|
||||||
|
setCreateRoomError(undefined);
|
||||||
|
setCreatingRoom(true);
|
||||||
|
|
||||||
|
return createRoom(client, roomName).catch((error) => {
|
||||||
|
setCreateRoomError(error);
|
||||||
|
setCreatingRoom(false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[client]
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loading,
|
creatingRoom,
|
||||||
authenticated,
|
createRoomError,
|
||||||
client,
|
createRoom: onCreateRoom,
|
||||||
login,
|
|
||||||
registerGuest,
|
|
||||||
register,
|
|
||||||
logout,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useCreateRoomAsPasswordlessUser() {
|
||||||
|
const { register } = useClient();
|
||||||
|
const [creatingRoom, setCreatingRoom] = useState(false);
|
||||||
|
const [createRoomError, setCreateRoomError] = useState();
|
||||||
|
|
||||||
|
const onCreateRoom = useCallback(
|
||||||
|
(roomName, userName) => {
|
||||||
|
async function onCreateRoom(roomName, userName) {
|
||||||
|
const client = await register(userName, randomString(16));
|
||||||
|
return await createRoom(client, roomName);
|
||||||
|
}
|
||||||
|
|
||||||
|
setCreateRoomError(undefined);
|
||||||
|
setCreatingRoom(true);
|
||||||
|
|
||||||
|
return onCreateRoom(roomName, userName).catch((error) => {
|
||||||
|
setCreateRoomError(error);
|
||||||
|
setCreatingRoom(false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[register]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
creatingRoom,
|
||||||
|
createRoomError,
|
||||||
|
createRoom: onCreateRoom,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useLoadGroupCall(client, roomId, viaServers) {
|
||||||
|
const [state, setState] = useState({
|
||||||
|
loading: true,
|
||||||
|
error: undefined,
|
||||||
|
groupCall: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setState({ loading: true });
|
||||||
|
fetchGroupCall(client, roomId, viaServers, 30000)
|
||||||
|
.then((groupCall) => setState({ loading: false, groupCall }))
|
||||||
|
.catch((error) => setState({ loading: false, error }));
|
||||||
|
}, [client, roomId]);
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
const tsCache = {};
|
const tsCache = {};
|
||||||
|
|
||||||
function getLastTs(client, r) {
|
function getLastTs(client, r) {
|
||||||
|
@ -324,6 +539,10 @@ export function useGroupCallRooms(client) {
|
||||||
const groupCall = client.getGroupCallForRoom(room.roomId);
|
const groupCall = client.getGroupCallForRoom(room.roomId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
roomId: room.roomId,
|
||||||
|
roomName: room.name,
|
||||||
|
avatarUrl: null,
|
||||||
|
roomUrl: `/room/${room.getCanonicalAlias() || room.roomId}`,
|
||||||
room,
|
room,
|
||||||
groupCall,
|
groupCall,
|
||||||
participants: [...groupCall.participants],
|
participants: [...groupCall.participants],
|
||||||
|
@ -352,9 +571,16 @@ export function usePublicRooms(client, publicSpaceRoomId, maxRooms = 50) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (publicSpaceRoomId) {
|
if (publicSpaceRoomId) {
|
||||||
client.getRoomHierarchy(publicSpaceRoomId, maxRooms).then(({ rooms }) => {
|
client.getRoomHierarchy(publicSpaceRoomId, maxRooms).then(({ rooms }) => {
|
||||||
const filteredRooms = rooms.filter(
|
const filteredRooms = rooms
|
||||||
(room) => room.room_type !== "m.space"
|
.filter((room) => room.room_type !== "m.space")
|
||||||
);
|
.map((room) => ({
|
||||||
|
roomId: room.room_id,
|
||||||
|
roomName: room.name,
|
||||||
|
avatarUrl: null,
|
||||||
|
room,
|
||||||
|
roomUrl: `/room/${room.room_id}`,
|
||||||
|
participants: [],
|
||||||
|
}));
|
||||||
|
|
||||||
setRooms(filteredRooms);
|
setRooms(filteredRooms);
|
||||||
});
|
});
|
|
@ -1,49 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 New Vector Ltd
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React, { useState, useEffect } from "react";
|
|
||||||
import styles from "./GuestAuthPage.module.css";
|
|
||||||
import { useLocation, useHistory } from "react-router-dom";
|
|
||||||
import { Header, LeftNav, HeaderLogo } from "./Header";
|
|
||||||
import { Center, Content, Modal } from "./Layout";
|
|
||||||
import { ErrorModal } from "./ErrorModal";
|
|
||||||
|
|
||||||
export function GuestAuthPage({ onLoginAsGuest }) {
|
|
||||||
const history = useHistory();
|
|
||||||
const location = useLocation();
|
|
||||||
const [error, setError] = useState();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
onLoginAsGuest("Guest " + Math.round(Math.random() * 999)).catch(setError);
|
|
||||||
}, [onLoginAsGuest, location, history]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.guestAuthPage}>
|
|
||||||
<Header>
|
|
||||||
<LeftNav>
|
|
||||||
<HeaderLogo />
|
|
||||||
</LeftNav>
|
|
||||||
</Header>
|
|
||||||
<Content>
|
|
||||||
<Center>
|
|
||||||
<Modal>
|
|
||||||
{error ? <ErrorModal error={error} /> : <div>Loading...</div>}
|
|
||||||
</Modal>
|
|
||||||
</Center>
|
|
||||||
</Content>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
.guestAuthPage {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
310
src/Home.jsx
310
src/Home.jsx
|
@ -14,110 +14,57 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useState } from "react";
|
import React, { useCallback } from "react";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
|
useClient,
|
||||||
useGroupCallRooms,
|
useGroupCallRooms,
|
||||||
usePublicRooms,
|
usePublicRooms,
|
||||||
|
useCreateRoom,
|
||||||
|
useCreateRoomAsPasswordlessUser,
|
||||||
} from "./ConferenceCallManagerHooks";
|
} from "./ConferenceCallManagerHooks";
|
||||||
import { Header, HeaderLogo, LeftNav, RightNav } from "./Header";
|
import { Header, HeaderLogo, LeftNav, RightNav } from "./Header";
|
||||||
import styles from "./Home.module.css";
|
import styles from "./Home.module.css";
|
||||||
import { FieldRow, InputField, ErrorMessage } from "./Input";
|
import { FieldRow, InputField, ErrorMessage } from "./Input";
|
||||||
import {
|
|
||||||
GroupCallIntent,
|
|
||||||
GroupCallType,
|
|
||||||
} from "matrix-js-sdk/src/browser-index";
|
|
||||||
import { UserMenu } from "./UserMenu";
|
import { UserMenu } from "./UserMenu";
|
||||||
import { Button } from "./button";
|
import { Button } from "./button";
|
||||||
import { CallTile } from "./CallTile";
|
import { CallList } from "./CallList";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { ErrorModal } from "./ErrorModal";
|
||||||
|
|
||||||
function roomAliasFromRoomName(roomName) {
|
export function Home() {
|
||||||
return roomName
|
const { isAuthenticated, isGuest, loading, error, client } = useClient();
|
||||||
.trim()
|
|
||||||
.replace(/\s/g, "-")
|
if (loading) {
|
||||||
.replace(/[^\w-]/g, "")
|
return <div>Loading...</div>;
|
||||||
.toLowerCase();
|
} else if (error) {
|
||||||
|
return <ErrorModal error={error} />;
|
||||||
|
} else if (!isAuthenticated || isGuest) {
|
||||||
|
return <UnregisteredView />;
|
||||||
|
} else {
|
||||||
|
return <RegisteredView client={client} />;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Home({ client, onLogout }) {
|
function UnregisteredView() {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [roomName, setRoomName] = useState("");
|
const { createRoomError, creatingRoom, createRoom } =
|
||||||
const [guestAccess, setGuestAccess] = useState(false);
|
useCreateRoomAsPasswordlessUser();
|
||||||
const [createRoomError, setCreateRoomError] = useState();
|
|
||||||
const rooms = useGroupCallRooms(client);
|
|
||||||
const publicRooms = usePublicRooms(
|
|
||||||
client,
|
|
||||||
import.meta.env.VITE_PUBLIC_SPACE_ROOM_ID
|
|
||||||
);
|
|
||||||
|
|
||||||
const onCreateRoom = useCallback(
|
const onCreateRoom = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setCreateRoomError(undefined);
|
|
||||||
|
|
||||||
async function createRoom(name, roomAlias, guestAccess) {
|
|
||||||
const { room_id, room_alias } = await client.createRoom({
|
|
||||||
visibility: "private",
|
|
||||||
preset: "public_chat",
|
|
||||||
name,
|
|
||||||
room_alias_name: roomAlias,
|
|
||||||
power_level_content_override: {
|
|
||||||
invite: 100,
|
|
||||||
kick: 100,
|
|
||||||
ban: 100,
|
|
||||||
redact: 50,
|
|
||||||
state_default: 0,
|
|
||||||
events_default: 0,
|
|
||||||
users_default: 0,
|
|
||||||
events: {
|
|
||||||
"m.room.power_levels": 100,
|
|
||||||
"m.room.history_visibility": 100,
|
|
||||||
"m.room.tombstone": 100,
|
|
||||||
"m.room.encryption": 100,
|
|
||||||
"m.room.name": 50,
|
|
||||||
"m.room.message": 0,
|
|
||||||
"m.room.encrypted": 50,
|
|
||||||
"m.sticker": 50,
|
|
||||||
"org.matrix.msc3401.call.member": 0,
|
|
||||||
},
|
|
||||||
users: {
|
|
||||||
[client.getUserId()]: 100,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (guestAccess) {
|
|
||||||
await client.setGuestAccess(room_id, {
|
|
||||||
allowJoin: true,
|
|
||||||
allowRead: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await client.createGroupCall(
|
|
||||||
room_id,
|
|
||||||
GroupCallType.Video,
|
|
||||||
GroupCallIntent.Prompt
|
|
||||||
);
|
|
||||||
|
|
||||||
history.push(`/room/${room_alias || room_id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = new FormData(e.target);
|
const data = new FormData(e.target);
|
||||||
const roomName = data.get("roomName");
|
const roomName = data.get("roomName");
|
||||||
const guestAccess = data.get("guestAccess");
|
const userName = data.get("userName");
|
||||||
|
|
||||||
createRoom(roomName, roomAliasFromRoomName(roomName), guestAccess).catch(
|
createRoom(roomName, userName).then((roomIdOrAlias) => {
|
||||||
(error) => {
|
history.push(`/room/${roomIdOrAlias}`);
|
||||||
setCreateRoomError(error);
|
});
|
||||||
setShowAdvanced(true);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
[client]
|
[history]
|
||||||
);
|
);
|
||||||
|
|
||||||
const [roomId, setRoomId] = useState("");
|
|
||||||
|
|
||||||
const onJoinRoom = useCallback(
|
const onJoinRoom = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -129,12 +76,134 @@ export function Home({ client, onLogout }) {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={styles.home}>
|
<div className={styles.home}>
|
||||||
<div className={styles.left}>
|
<div className={classNames(styles.left, styles.fullWidth)}>
|
||||||
<Header>
|
<Header>
|
||||||
<LeftNav>
|
<LeftNav>
|
||||||
<HeaderLogo />
|
<HeaderLogo />
|
||||||
</LeftNav>
|
</LeftNav>
|
||||||
|
<RightNav>
|
||||||
|
<UserMenu />
|
||||||
|
</RightNav>
|
||||||
|
</Header>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<div className={styles.centered}>
|
||||||
|
<form onSubmit={onJoinRoom}>
|
||||||
|
<h1>Join a call</h1>
|
||||||
|
<FieldRow className={styles.fieldRow}>
|
||||||
|
<InputField
|
||||||
|
id="roomId"
|
||||||
|
name="roomId"
|
||||||
|
label="Call ID"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
autoComplete="off"
|
||||||
|
placeholder="Call ID"
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
|
<FieldRow className={styles.fieldRow}>
|
||||||
|
<Button className={styles.button} type="submit">
|
||||||
|
Join call
|
||||||
|
</Button>
|
||||||
|
</FieldRow>
|
||||||
|
</form>
|
||||||
|
<hr />
|
||||||
|
<form onSubmit={onCreateRoom}>
|
||||||
|
<h1>Create a call</h1>
|
||||||
|
<FieldRow className={styles.fieldRow}>
|
||||||
|
<InputField
|
||||||
|
id="userName"
|
||||||
|
name="userName"
|
||||||
|
label="Username"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
autoComplete="off"
|
||||||
|
placeholder="Username"
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
|
<FieldRow className={styles.fieldRow}>
|
||||||
|
<InputField
|
||||||
|
id="roomName"
|
||||||
|
name="roomName"
|
||||||
|
label="Room Name"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
autoComplete="off"
|
||||||
|
placeholder="Room Name"
|
||||||
|
/>
|
||||||
|
</FieldRow>
|
||||||
|
{createRoomError && (
|
||||||
|
<FieldRow className={styles.fieldRow}>
|
||||||
|
<ErrorMessage>{createRoomError.message}</ErrorMessage>
|
||||||
|
</FieldRow>
|
||||||
|
)}
|
||||||
|
<FieldRow className={styles.fieldRow}>
|
||||||
|
<Button
|
||||||
|
className={styles.button}
|
||||||
|
type="submit"
|
||||||
|
disabled={creatingRoom}
|
||||||
|
>
|
||||||
|
{creatingRoom ? "Creating call..." : "Create call"}
|
||||||
|
</Button>
|
||||||
|
</FieldRow>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function RegisteredView({ client }) {
|
||||||
|
const history = useHistory();
|
||||||
|
const { createRoomError, creatingRoom, createRoom } = useCreateRoom(client);
|
||||||
|
|
||||||
|
const onCreateRoom = useCallback(
|
||||||
|
(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const data = new FormData(e.target);
|
||||||
|
const roomName = data.get("roomName");
|
||||||
|
|
||||||
|
createRoom(roomName).then((roomIdOrAlias) => {
|
||||||
|
history.push(`/room/${roomIdOrAlias}`);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[history]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onJoinRoom = useCallback(
|
||||||
|
(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const data = new FormData(e.target);
|
||||||
|
const roomId = data.get("roomId");
|
||||||
|
history.push(`/room/${roomId}`);
|
||||||
|
},
|
||||||
|
[history]
|
||||||
|
);
|
||||||
|
const publicRooms = usePublicRooms(
|
||||||
|
client,
|
||||||
|
import.meta.env.VITE_PUBLIC_SPACE_ROOM_ID
|
||||||
|
);
|
||||||
|
const recentRooms = useGroupCallRooms(client);
|
||||||
|
|
||||||
|
const hideCallList = publicRooms.length === 0 && recentRooms.length === 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.home}>
|
||||||
|
<div
|
||||||
|
className={classNames(styles.left, {
|
||||||
|
[styles.fullWidth]: hideCallList,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Header>
|
||||||
|
<LeftNav>
|
||||||
|
<HeaderLogo />
|
||||||
|
</LeftNav>
|
||||||
|
{hideCallList && (
|
||||||
|
<RightNav>
|
||||||
|
<UserMenu />
|
||||||
|
</RightNav>
|
||||||
|
)}
|
||||||
</Header>
|
</Header>
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
<div className={styles.centered}>
|
<div className={styles.centered}>
|
||||||
|
@ -149,8 +218,6 @@ export function Home({ client, onLogout }) {
|
||||||
required
|
required
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
placeholder="Call ID"
|
placeholder="Call ID"
|
||||||
value={roomId}
|
|
||||||
onChange={(e) => setRoomId(e.target.value)}
|
|
||||||
/>
|
/>
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
<FieldRow className={styles.fieldRow}>
|
<FieldRow className={styles.fieldRow}>
|
||||||
|
@ -171,18 +238,6 @@ export function Home({ client, onLogout }) {
|
||||||
required
|
required
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
placeholder="Room Name"
|
placeholder="Room Name"
|
||||||
value={roomName}
|
|
||||||
onChange={(e) => setRoomName(e.target.value)}
|
|
||||||
/>
|
|
||||||
</FieldRow>
|
|
||||||
<FieldRow>
|
|
||||||
<InputField
|
|
||||||
id="guestAccess"
|
|
||||||
name="guestAccess"
|
|
||||||
label="Allow Guest Access"
|
|
||||||
type="checkbox"
|
|
||||||
checked={guestAccess}
|
|
||||||
onChange={(e) => setGuestAccess(e.target.checked)}
|
|
||||||
/>
|
/>
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
{createRoomError && (
|
{createRoomError && (
|
||||||
|
@ -191,55 +246,36 @@ export function Home({ client, onLogout }) {
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
)}
|
)}
|
||||||
<FieldRow className={styles.fieldRow}>
|
<FieldRow className={styles.fieldRow}>
|
||||||
<Button className={styles.button} type="submit">
|
<Button
|
||||||
Create call
|
className={styles.button}
|
||||||
|
type="submit"
|
||||||
|
disabled={creatingRoom}
|
||||||
|
>
|
||||||
|
{creatingRoom ? "Creating call..." : "Create call"}
|
||||||
</Button>
|
</Button>
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.right}>
|
{!hideCallList && (
|
||||||
<Header>
|
<div className={styles.right}>
|
||||||
<LeftNav />
|
<Header>
|
||||||
<RightNav>
|
<LeftNav />
|
||||||
<UserMenu
|
<RightNav>
|
||||||
signedIn
|
<UserMenu />
|
||||||
userName={client.getUserIdLocalpart()}
|
</RightNav>
|
||||||
onLogout={onLogout}
|
</Header>
|
||||||
/>
|
<div className={styles.content}>
|
||||||
</RightNav>
|
{publicRooms.length > 0 && (
|
||||||
</Header>
|
<CallList title="Public Calls" rooms={publicRooms} />
|
||||||
<div className={styles.content}>
|
)}
|
||||||
{publicRooms.length > 0 && (
|
{recentRooms.length > 0 && (
|
||||||
<>
|
<CallList title="Recent Calls" rooms={recentRooms} />
|
||||||
<h3>Public Calls</h3>
|
)}
|
||||||
<div className={styles.roomList}>
|
|
||||||
{publicRooms.map((room) => (
|
|
||||||
<CallTile
|
|
||||||
key={room.room_id}
|
|
||||||
name={room.name}
|
|
||||||
avatarUrl={null}
|
|
||||||
roomUrl={`/room/${room.room_id}`}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<h3>Recent Calls</h3>
|
|
||||||
<div className={styles.roomList}>
|
|
||||||
{rooms.map(({ room, participants }) => (
|
|
||||||
<CallTile
|
|
||||||
key={room.roomId}
|
|
||||||
name={room.name}
|
|
||||||
avatarUrl={null}
|
|
||||||
roomUrl={`/room/${room.getCanonicalAlias() || room.roomId}`}
|
|
||||||
participants={participants}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,11 @@
|
||||||
background-color: var(--bgColor2);
|
background-color: var(--bgColor2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fullWidth {
|
||||||
|
background-color: var(--bgColor1);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.centered {
|
.centered {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -65,6 +70,10 @@
|
||||||
top: -12px;
|
top: -12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fullWidth .content hr:after {
|
||||||
|
background-color: var(--bgColor1);
|
||||||
|
}
|
||||||
|
|
||||||
.left .content form {
|
.left .content form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -99,11 +108,3 @@
|
||||||
.right .content h3:first-child {
|
.right .content h3:first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.roomList {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: flex-start;
|
|
||||||
gap: 24px;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
|
@ -20,8 +20,10 @@ import { Header, HeaderLogo, LeftNav } from "./Header";
|
||||||
import { FieldRow, InputField, ErrorMessage } from "./Input";
|
import { FieldRow, InputField, ErrorMessage } from "./Input";
|
||||||
import { Center, Content, Info, Modal } from "./Layout";
|
import { Center, Content, Info, Modal } from "./Layout";
|
||||||
import { Button } from "./button";
|
import { Button } from "./button";
|
||||||
|
import { useClient } from "./ConferenceCallManagerHooks";
|
||||||
|
|
||||||
export function LoginPage({ onLogin }) {
|
export function LoginPage() {
|
||||||
|
const { login } = useClient();
|
||||||
const [homeserver, setHomeServer] = useState(
|
const [homeserver, setHomeServer] = useState(
|
||||||
`${window.location.protocol}//${window.location.host}`
|
`${window.location.protocol}//${window.location.host}`
|
||||||
);
|
);
|
||||||
|
@ -32,17 +34,19 @@ export function LoginPage({ onLogin }) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState();
|
const [error, setError] = useState();
|
||||||
|
|
||||||
|
// TODO: Handle hitting login page with authenticated client
|
||||||
|
|
||||||
const onSubmitLoginForm = useCallback(
|
const onSubmitLoginForm = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
onLogin(homeserver, usernameRef.current.value, passwordRef.current.value)
|
login(homeserver, usernameRef.current.value, passwordRef.current.value)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (location.state && location.state.from) {
|
if (location.state && location.state.from) {
|
||||||
history.replace(location.state.from);
|
history.push(location.state.from);
|
||||||
} else {
|
} else {
|
||||||
history.replace("/");
|
history.push("/");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
@ -50,7 +54,7 @@ export function LoginPage({ onLogin }) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[onLogin, location, history, homeserver]
|
[login, location, history, homeserver]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -20,8 +20,11 @@ import { Header, LeftNav, HeaderLogo } from "./Header";
|
||||||
import { FieldRow, InputField, ErrorMessage } from "./Input";
|
import { FieldRow, InputField, ErrorMessage } from "./Input";
|
||||||
import { Center, Content, Info, Modal } from "./Layout";
|
import { Center, Content, Info, Modal } from "./Layout";
|
||||||
import { Button } from "./button";
|
import { Button } from "./button";
|
||||||
|
import { useClient } from "./ConferenceCallManagerHooks";
|
||||||
|
|
||||||
export function RegisterPage({ onRegister }) {
|
export function RegisterPage() {
|
||||||
|
// TODO: Handle hitting login page with authenticated client
|
||||||
|
const { register } = useClient();
|
||||||
const registerUsernameRef = useRef();
|
const registerUsernameRef = useRef();
|
||||||
const registerPasswordRef = useRef();
|
const registerPasswordRef = useRef();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
@ -33,15 +36,15 @@ export function RegisterPage({ onRegister }) {
|
||||||
(e) => {
|
(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
onRegister(
|
register(
|
||||||
registerUsernameRef.current.value,
|
registerUsernameRef.current.value,
|
||||||
registerPasswordRef.current.value
|
registerPasswordRef.current.value
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (location.state && location.state.from) {
|
if (location.state && location.state.from) {
|
||||||
history.replace(location.state.from);
|
history.push(location.state.from);
|
||||||
} else {
|
} else {
|
||||||
history.replace("/");
|
history.push("/");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
@ -49,7 +52,7 @@ export function RegisterPage({ onRegister }) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[onRegister, location, history]
|
[register, location, history]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
62
src/Room.jsx
62
src/Room.jsx
|
@ -42,7 +42,7 @@ import "matrix-react-sdk/res/css/views/voip/GroupCallView/_VideoGrid.scss";
|
||||||
import { useGroupCall } from "matrix-react-sdk/src/hooks/useGroupCall";
|
import { useGroupCall } from "matrix-react-sdk/src/hooks/useGroupCall";
|
||||||
import { useCallFeed } from "matrix-react-sdk/src/hooks/useCallFeed";
|
import { useCallFeed } from "matrix-react-sdk/src/hooks/useCallFeed";
|
||||||
import { useMediaStream } from "matrix-react-sdk/src/hooks/useMediaStream";
|
import { useMediaStream } from "matrix-react-sdk/src/hooks/useMediaStream";
|
||||||
import { fetchGroupCall } from "./ConferenceCallManagerHooks";
|
import { useClient, useLoadGroupCall } from "./ConferenceCallManagerHooks";
|
||||||
import { ErrorModal } from "./ErrorModal";
|
import { ErrorModal } from "./ErrorModal";
|
||||||
import { GroupCallInspector } from "./GroupCallInspector";
|
import { GroupCallInspector } from "./GroupCallInspector";
|
||||||
import * as Sentry from "@sentry/react";
|
import * as Sentry from "@sentry/react";
|
||||||
|
@ -56,24 +56,39 @@ const canScreenshare = "getDisplayMedia" in navigator.mediaDevices;
|
||||||
// For now we can disable screensharing in Safari.
|
// For now we can disable screensharing in Safari.
|
||||||
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||||
|
|
||||||
function useLoadGroupCall(client, roomId, viaServers) {
|
export function Room() {
|
||||||
const [state, setState] = useState({
|
const [registeringGuest, setRegisteringGuest] = useState(false);
|
||||||
loading: true,
|
const [registrationError, setRegistrationError] = useState();
|
||||||
error: undefined,
|
const { loading, isAuthenticated, error, client, registerGuest } =
|
||||||
groupCall: undefined,
|
useClient();
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setState({ loading: true });
|
if (!loading && !isAuthenticated) {
|
||||||
fetchGroupCall(client, roomId, viaServers, 30000)
|
setRegisteringGuest(true);
|
||||||
.then((groupCall) => setState({ loading: false, groupCall }))
|
|
||||||
.catch((error) => setState({ loading: false, error }));
|
|
||||||
}, [roomId]);
|
|
||||||
|
|
||||||
return state;
|
registerGuest()
|
||||||
|
.then(() => {
|
||||||
|
setRegisteringGuest(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setRegistrationError(error);
|
||||||
|
setRegisteringGuest(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, isAuthenticated]);
|
||||||
|
|
||||||
|
if (loading || registeringGuest) {
|
||||||
|
return <div>Loading...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (registrationError || error) {
|
||||||
|
return <ErrorModal error={registrationError || error} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <GroupCall client={client} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Room({ client, onLogout }) {
|
export function GroupCall({ client }) {
|
||||||
const { roomId: maybeRoomId } = useParams();
|
const { roomId: maybeRoomId } = useParams();
|
||||||
const { hash, search } = useLocation();
|
const { hash, search } = useLocation();
|
||||||
const [simpleGrid, viaServers] = useMemo(() => {
|
const [simpleGrid, viaServers] = useMemo(() => {
|
||||||
|
@ -115,7 +130,6 @@ export function Room({ client, onLogout }) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.room}>
|
<div className={styles.room}>
|
||||||
<GroupCallView
|
<GroupCallView
|
||||||
onLogout={onLogout}
|
|
||||||
client={client}
|
client={client}
|
||||||
groupCall={groupCall}
|
groupCall={groupCall}
|
||||||
simpleGrid={simpleGrid}
|
simpleGrid={simpleGrid}
|
||||||
|
@ -124,7 +138,7 @@ export function Room({ client, onLogout }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GroupCallView({ client, groupCall, simpleGrid, onLogout }) {
|
export function GroupCallView({ client, groupCall, simpleGrid }) {
|
||||||
const [showInspector, setShowInspector] = useState(false);
|
const [showInspector, setShowInspector] = useState(false);
|
||||||
const {
|
const {
|
||||||
state,
|
state,
|
||||||
|
@ -184,7 +198,6 @@ export function GroupCallView({ client, groupCall, simpleGrid, onLogout }) {
|
||||||
} else if (state === GroupCallState.Entered) {
|
} else if (state === GroupCallState.Entered) {
|
||||||
return (
|
return (
|
||||||
<InRoomView
|
<InRoomView
|
||||||
onLogout={onLogout}
|
|
||||||
groupCall={groupCall}
|
groupCall={groupCall}
|
||||||
client={client}
|
client={client}
|
||||||
roomName={groupCall.room.name}
|
roomName={groupCall.room.name}
|
||||||
|
@ -209,7 +222,6 @@ export function GroupCallView({ client, groupCall, simpleGrid, onLogout }) {
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<RoomSetupView
|
<RoomSetupView
|
||||||
onLogout={onLogout}
|
|
||||||
client={client}
|
client={client}
|
||||||
hasLocalParticipant={hasLocalParticipant}
|
hasLocalParticipant={hasLocalParticipant}
|
||||||
roomName={groupCall.room.name}
|
roomName={groupCall.room.name}
|
||||||
|
@ -249,7 +261,6 @@ export function EnteringRoomView() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function RoomSetupView({
|
function RoomSetupView({
|
||||||
onLogout,
|
|
||||||
client,
|
client,
|
||||||
roomName,
|
roomName,
|
||||||
state,
|
state,
|
||||||
|
@ -282,11 +293,7 @@ function RoomSetupView({
|
||||||
/>
|
/>
|
||||||
</LeftNav>
|
</LeftNav>
|
||||||
<RightNav>
|
<RightNav>
|
||||||
<UserMenu
|
<UserMenu />
|
||||||
signedIn
|
|
||||||
userName={client.getUserIdLocalpart()}
|
|
||||||
onLogout={onLogout}
|
|
||||||
/>
|
|
||||||
</RightNav>
|
</RightNav>
|
||||||
</Header>
|
</Header>
|
||||||
<div className={styles.joinRoom}>
|
<div className={styles.joinRoom}>
|
||||||
|
@ -347,7 +354,6 @@ function RoomSetupView({
|
||||||
}
|
}
|
||||||
|
|
||||||
function InRoomView({
|
function InRoomView({
|
||||||
onLogout,
|
|
||||||
client,
|
client,
|
||||||
groupCall,
|
groupCall,
|
||||||
roomName,
|
roomName,
|
||||||
|
@ -424,11 +430,7 @@ function InRoomView({
|
||||||
</LeftNav>
|
</LeftNav>
|
||||||
<RightNav>
|
<RightNav>
|
||||||
<GridLayoutMenu layout={layout} setLayout={setLayout} />
|
<GridLayoutMenu layout={layout} setLayout={setLayout} />
|
||||||
<UserMenu
|
<UserMenu />
|
||||||
signedIn
|
|
||||||
userName={client.getUserIdLocalpart()}
|
|
||||||
onLogout={onLogout}
|
|
||||||
/>
|
|
||||||
</RightNav>
|
</RightNav>
|
||||||
</Header>
|
</Header>
|
||||||
{items.length === 0 ? (
|
{items.length === 0 ? (
|
||||||
|
|
|
@ -7,45 +7,67 @@ import { ReactComponent as LogoutIcon } from "./icons/Logout.svg";
|
||||||
import styles from "./UserMenu.module.css";
|
import styles from "./UserMenu.module.css";
|
||||||
import { Item } from "@react-stately/collections";
|
import { Item } from "@react-stately/collections";
|
||||||
import { Menu } from "./Menu";
|
import { Menu } from "./Menu";
|
||||||
|
import { useHistory, useLocation } from "react-router-dom";
|
||||||
|
import { useClient } from "./ConferenceCallManagerHooks";
|
||||||
|
|
||||||
export function UserMenu({ userName, signedIn, onLogin, onLogout }) {
|
export function UserMenu() {
|
||||||
const onAction = useCallback((value) => {
|
const location = useLocation();
|
||||||
switch (value) {
|
const history = useHistory();
|
||||||
case "user":
|
const { isAuthenticated, isGuest, logout, userName } = useClient();
|
||||||
break;
|
|
||||||
case "logout":
|
const onAction = useCallback(
|
||||||
onLogout();
|
(value) => {
|
||||||
break;
|
switch (value) {
|
||||||
case "login":
|
case "user":
|
||||||
onLogin();
|
break;
|
||||||
break;
|
case "logout":
|
||||||
}
|
logout();
|
||||||
});
|
break;
|
||||||
|
case "login":
|
||||||
|
history.push("/login", { state: { from: location } });
|
||||||
|
break;
|
||||||
|
case "register":
|
||||||
|
history.push("/register", { state: { from: location } });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[history, location, logout]
|
||||||
|
);
|
||||||
|
|
||||||
const items = useMemo(() => {
|
const items = useMemo(() => {
|
||||||
if (signedIn) {
|
const arr = [];
|
||||||
return [
|
|
||||||
{
|
if (isAuthenticated) {
|
||||||
key: "user",
|
arr.push({
|
||||||
icon: UserIcon,
|
key: "user",
|
||||||
label: userName,
|
icon: UserIcon,
|
||||||
},
|
label: userName,
|
||||||
{
|
});
|
||||||
key: "logout",
|
}
|
||||||
label: "Sign Out",
|
|
||||||
icon: LogoutIcon,
|
if (!isAuthenticated || isGuest) {
|
||||||
},
|
arr.push(
|
||||||
];
|
|
||||||
} else {
|
|
||||||
return [
|
|
||||||
{
|
{
|
||||||
key: "login",
|
key: "login",
|
||||||
label: "Sign In",
|
label: "Sign In",
|
||||||
icon: LoginIcon,
|
icon: LoginIcon,
|
||||||
},
|
},
|
||||||
];
|
{
|
||||||
|
key: "register",
|
||||||
|
label: "Register",
|
||||||
|
icon: LoginIcon,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
arr.push({
|
||||||
|
key: "logout",
|
||||||
|
label: "Sign Out",
|
||||||
|
icon: LogoutIcon,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [signedIn, userName]);
|
|
||||||
|
return arr;
|
||||||
|
}, [isAuthenticated, isGuest, userName]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PopoverMenuTrigger placement="bottom right">
|
<PopoverMenuTrigger placement="bottom right">
|
||||||
|
|
Loading…
Add table
Reference in a new issue