2019-04-07 23:25:39 +02:00
|
|
|
import React, { Component } from 'react';
|
2019-04-09 22:58:45 +02:00
|
|
|
import {withStyles, Paper, Typography, Button, TextField, Fade, Link, CircularProgress, Tooltip} from '@material-ui/core';
|
2019-04-07 23:25:39 +02:00
|
|
|
import {styles} from './WelcomePage.styles';
|
|
|
|
import Mastodon from 'megalodon';
|
|
|
|
import {SaveClientSession} from '../types/SessionData';
|
|
|
|
import { createHyperspaceApp } from '../utilities/login';
|
|
|
|
import {parseUrl} from 'query-string';
|
2019-04-08 00:31:18 +02:00
|
|
|
import { getConfig } from '../utilities/settings';
|
2019-04-09 22:53:00 +02:00
|
|
|
import axios from 'axios';
|
2019-04-07 23:25:39 +02:00
|
|
|
|
|
|
|
interface IWelcomeState {
|
|
|
|
logoUrl?: string;
|
|
|
|
backgroundUrl?: string;
|
|
|
|
brandName?: string;
|
|
|
|
registerBase?: string;
|
|
|
|
federates?: boolean;
|
|
|
|
wantsToLogin: boolean;
|
|
|
|
user: string;
|
|
|
|
userInputError: boolean;
|
2019-04-09 22:53:00 +02:00
|
|
|
userInputErrorMessage: string;
|
2019-04-07 23:25:39 +02:00
|
|
|
clientId?: string;
|
|
|
|
clientSecret?: string;
|
|
|
|
authUrl?: string;
|
|
|
|
foundSavedLogin: boolean;
|
|
|
|
authority: boolean;
|
2019-04-12 20:26:27 +02:00
|
|
|
license?: string;
|
|
|
|
repo?: string;
|
2019-04-20 20:59:32 +02:00
|
|
|
defaultRedirectAddress: string;
|
2019-04-07 23:25:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
class WelcomePage extends Component<any, IWelcomeState> {
|
|
|
|
|
|
|
|
client: any;
|
|
|
|
|
|
|
|
constructor(props: any) {
|
|
|
|
super(props);
|
|
|
|
|
|
|
|
this.state = {
|
|
|
|
wantsToLogin: false,
|
|
|
|
user: "",
|
|
|
|
userInputError: false,
|
|
|
|
foundSavedLogin: false,
|
2019-04-09 22:53:00 +02:00
|
|
|
authority: false,
|
2019-04-20 20:59:32 +02:00
|
|
|
userInputErrorMessage: '',
|
|
|
|
defaultRedirectAddress: ''
|
2019-04-07 23:25:39 +02:00
|
|
|
}
|
|
|
|
|
2019-04-08 00:31:18 +02:00
|
|
|
getConfig().then((result: any) => {
|
2019-04-20 20:59:32 +02:00
|
|
|
if (result.location === "dynamic") {
|
|
|
|
console.warn("Recirect URI is set to dyanmic, which may affect how sign-in works for some users. Careful!");
|
|
|
|
}
|
2019-04-07 23:25:39 +02:00
|
|
|
this.setState({
|
|
|
|
logoUrl: result.branding? result.branding.logo: "logo.png",
|
|
|
|
backgroundUrl: result.branding? result.branding.background: "background.png",
|
|
|
|
brandName: result.branding? result.branding.name: "Hyperspace",
|
|
|
|
registerBase: result.registration? result.registration.defaultInstance: "",
|
2019-04-12 20:26:27 +02:00
|
|
|
federates: result.federated? result.federated === "true": true,
|
|
|
|
license: result.license.url,
|
2019-04-20 20:59:32 +02:00
|
|
|
repo: result.repository,
|
|
|
|
defaultRedirectAddress: result.location != "dynamic"? result.location: `https://${window.location.host}`
|
2019-04-07 23:25:39 +02:00
|
|
|
});
|
|
|
|
}).catch(() => {
|
2019-04-20 20:59:32 +02:00
|
|
|
console.error('config.json is missing. If you want to customize Hyperspace, please include config.json');
|
2019-04-07 23:25:39 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
if (localStorage.getItem("login")) {
|
|
|
|
this.setState({
|
|
|
|
foundSavedLogin: true
|
|
|
|
})
|
|
|
|
this.getSavedSession();
|
|
|
|
this.checkForToken();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
checkForToken() {
|
|
|
|
let location = window.location.href;
|
|
|
|
if (location.includes("?code=")) {
|
|
|
|
let code = parseUrl(location).query.code as string;
|
|
|
|
this.setState({ authority: true });
|
|
|
|
let loginData = localStorage.getItem("login");
|
|
|
|
|
|
|
|
if (loginData) {
|
|
|
|
let clientLoginSession: SaveClientSession = JSON.parse(loginData);
|
|
|
|
console.log(clientLoginSession);
|
|
|
|
Mastodon.fetchAccessToken(
|
|
|
|
clientLoginSession.clientId,
|
|
|
|
clientLoginSession.clientSecret,
|
|
|
|
code,
|
|
|
|
(localStorage.getItem("baseurl") as string),
|
|
|
|
`https://${window.location.host}`,
|
|
|
|
).then((tokenData: any) => {
|
|
|
|
localStorage.setItem("access_token", tokenData.access_token);
|
|
|
|
window.location.href=`https://${window.location.host}/#/`;
|
|
|
|
}).catch((err: Error) => {
|
|
|
|
console.log(err.message);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
updateUserInfo(user: string) {
|
|
|
|
this.setState({ user });
|
|
|
|
}
|
|
|
|
|
|
|
|
getLoginUser(user: string) {
|
|
|
|
if (user.includes("@")) {
|
|
|
|
let newUser = user;
|
|
|
|
this.setState({ user: newUser })
|
|
|
|
return "https://" + newUser.split("@")[1];
|
|
|
|
} else {
|
|
|
|
let newUser = `${user}@${this.state.registerBase? this.state.registerBase: "mastodon.social"}`;
|
|
|
|
this.setState({ user: newUser });
|
|
|
|
return "https://" + (this.state.registerBase? this.state.registerBase: "mastodon.social");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
startRegistration() {
|
|
|
|
if (this.state.registerBase) {
|
|
|
|
return "https://" + this.state.registerBase + "/auth/sign_up";
|
|
|
|
} else {
|
|
|
|
return "https://joinmastodon.org/#getting-started";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
startLogin() {
|
2019-04-12 20:26:27 +02:00
|
|
|
let error = this.checkForErrors();
|
|
|
|
if (!error) {
|
2019-04-07 23:25:39 +02:00
|
|
|
const scopes = 'read write follow';
|
|
|
|
const baseurl = this.getLoginUser(this.state.user);
|
|
|
|
localStorage.setItem("baseurl", baseurl);
|
2019-04-20 20:59:32 +02:00
|
|
|
createHyperspaceApp(scopes, baseurl, this.state.defaultRedirectAddress).then((resp: any) => {
|
2019-04-07 23:25:39 +02:00
|
|
|
let saveSessionForCrashing: SaveClientSession = {
|
|
|
|
clientId: resp.clientId,
|
|
|
|
clientSecret: resp.clientSecret,
|
|
|
|
authUrl: resp.url
|
|
|
|
}
|
|
|
|
localStorage.setItem("login", JSON.stringify(saveSessionForCrashing));
|
|
|
|
this.setState({
|
|
|
|
clientId: resp.clientId,
|
|
|
|
clientSecret: resp.clientSecret,
|
|
|
|
authUrl: resp.url,
|
|
|
|
wantsToLogin: true
|
|
|
|
})
|
|
|
|
})
|
|
|
|
} else {
|
2019-04-12 20:26:27 +02:00
|
|
|
|
2019-04-07 23:25:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
resumeLogin() {
|
|
|
|
let loginData = localStorage.getItem("login");
|
|
|
|
if (loginData) {
|
|
|
|
let session: SaveClientSession = JSON.parse(loginData);
|
|
|
|
this.setState({
|
|
|
|
clientId: session.clientId,
|
|
|
|
clientSecret: session.clientSecret,
|
|
|
|
authUrl: session.authUrl,
|
|
|
|
wantsToLogin: true
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
getSavedSession() {
|
|
|
|
let loginData = localStorage.getItem("login");
|
|
|
|
if (loginData) {
|
|
|
|
let session: SaveClientSession = JSON.parse(loginData);
|
|
|
|
this.setState({
|
|
|
|
clientId: session.clientId,
|
|
|
|
clientSecret: session.clientSecret,
|
|
|
|
authUrl: session.authUrl
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-12 20:26:27 +02:00
|
|
|
checkForErrors(): boolean {
|
2019-04-09 22:53:00 +02:00
|
|
|
let userInputError = false;
|
|
|
|
let userInputErrorMessage = "";
|
|
|
|
|
|
|
|
if (this.state.user === "") {
|
|
|
|
userInputError = true;
|
2019-04-12 20:26:27 +02:00
|
|
|
userInputErrorMessage = "Username cannot be blank.";
|
2019-04-09 22:53:00 +02:00
|
|
|
this.setState({ userInputError, userInputErrorMessage });
|
2019-04-12 20:26:27 +02:00
|
|
|
return true;
|
2019-04-09 22:53:00 +02:00
|
|
|
} else {
|
|
|
|
if (this.state.user.includes("@")) {
|
|
|
|
let baseUrl = this.state.user.split("@")[1];
|
|
|
|
axios.get("https://" + baseUrl + "/api/v1/timelines/public").catch((err: Error) => {
|
|
|
|
let userInputError = true;
|
2019-04-12 20:26:27 +02:00
|
|
|
let userInputErrorMessage = "Instance name is invalid.";
|
2019-04-09 22:53:00 +02:00
|
|
|
this.setState({ userInputError, userInputErrorMessage });
|
2019-04-12 20:26:27 +02:00
|
|
|
return true;
|
2019-04-09 22:53:00 +02:00
|
|
|
})
|
|
|
|
} else {
|
|
|
|
this.setState({ userInputError, userInputErrorMessage });
|
2019-04-12 20:26:27 +02:00
|
|
|
return false;
|
2019-04-09 22:53:00 +02:00
|
|
|
}
|
2019-04-12 20:26:27 +02:00
|
|
|
return false;
|
2019-04-09 22:53:00 +02:00
|
|
|
}
|
|
|
|
|
2019-04-07 23:25:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
readyForAuth() {
|
|
|
|
if (localStorage.getItem('baseurl')) {
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
showLanding() {
|
|
|
|
const { classes } = this.props;
|
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<Typography variant="h5">Sign in</Typography>
|
|
|
|
<Typography>with your Mastodon account</Typography>
|
|
|
|
<div className={classes.middlePadding}/>
|
|
|
|
<TextField
|
|
|
|
variant="outlined"
|
|
|
|
label="Account name"
|
|
|
|
fullWidth
|
|
|
|
placeholder="example@mastodon.host"
|
|
|
|
onChange={(event) => this.updateUserInfo(event.target.value)}
|
|
|
|
error={this.state.userInputError}
|
|
|
|
onBlur={() => this.checkForErrors()}
|
|
|
|
></TextField>
|
2019-04-09 22:53:00 +02:00
|
|
|
{
|
|
|
|
this.state.userInputError? <Typography color="error">{this.state.userInputErrorMessage}</Typography> : null
|
|
|
|
}
|
2019-04-12 20:26:27 +02:00
|
|
|
<br/>
|
2019-04-07 23:25:39 +02:00
|
|
|
{
|
|
|
|
this.state.registerBase? <Typography variant="caption">If you are from <b>{this.state.registerBase? this.state.registerBase: "noinstance"}</b>, sign in with your username.</Typography>: null
|
|
|
|
}
|
|
|
|
<br/>
|
|
|
|
{
|
|
|
|
this.state.foundSavedLogin?
|
|
|
|
<Typography>
|
|
|
|
Signing in from a previous session? <Link onClick={() => this.resumeLogin()}>Continue login</Link>.
|
|
|
|
</Typography>: null
|
|
|
|
}
|
|
|
|
|
|
|
|
<div className={classes.middlePadding}/>
|
|
|
|
<div style={{ display: "flex" }}>
|
2019-04-12 20:26:27 +02:00
|
|
|
<Tooltip title="Create account on site">
|
2019-04-09 22:58:45 +02:00
|
|
|
<Button
|
|
|
|
color="primary"
|
|
|
|
href={this.startRegistration()}
|
|
|
|
target="_blank"
|
|
|
|
rel="noreferrer"
|
|
|
|
>Create account</Button>
|
|
|
|
</Tooltip>
|
2019-04-07 23:25:39 +02:00
|
|
|
<div className={classes.flexGrow}/>
|
2019-04-12 20:26:27 +02:00
|
|
|
<Tooltip title="Continue sign-in">
|
|
|
|
<Button color="primary" variant="contained" onClick={() => this.startLogin()}>Next</Button>
|
|
|
|
</Tooltip>
|
|
|
|
|
2019-04-07 23:25:39 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
showLoginAuth() {
|
|
|
|
const { classes } = this.props;
|
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<Typography variant="h5">Howdy, {this.state.user? this.state.user.split("@")[0]: "user"}</Typography>
|
|
|
|
<Typography>To continue, finish signing in on your instance's website and authorize Hyperspace.</Typography>
|
|
|
|
<div className={classes.middlePadding}/>
|
|
|
|
<div style={{ display: "flex" }}>
|
|
|
|
<div className={classes.flexGrow}/>
|
|
|
|
<Button
|
|
|
|
color="primary"
|
|
|
|
variant="contained"
|
|
|
|
size="large"
|
|
|
|
href={this.state.authUrl? this.state.authUrl: ""}
|
|
|
|
>
|
|
|
|
Authorize
|
|
|
|
</Button>
|
|
|
|
<div className={classes.flexGrow}/>
|
|
|
|
</div>
|
|
|
|
<div className={classes.middlePadding}/>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
showAuthority() {
|
|
|
|
const { classes } = this.props;
|
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<Typography variant="h5">Authorizing</Typography>
|
|
|
|
<Typography>Please wait while Hyperspace authorizes with Mastodon. This shouldn't take long...</Typography>
|
|
|
|
<div className={classes.middlePadding}/>
|
|
|
|
<div style={{ display: "flex" }}>
|
|
|
|
<div className={classes.flexGrow}/>
|
|
|
|
<CircularProgress/>
|
|
|
|
<div className={classes.flexGrow}/>
|
|
|
|
</div>
|
|
|
|
<div className={classes.middlePadding}/>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
const { classes } = this.props;
|
|
|
|
return (
|
|
|
|
<div className={classes.root} style={{ backgroundImage: `url(${this.state !== null? this.state.backgroundUrl: "background.png"})`}}>
|
|
|
|
<Paper className={classes.paper}>
|
2019-04-09 22:53:00 +02:00
|
|
|
<img className={classes.logo} alt={this.state? this.state.brandName: "Hyperspace"} src={this.state? this.state.logoUrl: "logo.png"}/>
|
2019-04-07 23:25:39 +02:00
|
|
|
<br/>
|
|
|
|
<Fade in={true}>
|
|
|
|
{
|
|
|
|
this.state.authority?
|
|
|
|
this.showAuthority():
|
|
|
|
this.state.wantsToLogin?
|
|
|
|
this.showLoginAuth():
|
|
|
|
this.showLanding()
|
|
|
|
}
|
|
|
|
</Fade>
|
|
|
|
<br/>
|
|
|
|
<Typography variant="caption">
|
|
|
|
© 2019 <Link href="https://hyperspace.marquiskurt.net" target="_blank" rel="noreferrer">Hyperspace</Link> developers. All rights reserved.
|
|
|
|
</Typography>
|
|
|
|
<Typography variant="caption">
|
2019-04-12 20:26:27 +02:00
|
|
|
{ this.state.repo? <span><Link href={this.state.repo? this.state.repo: "https://github.com/hyperspacedev"} target="_blank" rel="noreferrer">Source code</Link> | </span>: null}<Link href={this.state.license? this.state.license: "https://www.apache.org/licenses/LICENSE-2.0"} target="_blank" rel="noreferrer">License</Link> | <Link href="https://github.com/hyperspacedev/hyperspace/issues/new" target="_blank" rel="noreferrer">File an Issue</Link>
|
2019-04-07 23:25:39 +02:00
|
|
|
</Typography>
|
|
|
|
</Paper>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default withStyles(styles)(WelcomePage);
|