diff --git a/app/soapbox/actions/auth.js b/app/soapbox/actions/auth.js index 5c0fc467a..00f9c606d 100644 --- a/app/soapbox/actions/auth.js +++ b/app/soapbox/actions/auth.js @@ -249,10 +249,12 @@ export function logOut(intl) { const account = getLoggedInAccount(state); const standalone = isStandalone(state); + if (!account) return dispatch(noOp); + const params = { client_id: state.getIn(['auth', 'app', 'client_id']), client_secret: state.getIn(['auth', 'app', 'client_secret']), - token: state.getIn(['auth', 'users', account.get('url'), 'access_token']), + token: state.getIn(['auth', 'users', account.url, 'access_token']), }; return Promise.all([ diff --git a/app/soapbox/actions/external_auth.js b/app/soapbox/actions/external_auth.js index 8bf0bba3f..4f389667f 100644 --- a/app/soapbox/actions/external_auth.js +++ b/app/soapbox/actions/external_auth.js @@ -42,7 +42,7 @@ function createExternalApp(instance, baseURL) { const params = { client_name: sourceCode.displayName, - redirect_uris: `${window.location.origin}/auth/external`, + redirect_uris: `${window.location.origin}/login/external`, website: sourceCode.homepage, scopes, }; diff --git a/app/soapbox/actions/soapbox.js b/app/soapbox/actions/soapbox.js index 0b18c9bc1..318050219 100644 --- a/app/soapbox/actions/soapbox.js +++ b/app/soapbox/actions/soapbox.js @@ -47,21 +47,34 @@ export function rememberSoapboxConfig(host) { }; } -export function fetchSoapboxConfig(host) { +export function fetchFrontendConfigurations() { return (dispatch, getState) => { - api(getState).get('/api/pleroma/frontend_configurations').then(response => { - if (response.data.soapbox_fe) { - dispatch(importSoapboxConfig(response.data.soapbox_fe, host)); - } else { - dispatch(fetchSoapboxJson(host)); - } - }).catch(error => { - dispatch(fetchSoapboxJson(host)); - }); + return api(getState) + .get('/api/pleroma/frontend_configurations') + .then(({ data }) => data); }; } -// Tries to remember the config from browser storage before fetching it +/** Conditionally fetches Soapbox config depending on backend features */ +export function fetchSoapboxConfig(host) { + return (dispatch, getState) => { + const features = getFeatures(getState().instance); + + if (features.frontendConfigurations) { + return dispatch(fetchFrontendConfigurations()).then(data => { + if (data.soapbox_fe) { + dispatch(importSoapboxConfig(data.soapbox_fe, host)); + } else { + dispatch(fetchSoapboxJson(host)); + } + }); + } else { + return dispatch(fetchSoapboxJson(host)); + } + }; +} + +/** Tries to remember the config from browser storage before fetching it */ export function loadSoapboxConfig() { return (dispatch, getState) => { const host = getHost(getState()); diff --git a/app/soapbox/components/error_boundary.js b/app/soapbox/components/error_boundary.tsx similarity index 53% rename from app/soapbox/components/error_boundary.js rename to app/soapbox/components/error_boundary.tsx index 9489b0e75..b07bb2733 100644 --- a/app/soapbox/components/error_boundary.js +++ b/app/soapbox/components/error_boundary.tsx @@ -1,43 +1,58 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; -import { NODE_ENV } from 'soapbox/build_config'; +import { getSoapboxConfig } from 'soapbox/actions/soapbox'; +import BuildConfig from 'soapbox/build_config'; import { Text, Stack } from 'soapbox/components/ui'; +import SvgIcon from 'soapbox/components/ui/icon/svg-icon'; import { captureException } from 'soapbox/monitoring'; import sourceCode from 'soapbox/utils/code'; -import { getSoapboxConfig } from '../actions/soapbox'; +import type { RootState } from 'soapbox/store'; -const mapStateToProps = (state) => { - const soapboxConfig = getSoapboxConfig(state); +const goHome = () => location.href = '/'; + +/** Unregister the ServiceWorker */ +// https://stackoverflow.com/a/49771828/8811886 +const unregisterSw = async() => { + if (!navigator.serviceWorker) return; + const registrations = await navigator.serviceWorker.getRegistrations(); + const unregisterAll = registrations.map(r => r.unregister()); + await Promise.all(unregisterAll); +}; + +const mapStateToProps = (state: RootState) => { + const { links, logo } = getSoapboxConfig(state); return { siteTitle: state.instance.title, - helpLink: soapboxConfig.getIn(['links', 'help']), - supportLink: soapboxConfig.getIn(['links', 'support']), - statusLink: soapboxConfig.getIn(['links', 'status']), + logo, + links, }; }; -@connect(mapStateToProps) -class ErrorBoundary extends React.PureComponent { +type Props = ReturnType; - static propTypes = { - children: PropTypes.node, - siteTitle: PropTypes.string, - supportLink: PropTypes.string, - helpLink: PropTypes.string, - statusLink: PropTypes.string, - }; +type State = { + hasError: boolean, + error: any, + componentStack: any, + browser?: Bowser.Parser.Parser, +} - state = { +class ErrorBoundary extends React.PureComponent { + + state: State = { hasError: false, + error: undefined, componentStack: undefined, + browser: undefined, } - componentDidCatch(error, info) { + textarea: HTMLTextAreaElement | null = null; + + componentDidCatch(error: any, info: any): void { captureException(error); this.setState({ @@ -55,11 +70,11 @@ class ErrorBoundary extends React.PureComponent { .catch(() => {}); } - setTextareaRef = c => { + setTextareaRef: React.RefCallback = c => { this.textarea = c; } - handleCopy = e => { + handleCopy: React.MouseEventHandler = () => { if (!this.textarea) return; this.textarea.select(); @@ -68,25 +83,30 @@ class ErrorBoundary extends React.PureComponent { document.execCommand('copy'); } - getErrorText = () => { + getErrorText = (): string => { const { error, componentStack } = this.state; return error + componentStack; } - clearCookies = e => { + clearCookies: React.MouseEventHandler = (e) => { localStorage.clear(); sessionStorage.clear(); + + if ('serviceWorker' in navigator) { + e.preventDefault(); + unregisterSw().then(goHome).catch(goHome); + } } render() { const { browser, hasError } = this.state; - const { children, siteTitle, helpLink, statusLink, supportLink } = this.props; + const { children, siteTitle, logo, links } = this.props; if (!hasError) { return children; } - const isProduction = NODE_ENV === 'production'; + const isProduction = BuildConfig.NODE_ENV === 'production'; const errorText = this.getErrorText(); @@ -95,7 +115,11 @@ class ErrorBoundary extends React.PureComponent {
- {siteTitle} + {logo ? ( + {siteTitle} + ) : ( + + )}
@@ -105,14 +129,18 @@ class ErrorBoundary extends React.PureComponent {

- We're sorry for the interruption. If the problem persists, please reach out to our support team. You - may also try to - - - {' ' }(this will log you out). + + + + ) }} + />

@@ -144,7 +172,7 @@ class ErrorBoundary extends React.PureComponent { {browser && ( - Browser + {browser.getBrowserName()} {browser.getBrowserVersion()} )} @@ -155,28 +183,28 @@ class ErrorBoundary extends React.PureComponent {