Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fc4af2bd7 | ||
|
|
0f3a7f9fd9 | ||
|
|
1cc634509b | ||
|
|
cb07ce32cb | ||
|
|
6866d662f7 | ||
|
|
51a2027d64 | ||
|
|
0f6b8f9bb1 | ||
|
|
63229ce2d7 | ||
|
|
1d620910c5 | ||
|
|
47357b3fc6 | ||
|
|
3ed35f9477 | ||
|
|
a369444b62 | ||
|
|
742d658021 | ||
|
|
681c24a0ca | ||
|
|
fc057bf988 |
@@ -41,7 +41,8 @@
|
||||
"react-router": "6",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-use-clipboard": "^1.0.7",
|
||||
"react-use-measure": "^2.1.1"
|
||||
"react-use-measure": "^2.1.1",
|
||||
"unique-names-generator": "^4.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.16.5",
|
||||
|
||||
@@ -24,6 +24,9 @@ yarn link
|
||||
cd ..
|
||||
|
||||
cd matrix-video-chat
|
||||
|
||||
export VITE_APP_VERSION=$(git describe --tags --abbrev=0)
|
||||
|
||||
yarn link matrix-js-sdk
|
||||
yarn link matrix-react-sdk
|
||||
yarn install
|
||||
|
||||
@@ -23,6 +23,7 @@ import React, {
|
||||
useContext,
|
||||
} from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { ErrorView } from "./FullScreenView";
|
||||
import { initClient, defaultHomeserver } from "./matrix-utils";
|
||||
|
||||
const ClientContext = createContext();
|
||||
@@ -30,7 +31,7 @@ const ClientContext = createContext();
|
||||
export function ClientProvider({ children }) {
|
||||
const history = useHistory();
|
||||
const [
|
||||
{ loading, isAuthenticated, isPasswordlessUser, client, userName },
|
||||
{ loading, isAuthenticated, isPasswordlessUser, client, userName, error },
|
||||
setState,
|
||||
] = useState({
|
||||
loading: true,
|
||||
@@ -38,6 +39,7 @@ export function ClientProvider({ children }) {
|
||||
isPasswordlessUser: false,
|
||||
client: undefined,
|
||||
userName: null,
|
||||
error: undefined,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -143,35 +145,71 @@ export function ClientProvider({ children }) {
|
||||
[client]
|
||||
);
|
||||
|
||||
const setClient = useCallback((client, session) => {
|
||||
if (client) {
|
||||
localStorage.setItem("matrix-auth-store", JSON.stringify(session));
|
||||
const setClient = useCallback(
|
||||
(newClient, session) => {
|
||||
if (client && client !== newClient) {
|
||||
client.stopClient();
|
||||
}
|
||||
|
||||
setState({
|
||||
client,
|
||||
loading: false,
|
||||
isAuthenticated: true,
|
||||
isPasswordlessUser: !!session.passwordlessUser,
|
||||
userName: client.getUserIdLocalpart(),
|
||||
});
|
||||
} else {
|
||||
localStorage.removeItem("matrix-auth-store");
|
||||
if (newClient) {
|
||||
localStorage.setItem("matrix-auth-store", JSON.stringify(session));
|
||||
|
||||
setState({
|
||||
client: undefined,
|
||||
loading: false,
|
||||
isAuthenticated: false,
|
||||
isPasswordlessUser: false,
|
||||
userName: null,
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
setState({
|
||||
client: newClient,
|
||||
loading: false,
|
||||
isAuthenticated: true,
|
||||
isPasswordlessUser: !!session.passwordlessUser,
|
||||
userName: newClient.getUserIdLocalpart(),
|
||||
});
|
||||
} else {
|
||||
localStorage.removeItem("matrix-auth-store");
|
||||
|
||||
setState({
|
||||
client: undefined,
|
||||
loading: false,
|
||||
isAuthenticated: false,
|
||||
isPasswordlessUser: false,
|
||||
userName: null,
|
||||
});
|
||||
}
|
||||
},
|
||||
[client]
|
||||
);
|
||||
|
||||
const logout = useCallback(() => {
|
||||
localStorage.removeItem("matrix-auth-store");
|
||||
window.location = "/";
|
||||
}, [history]);
|
||||
|
||||
useEffect(() => {
|
||||
if ("BroadcastChannel" in window) {
|
||||
const loadTime = Date.now();
|
||||
const broadcastChannel = new BroadcastChannel("matrix-video-chat");
|
||||
|
||||
function onMessage({ data }) {
|
||||
if (data.load !== undefined && data.load > loadTime) {
|
||||
if (client) {
|
||||
client.stopClient();
|
||||
}
|
||||
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
error: new Error(
|
||||
"This application has been opened in another tab."
|
||||
),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
broadcastChannel.addEventListener("message", onMessage);
|
||||
broadcastChannel.postMessage({ load: loadTime });
|
||||
|
||||
return () => {
|
||||
broadcastChannel.removeEventListener("message", onMessage);
|
||||
};
|
||||
}
|
||||
}, [client]);
|
||||
|
||||
const context = useMemo(
|
||||
() => ({
|
||||
loading,
|
||||
@@ -195,6 +233,14 @@ export function ClientProvider({ children }) {
|
||||
]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
window.matrixclient = client;
|
||||
}, [client]);
|
||||
|
||||
if (error) {
|
||||
return <ErrorView error={error} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ClientContext.Provider value={context}>{children}</ClientContext.Provider>
|
||||
);
|
||||
|
||||
@@ -31,13 +31,7 @@ import { usePageTitle } from "../usePageTitle";
|
||||
export function RegisterPage() {
|
||||
usePageTitle("Register");
|
||||
|
||||
const {
|
||||
loading,
|
||||
client,
|
||||
changePassword,
|
||||
isAuthenticated,
|
||||
isPasswordlessUser,
|
||||
} = useClient();
|
||||
const { loading, isAuthenticated, isPasswordlessUser, client } = useClient();
|
||||
const confirmPasswordRef = useRef();
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
@@ -64,11 +58,31 @@ export function RegisterPage() {
|
||||
async function submit() {
|
||||
setRegistering(true);
|
||||
|
||||
if (isPasswordlessUser) {
|
||||
await changePassword(password);
|
||||
} else {
|
||||
const recaptchaResponse = await execute();
|
||||
await register(userName, password, recaptchaResponse);
|
||||
let roomIds;
|
||||
|
||||
if (client && isPasswordlessUser) {
|
||||
const groupCalls = client.groupCallEventHandler.groupCalls.values();
|
||||
roomIds = Array.from(groupCalls).map(
|
||||
(groupCall) => groupCall.room.roomId
|
||||
);
|
||||
}
|
||||
|
||||
const recaptchaResponse = await execute();
|
||||
const newClient = await register(
|
||||
userName,
|
||||
password,
|
||||
userName,
|
||||
recaptchaResponse
|
||||
);
|
||||
|
||||
if (roomIds) {
|
||||
for (const roomId of roomIds) {
|
||||
try {
|
||||
await newClient.joinRoom(roomId);
|
||||
} catch (error) {
|
||||
console.warn(`Couldn't join room ${roomId}`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,15 +100,7 @@ export function RegisterPage() {
|
||||
reset();
|
||||
});
|
||||
},
|
||||
[
|
||||
register,
|
||||
changePassword,
|
||||
location,
|
||||
history,
|
||||
isPasswordlessUser,
|
||||
reset,
|
||||
execute,
|
||||
]
|
||||
[register, location, history, isPasswordlessUser, reset, execute, client]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -110,10 +116,10 @@ export function RegisterPage() {
|
||||
}, [password, passwordConfirmation]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && isAuthenticated && !isPasswordlessUser) {
|
||||
if (!loading && isAuthenticated && !isPasswordlessUser && !registering) {
|
||||
history.push("/");
|
||||
}
|
||||
}, [history, isAuthenticated, isPasswordlessUser]);
|
||||
}, [history, isAuthenticated, isPasswordlessUser, registering]);
|
||||
|
||||
if (loading) {
|
||||
return <LoadingView />;
|
||||
@@ -137,12 +143,6 @@ export function RegisterPage() {
|
||||
autoCapitalize="none"
|
||||
prefix="@"
|
||||
suffix={`:${defaultHomeserverHost}`}
|
||||
value={
|
||||
isAuthenticated && isPasswordlessUser
|
||||
? client.getUserIdLocalpart()
|
||||
: undefined
|
||||
}
|
||||
disabled={isAuthenticated && isPasswordlessUser}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
@@ -168,22 +168,20 @@ export function RegisterPage() {
|
||||
ref={confirmPasswordRef}
|
||||
/>
|
||||
</FieldRow>
|
||||
{!isPasswordlessUser && (
|
||||
<Caption>
|
||||
This site is protected by ReCAPTCHA and the Google{" "}
|
||||
<Link href="https://www.google.com/policies/privacy/">
|
||||
Privacy Policy
|
||||
</Link>{" "}
|
||||
and{" "}
|
||||
<Link href="https://policies.google.com/terms">
|
||||
Terms of Service
|
||||
</Link>{" "}
|
||||
apply.
|
||||
<br />
|
||||
By clicking "Log in", you agree to our{" "}
|
||||
<Link href={privacyPolicyUrl}>Terms and conditions</Link>
|
||||
</Caption>
|
||||
)}
|
||||
<Caption>
|
||||
This site is protected by ReCAPTCHA and the Google{" "}
|
||||
<Link href="https://www.google.com/policies/privacy/">
|
||||
Privacy Policy
|
||||
</Link>{" "}
|
||||
and{" "}
|
||||
<Link href="https://policies.google.com/terms">
|
||||
Terms of Service
|
||||
</Link>{" "}
|
||||
apply.
|
||||
<br />
|
||||
By clicking "Register", you agree to our{" "}
|
||||
<Link href={privacyPolicyUrl}>Terms and conditions</Link>
|
||||
</Caption>
|
||||
{error && (
|
||||
<FieldRow>
|
||||
<ErrorMessage>{error.message}</ErrorMessage>
|
||||
|
||||
137
src/auth/generateRandomName.js
Normal file
137
src/auth/generateRandomName.js
Normal file
@@ -0,0 +1,137 @@
|
||||
import {
|
||||
uniqueNamesGenerator,
|
||||
adjectives,
|
||||
colors,
|
||||
animals,
|
||||
} from "unique-names-generator";
|
||||
|
||||
const elements = [
|
||||
"hydrogen",
|
||||
"helium",
|
||||
"lithium",
|
||||
"beryllium",
|
||||
"boron",
|
||||
"carbon",
|
||||
"nitrogen",
|
||||
"oxygen",
|
||||
"fluorine",
|
||||
"neon",
|
||||
"sodium",
|
||||
"magnesium",
|
||||
"aluminum",
|
||||
"silicon",
|
||||
"phosphorus",
|
||||
"sulfur",
|
||||
"chlorine",
|
||||
"argon",
|
||||
"potassium",
|
||||
"calcium",
|
||||
"scandium",
|
||||
"titanium",
|
||||
"vanadium",
|
||||
"chromium",
|
||||
"manganese",
|
||||
"iron",
|
||||
"cobalt",
|
||||
"nickel",
|
||||
"copper",
|
||||
"zinc",
|
||||
"gallium",
|
||||
"germanium",
|
||||
"arsenic",
|
||||
"selenium",
|
||||
"bromine",
|
||||
"krypton",
|
||||
"rubidium",
|
||||
"strontium",
|
||||
"yttrium",
|
||||
"zirconium",
|
||||
"niobium",
|
||||
"molybdenum",
|
||||
"technetium",
|
||||
"ruthenium",
|
||||
"rhodium",
|
||||
"palladium",
|
||||
"silver",
|
||||
"cadmium",
|
||||
"indium",
|
||||
"tin",
|
||||
"antimony",
|
||||
"tellurium",
|
||||
"iodine",
|
||||
"xenon",
|
||||
"cesium",
|
||||
"barium",
|
||||
"lanthanum",
|
||||
"cerium",
|
||||
"praseodymium",
|
||||
"neodymium",
|
||||
"promethium",
|
||||
"samarium",
|
||||
"europium",
|
||||
"gadolinium",
|
||||
"terbium",
|
||||
"dysprosium",
|
||||
"holmium",
|
||||
"erbium",
|
||||
"thulium",
|
||||
"ytterbium",
|
||||
"lutetium",
|
||||
"hafnium",
|
||||
"tantalum",
|
||||
"wolfram",
|
||||
"rhenium",
|
||||
"osmium",
|
||||
"iridium",
|
||||
"platinum",
|
||||
"gold",
|
||||
"mercury",
|
||||
"thallium",
|
||||
"lead",
|
||||
"bismuth",
|
||||
"polonium",
|
||||
"astatine",
|
||||
"radon",
|
||||
"francium",
|
||||
"radium",
|
||||
"actinium",
|
||||
"thorium",
|
||||
"protactinium",
|
||||
"uranium",
|
||||
"neptunium",
|
||||
"plutonium",
|
||||
"americium",
|
||||
"curium",
|
||||
"berkelium",
|
||||
"californium",
|
||||
"einsteinium",
|
||||
"fermium",
|
||||
"mendelevium",
|
||||
"nobelium",
|
||||
"lawrencium",
|
||||
"rutherfordium",
|
||||
"dubnium",
|
||||
"seaborgium",
|
||||
"bohrium",
|
||||
"hassium",
|
||||
"meitnerium",
|
||||
"darmstadtium",
|
||||
"roentgenium",
|
||||
"copernicium",
|
||||
"nihonium",
|
||||
"flerovium",
|
||||
"moscovium",
|
||||
"livermorium",
|
||||
"tennessine",
|
||||
"oganesson",
|
||||
];
|
||||
|
||||
export function generateRandomName(config) {
|
||||
return uniqueNamesGenerator({
|
||||
dictionaries: [colors, adjectives, animals, elements],
|
||||
style: "lowerCase",
|
||||
length: 3,
|
||||
separator: "-",
|
||||
...config,
|
||||
});
|
||||
}
|
||||
@@ -25,7 +25,13 @@ export function useInteractiveRegistration() {
|
||||
}, []);
|
||||
|
||||
const register = useCallback(
|
||||
async (username, password, recaptchaResponse, passwordlessUser) => {
|
||||
async (
|
||||
username,
|
||||
password,
|
||||
displayName,
|
||||
recaptchaResponse,
|
||||
passwordlessUser
|
||||
) => {
|
||||
const interactiveAuth = new InteractiveAuth({
|
||||
matrixClient: authClientRef.current,
|
||||
busyChanged(loading) {
|
||||
@@ -66,7 +72,7 @@ export function useInteractiveRegistration() {
|
||||
deviceId: device_id,
|
||||
});
|
||||
|
||||
await client.setDisplayName(username);
|
||||
await client.setDisplayName(displayName);
|
||||
|
||||
const session = { user_id, device_id, access_token, passwordlessUser };
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import { TooltipTrigger } from "../Tooltip";
|
||||
export const variantToClassName = {
|
||||
default: [styles.button],
|
||||
toolbar: [styles.toolbarButton],
|
||||
toolbarSecondary: [styles.toolbarButtonSecondary],
|
||||
icon: [styles.iconButton],
|
||||
secondary: [styles.secondary],
|
||||
copy: [styles.copyButton],
|
||||
@@ -103,7 +104,7 @@ export function VideoButton({ muted, ...rest }) {
|
||||
export function ScreenshareButton({ enabled, className, ...rest }) {
|
||||
return (
|
||||
<TooltipTrigger>
|
||||
<Button variant="toolbar" {...rest} on={enabled}>
|
||||
<Button variant="toolbarSecondary" {...rest} on={enabled}>
|
||||
<ScreenshareIcon />
|
||||
</Button>
|
||||
{() => (enabled ? "Stop sharing screen" : "Share screen")}
|
||||
|
||||
@@ -16,6 +16,7 @@ limitations under the License.
|
||||
|
||||
.button,
|
||||
.toolbarButton,
|
||||
.toolbarButtonSecondary,
|
||||
.iconButton,
|
||||
.iconCopyButton,
|
||||
.secondary,
|
||||
@@ -48,6 +49,7 @@ limitations under the License.
|
||||
|
||||
.button:focus,
|
||||
.toolbarButton:focus,
|
||||
.toolbarButtonSecondary:focus,
|
||||
.iconButton:focus,
|
||||
.iconCopyButton:focus,
|
||||
.secondary:focus,
|
||||
@@ -55,14 +57,16 @@ limitations under the License.
|
||||
outline: auto;
|
||||
}
|
||||
|
||||
.toolbarButton {
|
||||
.toolbarButton,
|
||||
.toolbarButtonSecondary {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50px;
|
||||
background-color: var(--bgColor2);
|
||||
}
|
||||
|
||||
.toolbarButton:hover {
|
||||
.toolbarButton:hover,
|
||||
.toolbarButtonSecondary:hover {
|
||||
background-color: var(--bgColor4);
|
||||
}
|
||||
|
||||
@@ -71,6 +75,10 @@ limitations under the License.
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.toolbarButtonSecondary.on {
|
||||
background-color: #0dbd8b;
|
||||
}
|
||||
|
||||
.iconButton:not(.stroke) svg * {
|
||||
fill: #ffffff;
|
||||
}
|
||||
@@ -100,6 +108,10 @@ limitations under the License.
|
||||
fill: #21262c;
|
||||
}
|
||||
|
||||
.toolbarButtonSecondary.on svg * {
|
||||
fill: #ffffff;
|
||||
}
|
||||
|
||||
.secondary,
|
||||
.copyButton {
|
||||
color: #0dbd8b;
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Link } from "react-router-dom";
|
||||
import { CopyButton } from "../button";
|
||||
import { Facepile } from "../Facepile";
|
||||
import { Avatar } from "../Avatar";
|
||||
import { ReactComponent as VideoIcon } from "../icons/Video.svg";
|
||||
import styles from "./CallList.module.css";
|
||||
import { getRoomUrl } from "../matrix-utils";
|
||||
import { Body, Caption } from "../typography/Typography";
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
display: flex;
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.avatar,
|
||||
|
||||
@@ -17,6 +17,7 @@ import { Form } from "../form/Form";
|
||||
export function RegisteredView({ client }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState();
|
||||
const history = useHistory();
|
||||
const onSubmit = useCallback(
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
@@ -55,7 +56,6 @@ export function RegisteredView({ client }) {
|
||||
|
||||
const { modalState, modalProps } = useModalTriggerState();
|
||||
const [existingRoomId, setExistingRoomId] = useState();
|
||||
const history = useHistory();
|
||||
const onJoinExistingRoom = useCallback(() => {
|
||||
history.push(`/${existingRoomId}`);
|
||||
}, [history, existingRoomId]);
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Body, Caption, Link, Headline } from "../typography/Typography";
|
||||
import { Form } from "../form/Form";
|
||||
import styles from "./UnauthenticatedView.module.css";
|
||||
import commonStyles from "./common.module.css";
|
||||
import { generateRandomName } from "../auth/generateRandomName";
|
||||
|
||||
export function UnauthenticatedView() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -26,19 +27,20 @@ export function UnauthenticatedView() {
|
||||
e.preventDefault();
|
||||
const data = new FormData(e.target);
|
||||
const roomName = data.get("callName");
|
||||
const userName = data.get("userName");
|
||||
const displayName = data.get("displayName");
|
||||
|
||||
async function submit() {
|
||||
setError(undefined);
|
||||
setLoading(true);
|
||||
const recaptchaResponse = await execute();
|
||||
const userName = generateRandomName();
|
||||
const client = await register(
|
||||
userName,
|
||||
randomString(16),
|
||||
displayName,
|
||||
recaptchaResponse,
|
||||
true
|
||||
);
|
||||
|
||||
const roomIdOrAlias = await createRoom(client, roomName);
|
||||
|
||||
if (roomIdOrAlias) {
|
||||
@@ -100,10 +102,10 @@ export function UnauthenticatedView() {
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="userName"
|
||||
name="userName"
|
||||
label="Username"
|
||||
placeholder="Username"
|
||||
id="displayName"
|
||||
name="displayName"
|
||||
label="Display Name"
|
||||
placeholder="Display Name"
|
||||
type="text"
|
||||
required
|
||||
autoComplete="off"
|
||||
|
||||
@@ -27,6 +27,8 @@ import { InspectorContextProvider } from "./room/GroupCallInspector";
|
||||
|
||||
rageshake.init();
|
||||
|
||||
console.info(`matrix-video-chat ${import.meta.env.VITE_APP_VERSION || "dev"}`);
|
||||
|
||||
if (import.meta.env.VITE_CUSTOM_THEME) {
|
||||
const style = document.documentElement.style;
|
||||
style.setProperty("--primaryColor", import.meta.env.VITE_PRIMARY_COLOR);
|
||||
|
||||
@@ -50,6 +50,31 @@ export function roomAliasFromRoomName(roomName) {
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
export function roomNameFromRoomId(roomId) {
|
||||
return roomId
|
||||
.match(/([^:]+):.*$/)[1]
|
||||
.substring(1)
|
||||
.split("-")
|
||||
.map((part) =>
|
||||
part.length > 0 ? part.charAt(0).toUpperCase() + part.slice(1) : part
|
||||
)
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
export function isLocalRoomId(roomId) {
|
||||
if (!roomId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const parts = roomId.match(/[^:]+:(.*)$/);
|
||||
|
||||
if (parts.length < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parts[1] === defaultHomeserverHost;
|
||||
}
|
||||
|
||||
export async function createRoom(client, name) {
|
||||
const { room_id, room_alias } = await client.createRoom({
|
||||
visibility: "private",
|
||||
@@ -95,12 +120,12 @@ export function getRoomUrl(roomId) {
|
||||
const [localPart, host] = roomId.replace("#", "").split(":");
|
||||
|
||||
if (host !== defaultHomeserverHost) {
|
||||
return `${window.location.host}/room/${roomId}`;
|
||||
return `${window.location.protocol}//${window.location.host}/room/${roomId}`;
|
||||
} else {
|
||||
return `${window.location.host}/${localPart}`;
|
||||
return `${window.location.protocol}//${window.location.host}/${localPart}`;
|
||||
}
|
||||
} else {
|
||||
return `${window.location.host}/room/${roomId}`;
|
||||
return `${window.location.protocol}//${window.location.host}/room/${roomId}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ import React from "react";
|
||||
import { useLoadGroupCall } from "./useLoadGroupCall";
|
||||
import { ErrorView, FullScreenView } from "../FullScreenView";
|
||||
import { usePageTitle } from "../usePageTitle";
|
||||
import { isLocalRoomId } from "../matrix-utils";
|
||||
import { RoomNotFoundView } from "./RoomNotFoundView";
|
||||
|
||||
export function GroupCallLoader({ client, roomId, viaServers, children }) {
|
||||
const { loading, error, groupCall } = useLoadGroupCall(
|
||||
@@ -20,6 +22,16 @@ export function GroupCallLoader({ client, roomId, viaServers, children }) {
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
error &&
|
||||
(error.errcode === "M_NOT_FOUND" ||
|
||||
(error.message &&
|
||||
error.message.indexOf("Failed to fetch alias") !== -1)) &&
|
||||
isLocalRoomId(roomId)
|
||||
) {
|
||||
return <RoomNotFoundView client={client} roomId={roomId} />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <ErrorView error={error} />;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import { Avatar } from "../Avatar";
|
||||
import { UserMenuContainer } from "../UserMenuContainer";
|
||||
import { useRageshakeRequestModal } from "../settings/rageshake";
|
||||
import { RageshakeRequestModal } from "./RageshakeRequestModal";
|
||||
import { usePreventScroll } from "@react-aria/overlays";
|
||||
|
||||
const canScreenshare = "getDisplayMedia" in navigator.mediaDevices;
|
||||
// There is currently a bug in Safari our our code with cloning and sending MediaStreams
|
||||
@@ -47,7 +48,8 @@ export function InCallView({
|
||||
showInspector,
|
||||
roomId,
|
||||
}) {
|
||||
const [layout, setLayout] = useVideoGridLayout();
|
||||
usePreventScroll();
|
||||
const [layout, setLayout] = useVideoGridLayout(screenshareFeeds.length > 0);
|
||||
|
||||
const items = useMemo(() => {
|
||||
const participants = [];
|
||||
@@ -57,7 +59,7 @@ export function InCallView({
|
||||
id: callFeed.stream.id,
|
||||
callFeed,
|
||||
focused:
|
||||
screenshareFeeds.length === 0
|
||||
screenshareFeeds.length === 0 && layout === "spotlight"
|
||||
? callFeed.userId === activeSpeaker
|
||||
: false,
|
||||
});
|
||||
@@ -80,14 +82,14 @@ export function InCallView({
|
||||
}
|
||||
|
||||
return participants;
|
||||
}, [userMediaFeeds, activeSpeaker, screenshareFeeds]);
|
||||
}, [userMediaFeeds, activeSpeaker, screenshareFeeds, layout]);
|
||||
|
||||
const onFocusTile = useCallback(
|
||||
(tiles, focusedTile) => {
|
||||
if (layout === "freedom") {
|
||||
return tiles.map((tile) => {
|
||||
if (tile === focusedTile) {
|
||||
return { ...tile, presenter: !tile.presenter };
|
||||
return { ...tile, focused: !tile.focused };
|
||||
}
|
||||
|
||||
return tile;
|
||||
@@ -156,6 +158,7 @@ export function InCallView({
|
||||
key={item.id}
|
||||
item={item}
|
||||
getAvatar={renderAvatar}
|
||||
showName={items.length > 2 || item.focused}
|
||||
{...rest}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { useInteractiveRegistration } from "../auth/useInteractiveRegistration";
|
||||
import { Form } from "../form/Form";
|
||||
import { UserMenuContainer } from "../UserMenuContainer";
|
||||
import { generateRandomName } from "../auth/generateRandomName";
|
||||
|
||||
export function RoomAuthView() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -21,13 +22,20 @@ export function RoomAuthView() {
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
const data = new FormData(e.target);
|
||||
const userName = data.get("userName");
|
||||
const displayName = data.get("displayName");
|
||||
|
||||
async function submit() {
|
||||
setError(undefined);
|
||||
setLoading(true);
|
||||
const recaptchaResponse = await execute();
|
||||
await register(userName, randomString(16), recaptchaResponse, true);
|
||||
const userName = generateRandomName();
|
||||
await register(
|
||||
userName,
|
||||
randomString(16),
|
||||
displayName,
|
||||
recaptchaResponse,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
submit().catch((error) => {
|
||||
@@ -58,10 +66,10 @@ export function RoomAuthView() {
|
||||
<Form className={styles.form} onSubmit={onSubmit}>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="userName"
|
||||
name="userName"
|
||||
label="Pick a user name"
|
||||
placeholder="Pick a user name"
|
||||
id="displayName"
|
||||
name="displayName"
|
||||
label="Display Name"
|
||||
placeholder="Display Name"
|
||||
type="text"
|
||||
required
|
||||
autoComplete="off"
|
||||
|
||||
76
src/room/RoomNotFoundView.jsx
Normal file
76
src/room/RoomNotFoundView.jsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import React, { useState, useCallback } from "react";
|
||||
import { FullScreenView } from "../FullScreenView";
|
||||
import { Headline, Subtitle } from "../typography/Typography";
|
||||
import { createRoom, roomNameFromRoomId } from "../matrix-utils";
|
||||
import { FieldRow, ErrorMessage, InputField } from "../input/Input";
|
||||
import { Button } from "../button";
|
||||
import { Form } from "../form/Form";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import styles from "./RoomNotFoundView.module.css";
|
||||
|
||||
export function RoomNotFoundView({ client, roomId }) {
|
||||
const history = useHistory();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState();
|
||||
const roomName = roomNameFromRoomId(roomId);
|
||||
const onSubmit = useCallback(
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
async function submit() {
|
||||
setError(undefined);
|
||||
setLoading(true);
|
||||
|
||||
const roomIdOrAlias = await createRoom(client, roomName);
|
||||
|
||||
if (roomIdOrAlias) {
|
||||
history.push(`/room/${roomIdOrAlias}`);
|
||||
}
|
||||
}
|
||||
|
||||
submit().catch((error) => {
|
||||
console.error(error);
|
||||
setLoading(false);
|
||||
setError(error);
|
||||
});
|
||||
},
|
||||
[client, roomName]
|
||||
);
|
||||
|
||||
return (
|
||||
<FullScreenView>
|
||||
<Headline>Call Not Found</Headline>
|
||||
<Subtitle>Would you like to create this call?</Subtitle>
|
||||
<Form onSubmit={onSubmit} className={styles.form}>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="callName"
|
||||
name="callName"
|
||||
label="Call name"
|
||||
placeholder="Call name"
|
||||
type="text"
|
||||
required
|
||||
autoComplete="off"
|
||||
value={roomName}
|
||||
disabled
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<Button
|
||||
type="submit"
|
||||
size="lg"
|
||||
disabled={loading}
|
||||
className={styles.button}
|
||||
>
|
||||
{loading ? "Loading..." : "Create Room"}
|
||||
</Button>
|
||||
</FieldRow>
|
||||
{error && (
|
||||
<FieldRow>
|
||||
<ErrorMessage>{error.message}</ErrorMessage>
|
||||
</FieldRow>
|
||||
)}
|
||||
</Form>
|
||||
</FullScreenView>
|
||||
);
|
||||
}
|
||||
11
src/room/RoomNotFoundView.module.css
Normal file
11
src/room/RoomNotFoundView.module.css
Normal file
@@ -0,0 +1,11 @@
|
||||
.form {
|
||||
padding: 0 24px;
|
||||
justify-content: center;
|
||||
max-width: 409px;
|
||||
width: calc(100% - 48px);
|
||||
margin-bottom: 72px;
|
||||
}
|
||||
|
||||
.button {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -32,7 +32,7 @@ export function RoomPage() {
|
||||
const params = new URLSearchParams(search);
|
||||
return [params.has("simple"), params.getAll("via")];
|
||||
}, [search]);
|
||||
const roomId = maybeRoomId || hash;
|
||||
const roomId = (maybeRoomId || hash || "").toLowerCase();
|
||||
|
||||
if (loading) {
|
||||
return <LoadingView />;
|
||||
|
||||
@@ -11,7 +11,7 @@ export function RoomRedirect() {
|
||||
let roomId = pathname;
|
||||
|
||||
if (pathname.startsWith("/")) {
|
||||
roomId = roomId.substr(1, roomId.length);
|
||||
roomId = roomId.substring(1, roomId.length);
|
||||
}
|
||||
|
||||
if (!roomId.startsWith("#") && !roomId.startsWith("!")) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useMediaHandler } from "./useMediaHandler";
|
||||
import { FieldRow, InputField } from "../input/Input";
|
||||
import { Button } from "../button";
|
||||
import { useDownloadDebugLog } from "./rageshake";
|
||||
import { Body } from "../typography/Typography";
|
||||
|
||||
export function SettingsModal({
|
||||
client,
|
||||
@@ -82,6 +83,11 @@ export function SettingsModal({
|
||||
</>
|
||||
}
|
||||
>
|
||||
<FieldRow>
|
||||
<Body className={styles.fieldRowText}>
|
||||
Version: {import.meta.env.VITE_APP_VERSION || "dev"}
|
||||
</Body>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="showInspector"
|
||||
|
||||
@@ -6,3 +6,7 @@
|
||||
.tabContainer {
|
||||
margin: 27px 16px;
|
||||
}
|
||||
|
||||
.fieldRowText {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export function useSubmitRageshake() {
|
||||
opts.description || "User did not supply any additional text."
|
||||
);
|
||||
body.append("app", "matrix-video-chat");
|
||||
body.append("version", "dev");
|
||||
body.append("version", import.meta.env.VITE_APP_VERSION || "dev");
|
||||
body.append("user_agent", userAgent);
|
||||
body.append("installed_pwa", false);
|
||||
body.append("touch_input", touchInput);
|
||||
|
||||
@@ -15,7 +15,7 @@ export default {
|
||||
};
|
||||
|
||||
export const ParticipantsTest = () => {
|
||||
const [layout, setLayout] = useVideoGridLayout();
|
||||
const [layout, setLayout] = useVideoGridLayout(false);
|
||||
const [participantCount, setParticipantCount] = useState(1);
|
||||
|
||||
const items = useMemo(
|
||||
@@ -60,7 +60,12 @@ export const ParticipantsTest = () => {
|
||||
>
|
||||
<VideoGrid layout={layout} items={items}>
|
||||
{({ item, ...rest }) => (
|
||||
<VideoTile key={item.id} name={`User ${item.id}`} {...rest} />
|
||||
<VideoTile
|
||||
key={item.id}
|
||||
name={`User ${item.id}`}
|
||||
showName={items.length > 2 || item.focused}
|
||||
{...rest}
|
||||
/>
|
||||
)}
|
||||
</VideoGrid>
|
||||
</div>
|
||||
|
||||
@@ -12039,6 +12039,11 @@ unique-filename@^1.1.1:
|
||||
dependencies:
|
||||
unique-slug "^2.0.0"
|
||||
|
||||
unique-names-generator@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/unique-names-generator/-/unique-names-generator-4.6.0.tgz#852c1db8149815d6cf665a601820fe80ec2fbc37"
|
||||
integrity sha512-m0fke1emBeT96UYn2psPQYwljooDWRTKt9oUZ5vlt88ZFMBGxqwPyLHXwCfkbgdm8jzioCp7oIpo6KdM+fnUlQ==
|
||||
|
||||
unique-slug@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c"
|
||||
|
||||
Reference in New Issue
Block a user