Standalone: support running Soapbox FE on a subdomain
This commit is contained in:
parent
1b30468366
commit
0b4b3479ea
15 changed files with 222 additions and 32 deletions
|
@ -16,10 +16,10 @@ export const APP_VERIFY_CREDENTIALS_REQUEST = 'APP_VERIFY_CREDENTIALS_REQUEST';
|
||||||
export const APP_VERIFY_CREDENTIALS_SUCCESS = 'APP_VERIFY_CREDENTIALS_SUCCESS';
|
export const APP_VERIFY_CREDENTIALS_SUCCESS = 'APP_VERIFY_CREDENTIALS_SUCCESS';
|
||||||
export const APP_VERIFY_CREDENTIALS_FAIL = 'APP_VERIFY_CREDENTIALS_FAIL';
|
export const APP_VERIFY_CREDENTIALS_FAIL = 'APP_VERIFY_CREDENTIALS_FAIL';
|
||||||
|
|
||||||
export function createApp(params) {
|
export function createApp(params, baseURL) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch({ type: APP_CREATE_REQUEST, params });
|
dispatch({ type: APP_CREATE_REQUEST, params });
|
||||||
return baseClient().post('/api/v1/apps', params).then(({ data: app }) => {
|
return baseClient(null, baseURL).post('/api/v1/apps', params).then(({ data: app }) => {
|
||||||
dispatch({ type: APP_CREATE_SUCCESS, params, app });
|
dispatch({ type: APP_CREATE_SUCCESS, params, app });
|
||||||
return app;
|
return app;
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
|
|
|
@ -50,6 +50,7 @@ function createAuthApp() {
|
||||||
client_name: sourceCode.displayName,
|
client_name: sourceCode.displayName,
|
||||||
redirect_uris: 'urn:ietf:wg:oauth:2.0:oob',
|
redirect_uris: 'urn:ietf:wg:oauth:2.0:oob',
|
||||||
scopes: 'read write follow push admin',
|
scopes: 'read write follow push admin',
|
||||||
|
website: sourceCode.homepage,
|
||||||
};
|
};
|
||||||
|
|
||||||
return dispatch(createApp(params)).then(app => {
|
return dispatch(createApp(params)).then(app => {
|
||||||
|
@ -88,10 +89,8 @@ function createUserToken(username, password) {
|
||||||
password: password,
|
password: password,
|
||||||
};
|
};
|
||||||
|
|
||||||
return dispatch(obtainOAuthToken(params)).then(token => {
|
return dispatch(obtainOAuthToken(params))
|
||||||
dispatch(authLoggedIn(token));
|
.then(token => dispatch(authLoggedIn(token)));
|
||||||
return token;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,9 +109,8 @@ export function refreshUserToken() {
|
||||||
grant_type: 'refresh_token',
|
grant_type: 'refresh_token',
|
||||||
};
|
};
|
||||||
|
|
||||||
return dispatch(obtainOAuthToken(params)).then(token => {
|
return dispatch(obtainOAuthToken(params))
|
||||||
dispatch(authLoggedIn(token));
|
.then(token => dispatch(authLoggedIn(token)));
|
||||||
});
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,10 +124,7 @@ export function otpVerify(code, mfa_token) {
|
||||||
code: code,
|
code: code,
|
||||||
challenge_type: 'totp',
|
challenge_type: 'totp',
|
||||||
redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
|
redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
|
||||||
}).then(({ data: token }) => {
|
}).then(({ data: token }) => dispatch(authLoggedIn(token)));
|
||||||
dispatch(authLoggedIn(token));
|
|
||||||
return token;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,12 +204,9 @@ export function register(params) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
params.fullname = params.username;
|
params.fullname = params.username;
|
||||||
|
|
||||||
return dispatch(createAppAndToken()).then(() => {
|
return dispatch(createAppAndToken())
|
||||||
return dispatch(createAccount(params));
|
.then(() => dispatch(createAccount(params)))
|
||||||
}).then(({ token }) => {
|
.then(({ token }) => dispatch(authLoggedIn(token)));
|
||||||
dispatch(authLoggedIn(token));
|
|
||||||
return token;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,8 +217,8 @@ export function fetchCaptcha() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function authLoggedIn(token) {
|
export function authLoggedIn(token) {
|
||||||
return {
|
return (dispatch, getState) => {
|
||||||
type: AUTH_LOGGED_IN,
|
dispatch({ type: AUTH_LOGGED_IN, token });
|
||||||
token,
|
return token;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
65
app/soapbox/actions/external_auth.js
Normal file
65
app/soapbox/actions/external_auth.js
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
/**
|
||||||
|
* External Auth: workflow for logging in to remote servers.
|
||||||
|
* @module soapbox/actions/external_auth
|
||||||
|
* @see module:soapbox/actions/auth
|
||||||
|
* @see module:soapbox/actions/apps
|
||||||
|
* @see module:soapbox/actions/oauth
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createApp } from 'soapbox/actions/apps';
|
||||||
|
import { obtainOAuthToken } from 'soapbox/actions/oauth';
|
||||||
|
import { authLoggedIn, verifyCredentials } from 'soapbox/actions/auth';
|
||||||
|
import { parseBaseURL } from 'soapbox/utils/auth';
|
||||||
|
import sourceCode from 'soapbox/utils/code';
|
||||||
|
|
||||||
|
const scopes = 'read write follow push';
|
||||||
|
|
||||||
|
export function createAppAndRedirect(host) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const baseURL = parseBaseURL(host) || parseBaseURL(`https://${host}`);
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
client_name: sourceCode.displayName,
|
||||||
|
redirect_uris: `${window.location.origin}/auth/external`,
|
||||||
|
website: sourceCode.homepage,
|
||||||
|
scopes,
|
||||||
|
};
|
||||||
|
|
||||||
|
return dispatch(createApp(params, baseURL)).then(app => {
|
||||||
|
const { client_id, redirect_uri } = app;
|
||||||
|
|
||||||
|
const query = new URLSearchParams({
|
||||||
|
client_id,
|
||||||
|
redirect_uri,
|
||||||
|
response_type: 'code',
|
||||||
|
scopes,
|
||||||
|
});
|
||||||
|
|
||||||
|
localStorage.setItem('soapbox:external:app', JSON.stringify(app));
|
||||||
|
localStorage.setItem('soapbox:external:baseurl', baseURL);
|
||||||
|
|
||||||
|
window.location.href = `${baseURL}/oauth/authorize?${query.toString()}`;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loginWithCode(code) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const { client_id, client_secret } = JSON.parse(localStorage.getItem('soapbox:external:app'));
|
||||||
|
const baseURL = localStorage.getItem('soapbox:external:baseurl');
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
client_id,
|
||||||
|
client_secret,
|
||||||
|
redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
|
||||||
|
grant_type: 'authorization_code',
|
||||||
|
scope: scopes,
|
||||||
|
code,
|
||||||
|
};
|
||||||
|
|
||||||
|
return dispatch(obtainOAuthToken(params, baseURL))
|
||||||
|
.then(token => dispatch(authLoggedIn(token)))
|
||||||
|
.then(({ access_token }) => dispatch(verifyCredentials(access_token, baseURL)))
|
||||||
|
.then(() => window.location.href = '/');
|
||||||
|
};
|
||||||
|
}
|
|
@ -16,10 +16,10 @@ export const OAUTH_TOKEN_REVOKE_REQUEST = 'OAUTH_TOKEN_REVOKE_REQUEST';
|
||||||
export const OAUTH_TOKEN_REVOKE_SUCCESS = 'OAUTH_TOKEN_REVOKE_SUCCESS';
|
export const OAUTH_TOKEN_REVOKE_SUCCESS = 'OAUTH_TOKEN_REVOKE_SUCCESS';
|
||||||
export const OAUTH_TOKEN_REVOKE_FAIL = 'OAUTH_TOKEN_REVOKE_FAIL';
|
export const OAUTH_TOKEN_REVOKE_FAIL = 'OAUTH_TOKEN_REVOKE_FAIL';
|
||||||
|
|
||||||
export function obtainOAuthToken(params) {
|
export function obtainOAuthToken(params, baseURL) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch({ type: OAUTH_TOKEN_CREATE_REQUEST, params });
|
dispatch({ type: OAUTH_TOKEN_CREATE_REQUEST, params });
|
||||||
return baseClient().post('/oauth/token', params).then(({ data: token }) => {
|
return baseClient(null, baseURL).post('/oauth/token', params).then(({ data: token }) => {
|
||||||
dispatch({ type: OAUTH_TOKEN_CREATE_SUCCESS, params, token });
|
dispatch({ type: OAUTH_TOKEN_CREATE_SUCCESS, params, token });
|
||||||
return token;
|
return token;
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Helmet } from'react-helmet';
|
import { Helmet } from'react-helmet';
|
||||||
import { getSettings } from 'soapbox/actions/settings';
|
import { getSettings } from 'soapbox/actions/settings';
|
||||||
|
import sourceCode from 'soapbox/utils/code';
|
||||||
|
|
||||||
const getNotifTotals = state => {
|
const getNotifTotals = state => {
|
||||||
const notifications = state.getIn(['notifications', 'unread'], 0);
|
const notifications = state.getIn(['notifications', 'unread'], 0);
|
||||||
|
@ -16,7 +17,7 @@ const mapStateToProps = state => {
|
||||||
const settings = getSettings(state);
|
const settings = getSettings(state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
siteTitle: state.getIn(['instance', 'title']),
|
siteTitle: state.getIn(['instance', 'title'], sourceCode.displayName),
|
||||||
unreadCount: getNotifTotals(state),
|
unreadCount: getNotifTotals(state),
|
||||||
demetricator: settings.get('demetricator'),
|
demetricator: settings.get('demetricator'),
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,7 +13,6 @@ import { Switch, BrowserRouter, Route } from 'react-router-dom';
|
||||||
import { ScrollContext } from 'react-router-scroll-4';
|
import { ScrollContext } from 'react-router-scroll-4';
|
||||||
import UI from '../features/ui';
|
import UI from '../features/ui';
|
||||||
// import Introduction from '../features/introduction';
|
// import Introduction from '../features/introduction';
|
||||||
import { fetchCustomEmojis } from '../actions/custom_emojis';
|
|
||||||
import { preload } from '../actions/preload';
|
import { preload } from '../actions/preload';
|
||||||
import { IntlProvider } from 'react-intl';
|
import { IntlProvider } from 'react-intl';
|
||||||
import ErrorBoundary from '../components/error_boundary';
|
import ErrorBoundary from '../components/error_boundary';
|
||||||
|
@ -34,7 +33,6 @@ store.dispatch(preload());
|
||||||
store.dispatch(fetchMe());
|
store.dispatch(fetchMe());
|
||||||
store.dispatch(fetchInstance());
|
store.dispatch(fetchInstance());
|
||||||
store.dispatch(fetchSoapboxConfig());
|
store.dispatch(fetchSoapboxConfig());
|
||||||
store.dispatch(fetchCustomEmojis());
|
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = (state) => {
|
||||||
const me = state.get('me');
|
const me = state.get('me');
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { injectIntl, FormattedMessage, defineMessages } from 'react-intl';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import { SimpleForm, FieldsGroup, TextInput } from 'soapbox/features/forms';
|
||||||
|
import { createAppAndRedirect, loginWithCode } from 'soapbox/actions/external_auth';
|
||||||
|
import LoadingIndicator from 'soapbox/components/loading_indicator';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
instanceLabel: { id: 'login.fields.instance_label', defaultMessage: 'Instance' },
|
||||||
|
instancePlaceholder: { id: 'login.fields.instance_placeholder', defaultMessage: 'example.com' },
|
||||||
|
});
|
||||||
|
|
||||||
|
export default @connect()
|
||||||
|
@injectIntl
|
||||||
|
class ExternalLoginForm extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
state = {
|
||||||
|
host: '',
|
||||||
|
isLoading: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleHostChange = ({ target }) => {
|
||||||
|
this.setState({ host: target.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit = e => {
|
||||||
|
const { dispatch } = this.props;
|
||||||
|
const { host } = this.state;
|
||||||
|
|
||||||
|
this.setState({ isLoading: true });
|
||||||
|
|
||||||
|
dispatch(createAppAndRedirect(host))
|
||||||
|
.then(() => this.setState({ isLoading: false }))
|
||||||
|
.catch(() => this.setState({ isLoading: false }));
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const code = new URLSearchParams(window.location.search).get('code');
|
||||||
|
|
||||||
|
if (code) {
|
||||||
|
this.setState({ code });
|
||||||
|
this.props.dispatch(loginWithCode(code));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { intl } = this.props;
|
||||||
|
const { isLoading, code } = this.state;
|
||||||
|
|
||||||
|
if (code) {
|
||||||
|
return <LoadingIndicator />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SimpleForm onSubmit={this.handleSubmit}>
|
||||||
|
<fieldset disabled={isLoading}>
|
||||||
|
<FieldsGroup>
|
||||||
|
<TextInput
|
||||||
|
label={intl.formatMessage(messages.instanceLabel)}
|
||||||
|
placeholder={intl.formatMessage(messages.instancePlaceholder)}
|
||||||
|
name='host'
|
||||||
|
value={this.state.host}
|
||||||
|
onChange={this.handleHostChange}
|
||||||
|
autoComplete='off'
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</FieldsGroup>
|
||||||
|
</fieldset>
|
||||||
|
<div className='actions'>
|
||||||
|
<button name='button' type='submit' className='btn button button-primary'>
|
||||||
|
<FormattedMessage id='login.log_in' defaultMessage='Log in' />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</SimpleForm>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
11
app/soapbox/features/external_login/index.js
Normal file
11
app/soapbox/features/external_login/index.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import ExternalLoginForm from './components/external_login_form';
|
||||||
|
|
||||||
|
export default class ExternalLoginPage extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <ExternalLoginForm />;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { Switch, Route } from 'react-router-dom';
|
import { Switch, Route, Redirect } from 'react-router-dom';
|
||||||
import NotificationsContainer from 'soapbox/features/ui/containers/notifications_container';
|
import NotificationsContainer from 'soapbox/features/ui/containers/notifications_container';
|
||||||
import ModalContainer from 'soapbox/features/ui/containers/modal_container';
|
import ModalContainer from 'soapbox/features/ui/containers/modal_container';
|
||||||
import Header from './components/header';
|
import Header from './components/header';
|
||||||
|
@ -9,10 +9,23 @@ import Footer from './components/footer';
|
||||||
import LandingPage from '../landing_page';
|
import LandingPage from '../landing_page';
|
||||||
import AboutPage from '../about';
|
import AboutPage from '../about';
|
||||||
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||||
|
import { isPrerendered } from 'soapbox/precheck';
|
||||||
|
|
||||||
|
const validInstance = state => {
|
||||||
|
const v = state.getIn(['instance', 'version']);
|
||||||
|
return v && typeof v === 'string' && v !== '0.0.0';
|
||||||
|
};
|
||||||
|
|
||||||
|
const isStandalone = state => {
|
||||||
|
const hasInstance = validInstance(state);
|
||||||
|
const instanceFetchFailed = state.getIn(['meta', 'instance_fetch_failed']);
|
||||||
|
|
||||||
|
return !isPrerendered && !hasInstance && instanceFetchFailed;
|
||||||
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
instance: state.get('instance'),
|
|
||||||
soapbox: getSoapboxConfig(state),
|
soapbox: getSoapboxConfig(state),
|
||||||
|
standalone: isStandalone(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const wave = (
|
const wave = (
|
||||||
|
@ -24,8 +37,11 @@ const wave = (
|
||||||
class PublicLayout extends ImmutablePureComponent {
|
class PublicLayout extends ImmutablePureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { instance } = this.props;
|
const { standalone } = this.props;
|
||||||
if (instance.isEmpty()) return null;
|
|
||||||
|
if (standalone) {
|
||||||
|
return <Redirect to='/auth/external' />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='public-layout'>
|
<div className='public-layout'>
|
||||||
|
|
|
@ -43,6 +43,7 @@ import { isStaff, isAdmin } from 'soapbox/utils/accounts';
|
||||||
import ProfileHoverCard from 'soapbox/components/profile_hover_card';
|
import ProfileHoverCard from 'soapbox/components/profile_hover_card';
|
||||||
import { getAccessToken } from 'soapbox/utils/auth';
|
import { getAccessToken } from 'soapbox/utils/auth';
|
||||||
import { getFeatures } from 'soapbox/utils/features';
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
|
import { fetchCustomEmojis } from 'soapbox/actions/custom_emojis';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Status,
|
Status,
|
||||||
|
@ -79,6 +80,7 @@ import {
|
||||||
// GroupCreate,
|
// GroupCreate,
|
||||||
// GroupEdit,
|
// GroupEdit,
|
||||||
LoginPage,
|
LoginPage,
|
||||||
|
ExternalLogin,
|
||||||
Preferences,
|
Preferences,
|
||||||
EditProfile,
|
EditProfile,
|
||||||
SoapboxConfig,
|
SoapboxConfig,
|
||||||
|
@ -195,6 +197,7 @@ class SwitchingColumnsArea extends React.PureComponent {
|
||||||
<Switch>
|
<Switch>
|
||||||
<WrappedRoute path='/auth/sign_in' component={LoginPage} publicRoute exact />
|
<WrappedRoute path='/auth/sign_in' component={LoginPage} publicRoute exact />
|
||||||
<WrappedRoute path='/auth/reset_password' component={PasswordReset} publicRoute exact />
|
<WrappedRoute path='/auth/reset_password' component={PasswordReset} publicRoute exact />
|
||||||
|
<WrappedRoute path='/auth/external' component={ExternalLogin} publicRoute exact />
|
||||||
<WrappedRoute path='/auth/edit' page={DefaultPage} component={SecurityForm} exact />
|
<WrappedRoute path='/auth/edit' page={DefaultPage} component={SecurityForm} exact />
|
||||||
<WrappedRoute path='/auth/mfa' page={DefaultPage} component={MfaForm} exact />
|
<WrappedRoute path='/auth/mfa' page={DefaultPage} component={MfaForm} exact />
|
||||||
|
|
||||||
|
@ -458,6 +461,8 @@ class UI extends React.PureComponent {
|
||||||
|
|
||||||
setTimeout(() => this.props.dispatch(fetchScheduledStatuses()), 900);
|
setTimeout(() => this.props.dispatch(fetchScheduledStatuses()), 900);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.props.dispatch(fetchCustomEmojis());
|
||||||
this.connectStreaming();
|
this.connectStreaming();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -588,7 +593,10 @@ class UI extends React.PureComponent {
|
||||||
const { draggingOver, mobile } = this.state;
|
const { draggingOver, mobile } = this.state;
|
||||||
const { intl, children, location, dropdownMenuIsOpen, me } = this.props;
|
const { intl, children, location, dropdownMenuIsOpen, me } = this.props;
|
||||||
|
|
||||||
if (me === null || !streamingUrl) return null;
|
// Wait for login to succeed or fail
|
||||||
|
if (me === null) return null;
|
||||||
|
// If login didn't fail, wait for streaming to become available
|
||||||
|
if (me !== false && !streamingUrl) return null;
|
||||||
|
|
||||||
const handlers = me ? {
|
const handlers = me ? {
|
||||||
help: this.handleHotkeyToggleHelp,
|
help: this.handleHotkeyToggleHelp,
|
||||||
|
|
|
@ -170,6 +170,10 @@ export function LoginPage() {
|
||||||
return import(/* webpackChunkName: "features/auth_login" */'../../auth_login/components/login_page');
|
return import(/* webpackChunkName: "features/auth_login" */'../../auth_login/components/login_page');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ExternalLogin() {
|
||||||
|
return import(/* webpackChunkName: "features/external_login" */'../../external_login');
|
||||||
|
}
|
||||||
|
|
||||||
export function Preferences() {
|
export function Preferences() {
|
||||||
return import(/* webpackChunkName: "features/preferences" */'../../preferences');
|
return import(/* webpackChunkName: "features/preferences" */'../../preferences');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import './wdyr';
|
import './wdyr';
|
||||||
|
import './precheck';
|
||||||
// FIXME: Push notifications are temporarily removed
|
// FIXME: Push notifications are temporarily removed
|
||||||
// import * as registerPushNotifications from './actions/push_notifications';
|
// import * as registerPushNotifications from './actions/push_notifications';
|
||||||
// import { default as Soapbox, store } from './containers/soapbox';
|
// import { default as Soapbox, store } from './containers/soapbox';
|
||||||
|
|
11
app/soapbox/precheck.js
Normal file
11
app/soapbox/precheck.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
/**
|
||||||
|
* Precheck: information about the site before anything renders.
|
||||||
|
* @module soapbox/precheck
|
||||||
|
*/
|
||||||
|
|
||||||
|
const hasTitle = Boolean(document.querySelector('title'));
|
||||||
|
|
||||||
|
const hasPrerenderPleroma = Boolean(document.getElementById('initial-results'));
|
||||||
|
const hasPrerenderMastodon = Boolean(document.getElementById('initial-state'));
|
||||||
|
|
||||||
|
export const isPrerendered = hasTitle || hasPrerenderPleroma || hasPrerenderMastodon;
|
|
@ -1,6 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { ME_FETCH_SUCCESS, ME_PATCH_SUCCESS } from 'soapbox/actions/me';
|
import { ME_FETCH_SUCCESS, ME_PATCH_SUCCESS } from 'soapbox/actions/me';
|
||||||
|
import { INSTANCE_FETCH_FAIL } from 'soapbox/actions/instance';
|
||||||
import { Map as ImmutableMap, fromJS } from 'immutable';
|
import { Map as ImmutableMap, fromJS } from 'immutable';
|
||||||
|
|
||||||
const initialState = ImmutableMap();
|
const initialState = ImmutableMap();
|
||||||
|
@ -19,6 +20,8 @@ export default function meta(state = initialState, action) {
|
||||||
case ME_FETCH_SUCCESS:
|
case ME_FETCH_SUCCESS:
|
||||||
case ME_PATCH_SUCCESS:
|
case ME_PATCH_SUCCESS:
|
||||||
return importAccount(state, fromJS(action.me));
|
return importAccount(state, fromJS(action.me));
|
||||||
|
case INSTANCE_FETCH_FAIL:
|
||||||
|
return state.set('instance_fetch_failed', true);
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,4 +37,5 @@ module.exports = {
|
||||||
url: pkg.repository.url,
|
url: pkg.repository.url,
|
||||||
repository: shortRepoName(pkg.repository.url),
|
repository: shortRepoName(pkg.repository.url),
|
||||||
version: version(pkg),
|
version: version(pkg),
|
||||||
|
homepage: pkg.homepage,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue