Finish docs for Welcome page
This commit is contained in:
parent
c00bca93bc
commit
8d14ff78df
|
@ -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 (
|
||||
|
|
Loading…
Reference in New Issue