Finish docs for Welcome page

This commit is contained in:
Marquis Kurt 2020-01-21 10:05:05 -05:00
parent c00bca93bc
commit 8d14ff78df
No known key found for this signature in database
GPG Key ID: 725636D259F5402D
1 changed files with 193 additions and 35 deletions

View File

@ -176,12 +176,27 @@ interface IWelcomeState {
willAddAccount: boolean;
}
/**
* The base class for the Welcome page.
*
* The Welcome page is responsible for handling the registration,
* login, and authorization of accounts into the Hyperspace app.
*/
class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
/**
* The associated Mastodon client to handle logins/authorizations
* with
*/
client: any;
/**
* Construct the state and other components of the Welcome page
* @param props The properties passed onto the page
*/
constructor(props: any) {
super(props);
// Set up our state
this.state = {
proceedToGetCode: false,
user: "",
@ -198,15 +213,21 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
willAddAccount: false
};
// Read the configuration data and update the state
getConfig()
.then((result: any) => {
if (result !== undefined) {
let config: Config = result;
// Warn if the location is dynamic (unexpected behavior)
if (result.location === "dynamic") {
console.warn(
"Redirect URI is set to dynamic, which may affect how sign-in works for some users. Careful!"
);
}
// Reset to mastodon.social if the location is a disallowed
// domain.
if (
inDisallowedDomains(result.registration.defaultInstance)
) {
@ -215,6 +236,8 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
);
result.registration.defaultInstance = "mastodon.social";
}
// Update the state as per the configuration
this.setState({
logoUrl: config.branding
? result.branding.logo
@ -240,6 +263,8 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
});
}
})
// Print an error if the config wasn't found.
.catch(() => {
console.error(
"config.json is missing. If you want to customize Hyperspace, please include config.json"
@ -247,6 +272,10 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
});
}
/**
* Look for any existing logins and tokens before presenting
* the login page
*/
componentDidMount() {
if (localStorage.getItem("login")) {
this.getSavedSession();
@ -257,18 +286,33 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
}
}
/**
* Update the user field in the state
* @param user The string to update the state to
*/
updateUserInfo(user: string) {
this.setState({ user });
}
/**
* Update the auth code in the state
* @param code The authorization code to update the state to
*/
updateAuthCode(code: string) {
this.setState({ authCode: code });
}
/**
* Toggle the visibility of the authorization dialog
*/
toggleAuthDialog() {
this.setState({ openAuthDialog: !this.state.openAuthDialog });
}
/**
* Determine whether the app is ready to open the authorization
* process.
*/
readyForAuth() {
if (localStorage.getItem("baseurl")) {
return true;
@ -277,11 +321,18 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
}
}
/**
* Clear the current access token and base URL
*/
clear() {
localStorage.removeItem("access_token");
localStorage.removeItem("baseurl");
}
/**
* Get the current saved session from the previous login
* attempt and update the state
*/
getSavedSession() {
let loginData = localStorage.getItem("login");
if (loginData) {
@ -295,6 +346,9 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
}
}
/**
* Start the emergency login mode.
*/
startEmergencyLogin() {
if (!this.state.emergencyMode) {
this.createEmergencyLogin();
@ -302,6 +356,11 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
this.toggleAuthDialog();
}
/**
* Start the registration process.
* @returns A URL pointing to the signup page of the base as defined
* in the config's `registerBase` field
*/
startRegistration() {
if (this.state.registerBase) {
return "https://" + this.state.registerBase + "/auth/sign_up";
@ -310,15 +369,33 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
}
}
/**
* Watch the keyboard and start the login procedure if the user
* presses the ENTER/RETURN key
* @param event The keyboard event
*/
watchUsernameField(event: any) {
if (event.keyCode === 13) this.startLogin();
}
/**
* Watch the keyboard and start the emergency login auth procedure
* if the user presses the ENTER/RETURN key
* @param event The keyboard event
*/
watchAuthField(event: any) {
if (event.keyCode === 13) this.authorizeEmergencyLogin();
}
/**
* Get the "logged-in" user by reading the username string
* from the first field on the login page.
* @param user The user string to parse
* @returns The base URL of the user
*/
getLoginUser(user: string) {
// Did the user include "@"? They probably are not from the
// server defined in config
if (user.includes("@")) {
if (this.state.federates) {
let newUser = user;
@ -338,7 +415,10 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
: "mastodon.social")
);
}
} else {
}
// Otherwise, treat them as if they're from the server
else {
let newUser = `${user}@${
this.state.registerBase
? this.state.registerBase
@ -354,70 +434,104 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
}
}
/**
* Check the user string for any errors and then create a client with an
* ID and secret to start the authorization process.
*/
startLogin() {
// Check if we have errored
let error = this.checkForErrors();
// If we didn't, create the Hyperspace app to register onto that Mastodon
// server.
if (!error) {
// Define the app's scopes and base URL
const scopes = "read write follow";
const baseurl = this.getLoginUser(this.state.user);
localStorage.setItem("baseurl", baseurl);
// Create the Hyperspace app
createHyperspaceApp(
this.state.brandName ? this.state.brandName : "Hyperspace",
scopes,
baseurl,
getRedirectAddress(this.state.defaultRedirectAddress)
).then((resp: any) => {
let saveSessionForCrashing: SaveClientSession = {
clientId: resp.clientId,
clientSecret: resp.clientSecret,
authUrl: resp.url,
emergency: false
};
localStorage.setItem(
"login",
JSON.stringify(saveSessionForCrashing)
);
this.setState({
clientId: resp.clientId,
clientSecret: resp.clientSecret,
authUrl: resp.url,
proceedToGetCode: true
)
// If we succeeded, create a login attempt for later reference
.then((resp: any) => {
let saveSessionForCrashing: SaveClientSession = {
clientId: resp.clientId,
clientSecret: resp.clientSecret,
authUrl: resp.url,
emergency: false
};
localStorage.setItem(
"login",
JSON.stringify(saveSessionForCrashing)
);
// Finally, update the state
this.setState({
clientId: resp.clientId,
clientSecret: resp.clientSecret,
authUrl: resp.url,
proceedToGetCode: true
});
});
});
} else {
}
}
/**
* Create an emergency mode login. This is usually initiated when the
* "click-to-authorize" method fails and the user needs to copy and paste
* an authorization code manually.
*/
createEmergencyLogin() {
console.log("Creating an emergency login...");
// Set up the scopes and base URL
const scopes = "read write follow";
const baseurl =
localStorage.getItem("baseurl") ||
this.getLoginUser(this.state.user);
// Register the Mastodon app with the Mastodon server
Mastodon.registerApp(
this.state.brandName ? this.state.brandName : "Hyperspace",
{
scopes: scopes
},
baseurl
).then((appData: any) => {
let saveSessionForCrashing: SaveClientSession = {
clientId: appData.clientId,
clientSecret: appData.clientSecret,
authUrl: appData.url,
emergency: true
};
localStorage.setItem(
"login",
JSON.stringify(saveSessionForCrashing)
);
this.setState({
clientId: appData.clientId,
clientSecret: appData.clientSecret,
authUrl: appData.url
)
// If we succeed, create a login attempt for later reference
.then((appData: any) => {
let saveSessionForCrashing: SaveClientSession = {
clientId: appData.clientId,
clientSecret: appData.clientSecret,
authUrl: appData.url,
emergency: true
};
localStorage.setItem(
"login",
JSON.stringify(saveSessionForCrashing)
);
// Finally, update the state
this.setState({
clientId: appData.clientId,
clientSecret: appData.clientSecret,
authUrl: appData.url
});
});
});
}
/**
* Open the URL to redirect to an authorization sequence from an emergency
* login.
*
* Since Hyperspace reads the auth code from the URL, we need to redirect to
* a URL with the code inside to trigger an auth
*/
authorizeEmergencyLogin() {
let redirAddress =
this.state.defaultRedirectAddress === "desktop"
@ -426,6 +540,9 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
window.location.href = `${redirAddress}/?code=${this.state.authCode}#/`;
}
/**
* Restore a login attempt from a session
*/
resumeLogin() {
let loginData = localStorage.getItem("login");
if (loginData) {
@ -440,10 +557,14 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
}
}
/**
* Check the user input string for any possible errors
*/
checkForErrors(): boolean {
let userInputError = false;
let userInputErrorMessage = "";
// Is the user string blank?
if (this.state.user === "") {
userInputError = true;
userInputErrorMessage = "Username cannot be blank.";
@ -453,6 +574,8 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
if (this.state.user.includes("@")) {
if (this.state.federates && this.state.federates === true) {
let baseUrl = this.state.user.split("@")[1];
// Is the user's domain in the disallowed list?
if (inDisallowedDomains(baseUrl)) {
this.setState({
userInputError: true,
@ -460,6 +583,7 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
});
return true;
} else {
// Are we unable to ping the server?
axios
.get(
"https://instances.social/api/1.0/instances/show?name=" +
@ -506,12 +630,21 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
}
}
/**
* Read the URL and determine whether or not there's an auth code
* in the URL. If there is, try to authorize and get the access
* token for storage.
*/
checkForToken() {
let location = window.location.href;
// Is there an auth code?
if (location.includes("?code=")) {
let code = parseUrl(location).query.code as string;
this.setState({ authorizing: true });
let loginData = localStorage.getItem("login");
// If there's login data, try to fetch an access token
if (loginData) {
let clientLoginSession: SaveClientSession = JSON.parse(
loginData
@ -531,6 +664,8 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
? "hyperspace://hyperspace/app/"
: `https://${window.location.host}`
)
// If we succeeded, store the access token and redirect to the
// main view.
.then((tokenData: any) => {
localStorage.setItem(
"access_token",
@ -538,6 +673,8 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
);
this.redirectToApp();
})
// Otherwise, present an error
.catch((err: Error) => {
this.props.enqueueSnackbar(
`Couldn't authorize ${
@ -565,6 +702,9 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
: this.state.defaultRedirectAddress + "/#/";
}
/**
* Render the title bar for macOS
*/
titlebar() {
const { classes } = this.props;
if (isDarwinApp()) {
@ -580,6 +720,9 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
}
}
/**
* Show the multi-user account panel
*/
showMultiAccount() {
const { classes } = this.props;
return (
@ -635,6 +778,9 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
);
}
/**
* Show the main landing panel
*/
showLanding() {
const { classes } = this.props;
return (
@ -718,6 +864,9 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
);
}
/**
* Show the login auth panel
*/
showLoginAuth() {
const { classes } = this.props;
return (
@ -759,6 +908,9 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
);
}
/**
* Show the emergency login panel
*/
showAuthDialog() {
const { classes } = this.props;
return (
@ -813,6 +965,9 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
);
}
/**
* Show the authorizing panel
*/
showAuthorizationLoader() {
const { classes } = this.props;
return (
@ -833,6 +988,9 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
);
}
/**
* Render the page
*/
render() {
const { classes } = this.props;
return (