Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb07ce32cb | ||
|
|
6866d662f7 | ||
|
|
51a2027d64 | ||
|
|
0f6b8f9bb1 | ||
|
|
63229ce2d7 | ||
|
|
1d620910c5 | ||
|
|
47357b3fc6 | ||
|
|
3ed35f9477 | ||
|
|
a369444b62 | ||
|
|
742d658021 | ||
|
|
681c24a0ca |
@@ -41,7 +41,8 @@
|
|||||||
"react-router": "6",
|
"react-router": "6",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-use-clipboard": "^1.0.7",
|
"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": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.16.5",
|
"@babel/core": "^7.16.5",
|
||||||
|
|||||||
@@ -145,16 +145,21 @@ export function ClientProvider({ children }) {
|
|||||||
[client]
|
[client]
|
||||||
);
|
);
|
||||||
|
|
||||||
const setClient = useCallback((client, session) => {
|
const setClient = useCallback(
|
||||||
if (client) {
|
(newClient, session) => {
|
||||||
|
if (client && client !== newClient) {
|
||||||
|
client.stopClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newClient) {
|
||||||
localStorage.setItem("matrix-auth-store", JSON.stringify(session));
|
localStorage.setItem("matrix-auth-store", JSON.stringify(session));
|
||||||
|
|
||||||
setState({
|
setState({
|
||||||
client,
|
client: newClient,
|
||||||
loading: false,
|
loading: false,
|
||||||
isAuthenticated: true,
|
isAuthenticated: true,
|
||||||
isPasswordlessUser: !!session.passwordlessUser,
|
isPasswordlessUser: !!session.passwordlessUser,
|
||||||
userName: client.getUserIdLocalpart(),
|
userName: newClient.getUserIdLocalpart(),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem("matrix-auth-store");
|
localStorage.removeItem("matrix-auth-store");
|
||||||
@@ -167,7 +172,9 @@ export function ClientProvider({ children }) {
|
|||||||
userName: null,
|
userName: null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
},
|
||||||
|
[client]
|
||||||
|
);
|
||||||
|
|
||||||
const logout = useCallback(() => {
|
const logout = useCallback(() => {
|
||||||
localStorage.removeItem("matrix-auth-store");
|
localStorage.removeItem("matrix-auth-store");
|
||||||
@@ -226,6 +233,10 @@ export function ClientProvider({ children }) {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.matrixclient = client;
|
||||||
|
}, [client]);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <ErrorView error={error} />;
|
return <ErrorView error={error} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,13 +31,7 @@ import { usePageTitle } from "../usePageTitle";
|
|||||||
export function RegisterPage() {
|
export function RegisterPage() {
|
||||||
usePageTitle("Register");
|
usePageTitle("Register");
|
||||||
|
|
||||||
const {
|
const { loading, isAuthenticated, isPasswordlessUser, client } = useClient();
|
||||||
loading,
|
|
||||||
client,
|
|
||||||
changePassword,
|
|
||||||
isAuthenticated,
|
|
||||||
isPasswordlessUser,
|
|
||||||
} = useClient();
|
|
||||||
const confirmPasswordRef = useRef();
|
const confirmPasswordRef = useRef();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -64,11 +58,31 @@ export function RegisterPage() {
|
|||||||
async function submit() {
|
async function submit() {
|
||||||
setRegistering(true);
|
setRegistering(true);
|
||||||
|
|
||||||
if (isPasswordlessUser) {
|
let roomIds;
|
||||||
await changePassword(password);
|
|
||||||
} else {
|
if (client && isPasswordlessUser) {
|
||||||
|
const groupCalls = client.groupCallEventHandler.groupCalls.values();
|
||||||
|
roomIds = Array.from(groupCalls).map(
|
||||||
|
(groupCall) => groupCall.room.roomId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const recaptchaResponse = await execute();
|
const recaptchaResponse = await execute();
|
||||||
await register(userName, password, recaptchaResponse);
|
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();
|
reset();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[
|
[register, location, history, isPasswordlessUser, reset, execute, client]
|
||||||
register,
|
|
||||||
changePassword,
|
|
||||||
location,
|
|
||||||
history,
|
|
||||||
isPasswordlessUser,
|
|
||||||
reset,
|
|
||||||
execute,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -110,10 +116,10 @@ export function RegisterPage() {
|
|||||||
}, [password, passwordConfirmation]);
|
}, [password, passwordConfirmation]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!loading && isAuthenticated && !isPasswordlessUser) {
|
if (!loading && isAuthenticated && !isPasswordlessUser && !registering) {
|
||||||
history.push("/");
|
history.push("/");
|
||||||
}
|
}
|
||||||
}, [history, isAuthenticated, isPasswordlessUser]);
|
}, [history, isAuthenticated, isPasswordlessUser, registering]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <LoadingView />;
|
return <LoadingView />;
|
||||||
@@ -137,12 +143,6 @@ export function RegisterPage() {
|
|||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
prefix="@"
|
prefix="@"
|
||||||
suffix={`:${defaultHomeserverHost}`}
|
suffix={`:${defaultHomeserverHost}`}
|
||||||
value={
|
|
||||||
isAuthenticated && isPasswordlessUser
|
|
||||||
? client.getUserIdLocalpart()
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
disabled={isAuthenticated && isPasswordlessUser}
|
|
||||||
/>
|
/>
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
<FieldRow>
|
<FieldRow>
|
||||||
@@ -168,7 +168,6 @@ export function RegisterPage() {
|
|||||||
ref={confirmPasswordRef}
|
ref={confirmPasswordRef}
|
||||||
/>
|
/>
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
{!isPasswordlessUser && (
|
|
||||||
<Caption>
|
<Caption>
|
||||||
This site is protected by ReCAPTCHA and the Google{" "}
|
This site is protected by ReCAPTCHA and the Google{" "}
|
||||||
<Link href="https://www.google.com/policies/privacy/">
|
<Link href="https://www.google.com/policies/privacy/">
|
||||||
@@ -180,10 +179,9 @@ export function RegisterPage() {
|
|||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
apply.
|
apply.
|
||||||
<br />
|
<br />
|
||||||
By clicking "Log in", you agree to our{" "}
|
By clicking "Register", you agree to our{" "}
|
||||||
<Link href={privacyPolicyUrl}>Terms and conditions</Link>
|
<Link href={privacyPolicyUrl}>Terms and conditions</Link>
|
||||||
</Caption>
|
</Caption>
|
||||||
)}
|
|
||||||
{error && (
|
{error && (
|
||||||
<FieldRow>
|
<FieldRow>
|
||||||
<ErrorMessage>{error.message}</ErrorMessage>
|
<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(
|
const register = useCallback(
|
||||||
async (username, password, recaptchaResponse, passwordlessUser) => {
|
async (
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
displayName,
|
||||||
|
recaptchaResponse,
|
||||||
|
passwordlessUser
|
||||||
|
) => {
|
||||||
const interactiveAuth = new InteractiveAuth({
|
const interactiveAuth = new InteractiveAuth({
|
||||||
matrixClient: authClientRef.current,
|
matrixClient: authClientRef.current,
|
||||||
busyChanged(loading) {
|
busyChanged(loading) {
|
||||||
@@ -66,7 +72,7 @@ export function useInteractiveRegistration() {
|
|||||||
deviceId: device_id,
|
deviceId: device_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
await client.setDisplayName(username);
|
await client.setDisplayName(displayName);
|
||||||
|
|
||||||
const session = { user_id, device_id, access_token, passwordlessUser };
|
const session = { user_id, device_id, access_token, passwordlessUser };
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { TooltipTrigger } from "../Tooltip";
|
|||||||
export const variantToClassName = {
|
export const variantToClassName = {
|
||||||
default: [styles.button],
|
default: [styles.button],
|
||||||
toolbar: [styles.toolbarButton],
|
toolbar: [styles.toolbarButton],
|
||||||
|
toolbarSecondary: [styles.toolbarButtonSecondary],
|
||||||
icon: [styles.iconButton],
|
icon: [styles.iconButton],
|
||||||
secondary: [styles.secondary],
|
secondary: [styles.secondary],
|
||||||
copy: [styles.copyButton],
|
copy: [styles.copyButton],
|
||||||
@@ -103,7 +104,7 @@ export function VideoButton({ muted, ...rest }) {
|
|||||||
export function ScreenshareButton({ enabled, className, ...rest }) {
|
export function ScreenshareButton({ enabled, className, ...rest }) {
|
||||||
return (
|
return (
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<Button variant="toolbar" {...rest} on={enabled}>
|
<Button variant="toolbarSecondary" {...rest} on={enabled}>
|
||||||
<ScreenshareIcon />
|
<ScreenshareIcon />
|
||||||
</Button>
|
</Button>
|
||||||
{() => (enabled ? "Stop sharing screen" : "Share screen")}
|
{() => (enabled ? "Stop sharing screen" : "Share screen")}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ limitations under the License.
|
|||||||
|
|
||||||
.button,
|
.button,
|
||||||
.toolbarButton,
|
.toolbarButton,
|
||||||
|
.toolbarButtonSecondary,
|
||||||
.iconButton,
|
.iconButton,
|
||||||
.iconCopyButton,
|
.iconCopyButton,
|
||||||
.secondary,
|
.secondary,
|
||||||
@@ -48,6 +49,7 @@ limitations under the License.
|
|||||||
|
|
||||||
.button:focus,
|
.button:focus,
|
||||||
.toolbarButton:focus,
|
.toolbarButton:focus,
|
||||||
|
.toolbarButtonSecondary:focus,
|
||||||
.iconButton:focus,
|
.iconButton:focus,
|
||||||
.iconCopyButton:focus,
|
.iconCopyButton:focus,
|
||||||
.secondary:focus,
|
.secondary:focus,
|
||||||
@@ -55,14 +57,16 @@ limitations under the License.
|
|||||||
outline: auto;
|
outline: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbarButton {
|
.toolbarButton,
|
||||||
|
.toolbarButtonSecondary {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
border-radius: 50px;
|
border-radius: 50px;
|
||||||
background-color: var(--bgColor2);
|
background-color: var(--bgColor2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbarButton:hover {
|
.toolbarButton:hover,
|
||||||
|
.toolbarButtonSecondary:hover {
|
||||||
background-color: var(--bgColor4);
|
background-color: var(--bgColor4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,6 +75,10 @@ limitations under the License.
|
|||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toolbarButtonSecondary.on {
|
||||||
|
background-color: #0dbd8b;
|
||||||
|
}
|
||||||
|
|
||||||
.iconButton:not(.stroke) svg * {
|
.iconButton:not(.stroke) svg * {
|
||||||
fill: #ffffff;
|
fill: #ffffff;
|
||||||
}
|
}
|
||||||
@@ -100,6 +108,10 @@ limitations under the License.
|
|||||||
fill: #21262c;
|
fill: #21262c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toolbarButtonSecondary.on svg * {
|
||||||
|
fill: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
.secondary,
|
.secondary,
|
||||||
.copyButton {
|
.copyButton {
|
||||||
color: #0dbd8b;
|
color: #0dbd8b;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { Link } from "react-router-dom";
|
|||||||
import { CopyButton } from "../button";
|
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 styles from "./CallList.module.css";
|
import styles from "./CallList.module.css";
|
||||||
import { getRoomUrl } from "../matrix-utils";
|
import { getRoomUrl } from "../matrix-utils";
|
||||||
import { Body, Caption } from "../typography/Typography";
|
import { Body, Caption } from "../typography/Typography";
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar,
|
.avatar,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { Form } from "../form/Form";
|
|||||||
export function RegisteredView({ client }) {
|
export function RegisteredView({ client }) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState();
|
const [error, setError] = useState();
|
||||||
|
const history = useHistory();
|
||||||
const onSubmit = useCallback(
|
const onSubmit = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -55,7 +56,6 @@ export function RegisteredView({ client }) {
|
|||||||
|
|
||||||
const { modalState, modalProps } = useModalTriggerState();
|
const { modalState, modalProps } = useModalTriggerState();
|
||||||
const [existingRoomId, setExistingRoomId] = useState();
|
const [existingRoomId, setExistingRoomId] = useState();
|
||||||
const history = useHistory();
|
|
||||||
const onJoinExistingRoom = useCallback(() => {
|
const onJoinExistingRoom = useCallback(() => {
|
||||||
history.push(`/${existingRoomId}`);
|
history.push(`/${existingRoomId}`);
|
||||||
}, [history, existingRoomId]);
|
}, [history, existingRoomId]);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { Body, Caption, Link, Headline } from "../typography/Typography";
|
|||||||
import { Form } from "../form/Form";
|
import { Form } from "../form/Form";
|
||||||
import styles from "./UnauthenticatedView.module.css";
|
import styles from "./UnauthenticatedView.module.css";
|
||||||
import commonStyles from "./common.module.css";
|
import commonStyles from "./common.module.css";
|
||||||
|
import { generateRandomName } from "../auth/generateRandomName";
|
||||||
|
|
||||||
export function UnauthenticatedView() {
|
export function UnauthenticatedView() {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -26,19 +27,20 @@ export function UnauthenticatedView() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const data = new FormData(e.target);
|
const data = new FormData(e.target);
|
||||||
const roomName = data.get("callName");
|
const roomName = data.get("callName");
|
||||||
const userName = data.get("userName");
|
const displayName = data.get("displayName");
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const recaptchaResponse = await execute();
|
const recaptchaResponse = await execute();
|
||||||
|
const userName = generateRandomName();
|
||||||
const client = await register(
|
const client = await register(
|
||||||
userName,
|
userName,
|
||||||
randomString(16),
|
randomString(16),
|
||||||
|
displayName,
|
||||||
recaptchaResponse,
|
recaptchaResponse,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
const roomIdOrAlias = await createRoom(client, roomName);
|
const roomIdOrAlias = await createRoom(client, roomName);
|
||||||
|
|
||||||
if (roomIdOrAlias) {
|
if (roomIdOrAlias) {
|
||||||
@@ -100,10 +102,10 @@ export function UnauthenticatedView() {
|
|||||||
</FieldRow>
|
</FieldRow>
|
||||||
<FieldRow>
|
<FieldRow>
|
||||||
<InputField
|
<InputField
|
||||||
id="userName"
|
id="displayName"
|
||||||
name="userName"
|
name="displayName"
|
||||||
label="Username"
|
label="Display Name"
|
||||||
placeholder="Username"
|
placeholder="Display Name"
|
||||||
type="text"
|
type="text"
|
||||||
required
|
required
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
|||||||
@@ -50,6 +50,31 @@ export function roomAliasFromRoomName(roomName) {
|
|||||||
.toLowerCase();
|
.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) {
|
export async function createRoom(client, name) {
|
||||||
const { room_id, room_alias } = await client.createRoom({
|
const { room_id, room_alias } = await client.createRoom({
|
||||||
visibility: "private",
|
visibility: "private",
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import React from "react";
|
|||||||
import { useLoadGroupCall } from "./useLoadGroupCall";
|
import { useLoadGroupCall } from "./useLoadGroupCall";
|
||||||
import { ErrorView, FullScreenView } from "../FullScreenView";
|
import { ErrorView, FullScreenView } from "../FullScreenView";
|
||||||
import { usePageTitle } from "../usePageTitle";
|
import { usePageTitle } from "../usePageTitle";
|
||||||
|
import { isLocalRoomId } from "../matrix-utils";
|
||||||
|
import { RoomNotFoundView } from "./RoomNotFoundView";
|
||||||
|
|
||||||
export function GroupCallLoader({ client, roomId, viaServers, children }) {
|
export function GroupCallLoader({ client, roomId, viaServers, children }) {
|
||||||
const { loading, error, groupCall } = useLoadGroupCall(
|
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) {
|
if (error) {
|
||||||
return <ErrorView error={error} />;
|
return <ErrorView error={error} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export function InCallView({
|
|||||||
showInspector,
|
showInspector,
|
||||||
roomId,
|
roomId,
|
||||||
}) {
|
}) {
|
||||||
const [layout, setLayout] = useVideoGridLayout();
|
const [layout, setLayout] = useVideoGridLayout(screenshareFeeds.length > 0);
|
||||||
|
|
||||||
const items = useMemo(() => {
|
const items = useMemo(() => {
|
||||||
const participants = [];
|
const participants = [];
|
||||||
@@ -57,7 +57,7 @@ export function InCallView({
|
|||||||
id: callFeed.stream.id,
|
id: callFeed.stream.id,
|
||||||
callFeed,
|
callFeed,
|
||||||
focused:
|
focused:
|
||||||
screenshareFeeds.length === 0
|
screenshareFeeds.length === 0 && layout === "spotlight"
|
||||||
? callFeed.userId === activeSpeaker
|
? callFeed.userId === activeSpeaker
|
||||||
: false,
|
: false,
|
||||||
});
|
});
|
||||||
@@ -80,14 +80,14 @@ export function InCallView({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return participants;
|
return participants;
|
||||||
}, [userMediaFeeds, activeSpeaker, screenshareFeeds]);
|
}, [userMediaFeeds, activeSpeaker, screenshareFeeds, layout]);
|
||||||
|
|
||||||
const onFocusTile = useCallback(
|
const onFocusTile = useCallback(
|
||||||
(tiles, focusedTile) => {
|
(tiles, focusedTile) => {
|
||||||
if (layout === "freedom") {
|
if (layout === "freedom") {
|
||||||
return tiles.map((tile) => {
|
return tiles.map((tile) => {
|
||||||
if (tile === focusedTile) {
|
if (tile === focusedTile) {
|
||||||
return { ...tile, presenter: !tile.presenter };
|
return { ...tile, focused: !tile.focused };
|
||||||
}
|
}
|
||||||
|
|
||||||
return tile;
|
return tile;
|
||||||
@@ -156,6 +156,7 @@ export function InCallView({
|
|||||||
key={item.id}
|
key={item.id}
|
||||||
item={item}
|
item={item}
|
||||||
getAvatar={renderAvatar}
|
getAvatar={renderAvatar}
|
||||||
|
showName={items.length > 2 || item.focused}
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { randomString } from "matrix-js-sdk/src/randomstring";
|
|||||||
import { useInteractiveRegistration } from "../auth/useInteractiveRegistration";
|
import { useInteractiveRegistration } from "../auth/useInteractiveRegistration";
|
||||||
import { Form } from "../form/Form";
|
import { Form } from "../form/Form";
|
||||||
import { UserMenuContainer } from "../UserMenuContainer";
|
import { UserMenuContainer } from "../UserMenuContainer";
|
||||||
|
import { generateRandomName } from "../auth/generateRandomName";
|
||||||
|
|
||||||
export function RoomAuthView() {
|
export function RoomAuthView() {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -21,13 +22,20 @@ export function RoomAuthView() {
|
|||||||
(e) => {
|
(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const data = new FormData(e.target);
|
const data = new FormData(e.target);
|
||||||
const userName = data.get("userName");
|
const displayName = data.get("displayName");
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const recaptchaResponse = await execute();
|
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) => {
|
submit().catch((error) => {
|
||||||
@@ -58,10 +66,10 @@ export function RoomAuthView() {
|
|||||||
<Form className={styles.form} onSubmit={onSubmit}>
|
<Form className={styles.form} onSubmit={onSubmit}>
|
||||||
<FieldRow>
|
<FieldRow>
|
||||||
<InputField
|
<InputField
|
||||||
id="userName"
|
id="displayName"
|
||||||
name="userName"
|
name="displayName"
|
||||||
label="Pick a user name"
|
label="Display Name"
|
||||||
placeholder="Pick a user name"
|
placeholder="Display Name"
|
||||||
type="text"
|
type="text"
|
||||||
required
|
required
|
||||||
autoComplete="off"
|
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);
|
const params = new URLSearchParams(search);
|
||||||
return [params.has("simple"), params.getAll("via")];
|
return [params.has("simple"), params.getAll("via")];
|
||||||
}, [search]);
|
}, [search]);
|
||||||
const roomId = maybeRoomId || hash;
|
const roomId = (maybeRoomId || hash || "").toLowerCase();
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <LoadingView />;
|
return <LoadingView />;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export function RoomRedirect() {
|
|||||||
let roomId = pathname;
|
let roomId = pathname;
|
||||||
|
|
||||||
if (pathname.startsWith("/")) {
|
if (pathname.startsWith("/")) {
|
||||||
roomId = roomId.substr(1, roomId.length);
|
roomId = roomId.substring(1, roomId.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!roomId.startsWith("#") && !roomId.startsWith("!")) {
|
if (!roomId.startsWith("#") && !roomId.startsWith("!")) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export default {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ParticipantsTest = () => {
|
export const ParticipantsTest = () => {
|
||||||
const [layout, setLayout] = useVideoGridLayout();
|
const [layout, setLayout] = useVideoGridLayout(false);
|
||||||
const [participantCount, setParticipantCount] = useState(1);
|
const [participantCount, setParticipantCount] = useState(1);
|
||||||
|
|
||||||
const items = useMemo(
|
const items = useMemo(
|
||||||
@@ -60,7 +60,12 @@ export const ParticipantsTest = () => {
|
|||||||
>
|
>
|
||||||
<VideoGrid layout={layout} items={items}>
|
<VideoGrid layout={layout} items={items}>
|
||||||
{({ item, ...rest }) => (
|
{({ 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>
|
</VideoGrid>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12039,6 +12039,11 @@ unique-filename@^1.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
unique-slug "^2.0.0"
|
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:
|
unique-slug@^2.0.0:
|
||||||
version "2.0.2"
|
version "2.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c"
|
resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c"
|
||||||
|
|||||||
Reference in New Issue
Block a user