basic frontend implementation of new token system
This commit is contained in:
		
							
								
								
									
										240
									
								
								src/App.tsx
									
									
									
									
									
								
							
							
						
						
									
										240
									
								
								src/App.tsx
									
									
									
									
									
								
							@@ -9,21 +9,17 @@ import style from './App.module.css';
 | 
			
		||||
 | 
			
		||||
import SettingsPage from './pages/SettingsPage/SettingsPage';
 | 
			
		||||
import CategoryPage from './pages/CategoryPage/CategoryPage';
 | 
			
		||||
import {APINode, callAPI} from './utils/Api';
 | 
			
		||||
 | 
			
		||||
import {BrowserRouter as Router, NavLink, Route, Switch} from 'react-router-dom';
 | 
			
		||||
import {NavLink, Route, Switch, useRouteMatch} from 'react-router-dom';
 | 
			
		||||
import Player from './pages/Player/Player';
 | 
			
		||||
import ActorOverviewPage from './pages/ActorOverviewPage/ActorOverviewPage';
 | 
			
		||||
import ActorPage from './pages/ActorPage/ActorPage';
 | 
			
		||||
import {SettingsTypes} from './types/ApiTypes';
 | 
			
		||||
import AuthenticationPage from './pages/AuthenticationPage/AuthenticationPage';
 | 
			
		||||
import TVShowPage from './pages/TVShowPage/TVShowPage';
 | 
			
		||||
import TVPlayer from './pages/TVShowPage/TVPlayer';
 | 
			
		||||
import {CookieTokenStore} from './utils/TokenStore/CookieTokenStore';
 | 
			
		||||
import {token} from './utils/TokenHandler';
 | 
			
		||||
import {LoginContextProvider} from './utils/context/LoginContextProvider';
 | 
			
		||||
 | 
			
		||||
interface state {
 | 
			
		||||
    password: boolean | null; // null if uninitialized - true if pwd needed false if not needed
 | 
			
		||||
    mediacentername: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -34,100 +30,63 @@ class App extends React.Component<{}, state> {
 | 
			
		||||
    constructor(props: {}) {
 | 
			
		||||
        super(props);
 | 
			
		||||
 | 
			
		||||
        token.init(new CookieTokenStore());
 | 
			
		||||
 | 
			
		||||
        let pwdneeded: boolean | null = null;
 | 
			
		||||
 | 
			
		||||
        if (token.apiTokenValid()) {
 | 
			
		||||
            pwdneeded = false;
 | 
			
		||||
        } else {
 | 
			
		||||
            token.refreshAPIToken((err) => {
 | 
			
		||||
                if (err === 'invalid_client') {
 | 
			
		||||
                    this.setState({password: true});
 | 
			
		||||
                } else if (err === '') {
 | 
			
		||||
                    this.setState({password: false});
 | 
			
		||||
                } else {
 | 
			
		||||
                    console.log('unimplemented token error: ' + err);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.state = {
 | 
			
		||||
            mediacentername: 'OpenMediaCenter',
 | 
			
		||||
            password: pwdneeded
 | 
			
		||||
            mediacentername: 'OpenMediaCenter'
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // force an update on theme change
 | 
			
		||||
        GlobalInfos.onThemeChange(() => {
 | 
			
		||||
            this.forceUpdate();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // set the hook to load passwordfield on global func call
 | 
			
		||||
        GlobalInfos.loadPasswordPage = (callback?: () => void): void => {
 | 
			
		||||
            // try refreshing the token
 | 
			
		||||
            token.refreshAPIToken((err) => {
 | 
			
		||||
                if (err !== '') {
 | 
			
		||||
                    this.setState({password: true});
 | 
			
		||||
                } else {
 | 
			
		||||
                    // call callback if request was successful
 | 
			
		||||
                    if (callback) {
 | 
			
		||||
                        callback();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }, true);
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    initialAPICall(): void {
 | 
			
		||||
        // this is the first api call so if it fails we know there is no connection to backend
 | 
			
		||||
        callAPI(APINode.Settings, {action: 'loadInitialData'}, (result: SettingsTypes.initialApiCallData) => {
 | 
			
		||||
            // set theme
 | 
			
		||||
            GlobalInfos.enableDarkTheme(result.DarkMode);
 | 
			
		||||
 | 
			
		||||
            GlobalInfos.setVideoPaths(result.VideoPath, result.TVShowPath);
 | 
			
		||||
 | 
			
		||||
            GlobalInfos.setTVShowsEnabled(result.TVShowEnabled);
 | 
			
		||||
            GlobalInfos.setFullDeleteEnabled(result.FullDeleteEnabled);
 | 
			
		||||
 | 
			
		||||
            this.setState({
 | 
			
		||||
                mediacentername: result.MediacenterName
 | 
			
		||||
            });
 | 
			
		||||
            // set tab title to received mediacenter name
 | 
			
		||||
            document.title = result.MediacenterName;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    componentDidMount(): void {
 | 
			
		||||
        this.initialAPICall();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render(): JSX.Element {
 | 
			
		||||
        // add the main theme to the page body
 | 
			
		||||
        document.body.className = GlobalInfos.getThemeStyle().backgroundcolor;
 | 
			
		||||
 | 
			
		||||
        if (this.state.password === true) {
 | 
			
		||||
            // render authentication page if auth is neccessary
 | 
			
		||||
            return (
 | 
			
		||||
                <AuthenticationPage
 | 
			
		||||
                    onSuccessLogin={(): void => {
 | 
			
		||||
                        this.setState({password: false});
 | 
			
		||||
                        // reinit general infos
 | 
			
		||||
                        this.initialAPICall();
 | 
			
		||||
                    }}
 | 
			
		||||
                />
 | 
			
		||||
            );
 | 
			
		||||
        } else if (this.state.password === false) {
 | 
			
		||||
            return (
 | 
			
		||||
                <Router>
 | 
			
		||||
                    <div className={style.app}>
 | 
			
		||||
        return (
 | 
			
		||||
            <LoginContextProvider>
 | 
			
		||||
                <Switch>
 | 
			
		||||
                    <Route path='/login'>
 | 
			
		||||
                        <AuthenticationPage
 | 
			
		||||
                            onSuccessLogin={(): void => {
 | 
			
		||||
                                // this.setState({password: false});
 | 
			
		||||
                                // reinit general infos
 | 
			
		||||
                                // this.initialAPICall();
 | 
			
		||||
                            }}
 | 
			
		||||
                        />
 | 
			
		||||
                    </Route>
 | 
			
		||||
                    <Route path='/media'>
 | 
			
		||||
                        {this.navBar()}
 | 
			
		||||
                        {this.routing()}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </Router>
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            return <>still loading...</>;
 | 
			
		||||
        }
 | 
			
		||||
                        <MyRouter />
 | 
			
		||||
                    </Route>
 | 
			
		||||
                </Switch>
 | 
			
		||||
            </LoginContextProvider>
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // if (this.state.password === true) {
 | 
			
		||||
        //     // render authentication page if auth is neccessary
 | 
			
		||||
        //     return (
 | 
			
		||||
        //         <AuthenticationPage
 | 
			
		||||
        //             onSuccessLogin={(): void => {
 | 
			
		||||
        //                 this.setState({password: false});
 | 
			
		||||
        //                 // reinit general infos
 | 
			
		||||
        //                 this.initialAPICall();
 | 
			
		||||
        //             }}
 | 
			
		||||
        //         />
 | 
			
		||||
        //     );
 | 
			
		||||
        // } else if (this.state.password === false) {
 | 
			
		||||
        //     return (
 | 
			
		||||
        //         <Router>
 | 
			
		||||
        //             <div className={style.app}>
 | 
			
		||||
        //                 {this.navBar()}
 | 
			
		||||
        //                 {this.routing()}
 | 
			
		||||
        //             </div>
 | 
			
		||||
        //         </Router>
 | 
			
		||||
        //     );
 | 
			
		||||
        // } else {
 | 
			
		||||
        //     return <>still loading...</>;
 | 
			
		||||
        // }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -139,73 +98,84 @@ class App extends React.Component<{}, state> {
 | 
			
		||||
        return (
 | 
			
		||||
            <div className={[style.navcontainer, themeStyle.backgroundcolor, themeStyle.textcolor, themeStyle.hrcolor].join(' ')}>
 | 
			
		||||
                <div className={style.navbrand}>{this.state.mediacentername}</div>
 | 
			
		||||
                <NavLink className={[style.navitem, themeStyle.navitem].join(' ')} to={'/'} activeStyle={{opacity: '0.85'}}>
 | 
			
		||||
                <NavLink className={[style.navitem, themeStyle.navitem].join(' ')} to={'/media'} activeStyle={{opacity: '0.85'}}>
 | 
			
		||||
                    Home
 | 
			
		||||
                </NavLink>
 | 
			
		||||
                <NavLink className={[style.navitem, themeStyle.navitem].join(' ')} to={'/random'} activeStyle={{opacity: '0.85'}}>
 | 
			
		||||
                <NavLink
 | 
			
		||||
                    className={[style.navitem, themeStyle.navitem].join(' ')}
 | 
			
		||||
                    to={'/media/random'}
 | 
			
		||||
                    activeStyle={{opacity: '0.85'}}>
 | 
			
		||||
                    Random Video
 | 
			
		||||
                </NavLink>
 | 
			
		||||
 | 
			
		||||
                <NavLink className={[style.navitem, themeStyle.navitem].join(' ')} to={'/categories'} activeStyle={{opacity: '0.85'}}>
 | 
			
		||||
                <NavLink
 | 
			
		||||
                    className={[style.navitem, themeStyle.navitem].join(' ')}
 | 
			
		||||
                    to={'/media/categories'}
 | 
			
		||||
                    activeStyle={{opacity: '0.85'}}>
 | 
			
		||||
                    Categories
 | 
			
		||||
                </NavLink>
 | 
			
		||||
 | 
			
		||||
                {GlobalInfos.isTVShowEnabled() ? (
 | 
			
		||||
                    <NavLink className={[style.navitem, themeStyle.navitem].join(' ')} to={'/tvshows'} activeStyle={{opacity: '0.85'}}>
 | 
			
		||||
                    <NavLink
 | 
			
		||||
                        className={[style.navitem, themeStyle.navitem].join(' ')}
 | 
			
		||||
                        to={'/media/tvshows'}
 | 
			
		||||
                        activeStyle={{opacity: '0.85'}}>
 | 
			
		||||
                        TV Shows
 | 
			
		||||
                    </NavLink>
 | 
			
		||||
                ) : null}
 | 
			
		||||
 | 
			
		||||
                <NavLink className={[style.navitem, themeStyle.navitem].join(' ')} to={'/settings'} activeStyle={{opacity: '0.85'}}>
 | 
			
		||||
                <NavLink
 | 
			
		||||
                    className={[style.navitem, themeStyle.navitem].join(' ')}
 | 
			
		||||
                    to={'/media/settings'}
 | 
			
		||||
                    activeStyle={{opacity: '0.85'}}>
 | 
			
		||||
                    Settings
 | 
			
		||||
                </NavLink>
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * render the react router elements
 | 
			
		||||
     */
 | 
			
		||||
    routing(): JSX.Element {
 | 
			
		||||
        return (
 | 
			
		||||
            <Switch>
 | 
			
		||||
                <Route path='/random'>
 | 
			
		||||
                    <RandomPage />
 | 
			
		||||
                </Route>
 | 
			
		||||
                <Route path='/categories'>
 | 
			
		||||
                    <CategoryPage />
 | 
			
		||||
                </Route>
 | 
			
		||||
                <Route path='/settings'>
 | 
			
		||||
                    <SettingsPage />
 | 
			
		||||
                </Route>
 | 
			
		||||
                <Route exact path='/player/:id'>
 | 
			
		||||
                    <Player />
 | 
			
		||||
                </Route>
 | 
			
		||||
                <Route exact path='/actors'>
 | 
			
		||||
                    <ActorOverviewPage />
 | 
			
		||||
                </Route>
 | 
			
		||||
                <Route path='/actors/:id'>
 | 
			
		||||
                    <ActorPage />
 | 
			
		||||
                </Route>
 | 
			
		||||
 | 
			
		||||
                {GlobalInfos.isTVShowEnabled() ? (
 | 
			
		||||
                    <Route path='/tvshows'>
 | 
			
		||||
                        <TVShowPage />
 | 
			
		||||
                    </Route>
 | 
			
		||||
                ) : null}
 | 
			
		||||
 | 
			
		||||
                {GlobalInfos.isTVShowEnabled() ? (
 | 
			
		||||
                    <Route exact path='/tvplayer/:id'>
 | 
			
		||||
                        <TVPlayer />
 | 
			
		||||
                    </Route>
 | 
			
		||||
                ) : null}
 | 
			
		||||
 | 
			
		||||
                <Route path='/'>
 | 
			
		||||
                    <HomePage />
 | 
			
		||||
                </Route>
 | 
			
		||||
            </Switch>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const MyRouter = (): JSX.Element => {
 | 
			
		||||
    const match = useRouteMatch();
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Switch>
 | 
			
		||||
            <Route exact path={`${match.url}/random`}>
 | 
			
		||||
                <RandomPage />
 | 
			
		||||
            </Route>
 | 
			
		||||
            <Route path={`${match.url}/categories`}>
 | 
			
		||||
                <CategoryPage />
 | 
			
		||||
            </Route>
 | 
			
		||||
            <Route path={`${match.url}/settings`}>
 | 
			
		||||
                <SettingsPage />
 | 
			
		||||
            </Route>
 | 
			
		||||
            <Route exact path={`${match.url}/player/:id`}>
 | 
			
		||||
                <Player />
 | 
			
		||||
            </Route>
 | 
			
		||||
            <Route exact path={`${match.url}/actors`}>
 | 
			
		||||
                <ActorOverviewPage />
 | 
			
		||||
            </Route>
 | 
			
		||||
            <Route exact path={`${match.url}/actors/:id`}>
 | 
			
		||||
                <ActorPage />
 | 
			
		||||
            </Route>
 | 
			
		||||
 | 
			
		||||
            {GlobalInfos.isTVShowEnabled() ? (
 | 
			
		||||
                <Route exact path={`${match.url}/tvshows`}>
 | 
			
		||||
                    <TVShowPage />
 | 
			
		||||
                </Route>
 | 
			
		||||
            ) : null}
 | 
			
		||||
 | 
			
		||||
            {GlobalInfos.isTVShowEnabled() ? (
 | 
			
		||||
                <Route exact path={`${match.url}/tvplayer/:id`}>
 | 
			
		||||
                    <TVPlayer />
 | 
			
		||||
                </Route>
 | 
			
		||||
            ) : null}
 | 
			
		||||
 | 
			
		||||
            <Route path={`${match.url}/`}>
 | 
			
		||||
                <HomePage />
 | 
			
		||||
            </Route>
 | 
			
		||||
        </Switch>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default App;
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ const VideoContainer = (props: Props): JSX.Element => {
 | 
			
		||||
                        );
 | 
			
		||||
                    }}
 | 
			
		||||
                    name={el.MovieName}
 | 
			
		||||
                    linkPath={'/player/' + el.MovieId}
 | 
			
		||||
                    linkPath={'/media/player/' + el.MovieId}
 | 
			
		||||
                />
 | 
			
		||||
            )}
 | 
			
		||||
            data={props.data}>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,16 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import ReactDOM from 'react-dom';
 | 
			
		||||
import App from './App';
 | 
			
		||||
import {BrowserRouter} from 'react-router-dom';
 | 
			
		||||
 | 
			
		||||
// don't allow console logs within production env
 | 
			
		||||
global.console.log = process.env.NODE_ENV !== 'development' ? (_: string | number | boolean): void => {} : global.console.log;
 | 
			
		||||
 | 
			
		||||
ReactDOM.render(
 | 
			
		||||
    <React.StrictMode>
 | 
			
		||||
        <App />
 | 
			
		||||
        <BrowserRouter>
 | 
			
		||||
            <App />
 | 
			
		||||
        </BrowserRouter>
 | 
			
		||||
    </React.StrictMode>,
 | 
			
		||||
    document.getElementById('root')
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -2,9 +2,11 @@ import React from 'react';
 | 
			
		||||
import {Button} from '../../elements/GPElements/Button';
 | 
			
		||||
import style from './AuthenticationPage.module.css';
 | 
			
		||||
import {addKeyHandler, removeKeyHandler} from '../../utils/ShortkeyHandler';
 | 
			
		||||
import {token} from '../../utils/TokenHandler';
 | 
			
		||||
import {faTimes} from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
 | 
			
		||||
import {LoginContext, LoginState} from '../../utils/context/LoginContext';
 | 
			
		||||
import {APINode, callApiUnsafe} from '../../utils/Api';
 | 
			
		||||
import {cookie, Token} from '../../utils/context/Cookie';
 | 
			
		||||
 | 
			
		||||
interface state {
 | 
			
		||||
    pwdText: string;
 | 
			
		||||
@@ -36,6 +38,8 @@ class AuthenticationPage extends React.Component<Props, state> {
 | 
			
		||||
        removeKeyHandler(this.keypress);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static contextType = LoginContext;
 | 
			
		||||
 | 
			
		||||
    render(): JSX.Element {
 | 
			
		||||
        return (
 | 
			
		||||
            <>
 | 
			
		||||
@@ -76,21 +80,17 @@ class AuthenticationPage extends React.Component<Props, state> {
 | 
			
		||||
     * request a new token and check if pwd was valid
 | 
			
		||||
     */
 | 
			
		||||
    authenticate(): void {
 | 
			
		||||
        token.refreshAPIToken(
 | 
			
		||||
            (error) => {
 | 
			
		||||
                if (error !== '') {
 | 
			
		||||
                    this.setState({wrongPWDInfo: true});
 | 
			
		||||
        callApiUnsafe(
 | 
			
		||||
            APINode.Login,
 | 
			
		||||
            {action: 'login', Password: this.state.pwdText},
 | 
			
		||||
            (r: Token) => {
 | 
			
		||||
                cookie.Store(r);
 | 
			
		||||
 | 
			
		||||
                    // set timeout to make the info auto-disappearing
 | 
			
		||||
                    setTimeout(() => {
 | 
			
		||||
                        this.setState({wrongPWDInfo: false});
 | 
			
		||||
                    }, 2000);
 | 
			
		||||
                } else {
 | 
			
		||||
                    this.props.onSuccessLogin();
 | 
			
		||||
                }
 | 
			
		||||
                this.context.setLoginState(LoginState.LoggedIn);
 | 
			
		||||
            },
 | 
			
		||||
            true,
 | 
			
		||||
            this.state.pwdText
 | 
			
		||||
            (e) => {
 | 
			
		||||
                console.log(e);
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import {Route, Switch} from 'react-router-dom';
 | 
			
		||||
import {Route, Switch, useRouteMatch} from 'react-router-dom';
 | 
			
		||||
import {CategoryViewWR} from './CategoryView';
 | 
			
		||||
import TagView from './TagView';
 | 
			
		||||
 | 
			
		||||
@@ -7,19 +7,21 @@ import TagView from './TagView';
 | 
			
		||||
 * Component for Category Page
 | 
			
		||||
 * Contains a Tag Overview and loads specific Tag videos in VideoContainer
 | 
			
		||||
 */
 | 
			
		||||
class CategoryPage extends React.Component {
 | 
			
		||||
    render(): JSX.Element {
 | 
			
		||||
        return (
 | 
			
		||||
            <Switch>
 | 
			
		||||
                <Route path='/categories/:id'>
 | 
			
		||||
                    <CategoryViewWR />
 | 
			
		||||
                </Route>
 | 
			
		||||
                <Route path='/categories'>
 | 
			
		||||
                    <TagView />
 | 
			
		||||
                </Route>
 | 
			
		||||
            </Switch>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
const CategoryPage = (): JSX.Element => {
 | 
			
		||||
    const match = useRouteMatch();
 | 
			
		||||
 | 
			
		||||
    console.log(match.url);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Switch>
 | 
			
		||||
            <Route exact path={`${match.url}/:id`}>
 | 
			
		||||
                <CategoryViewWR />
 | 
			
		||||
            </Route>
 | 
			
		||||
            <Route exact path={`${match.url}/`}>
 | 
			
		||||
                <TagView />
 | 
			
		||||
            </Route>
 | 
			
		||||
        </Switch>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default CategoryPage;
 | 
			
		||||
 
 | 
			
		||||
@@ -119,9 +119,10 @@ export class CategoryView extends React.Component<CategoryViewProps, CategoryVie
 | 
			
		||||
                this.videodata = result.Videos;
 | 
			
		||||
                this.setState({loaded: true, TagName: result.TagName});
 | 
			
		||||
            },
 | 
			
		||||
            (_) => {
 | 
			
		||||
            (e) => {
 | 
			
		||||
                console.log(e);
 | 
			
		||||
                // if there is an load error redirect to home page
 | 
			
		||||
                this.props.history.push('/');
 | 
			
		||||
                // this.props.history.push('/');
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,7 @@ class TagView extends React.Component<Props, TagViewState> {
 | 
			
		||||
                <DynamicContentContainer
 | 
			
		||||
                    data={this.state.loadedtags}
 | 
			
		||||
                    renderElement={(m): JSX.Element => (
 | 
			
		||||
                        <Link to={'/categories/' + m.TagId} key={m.TagId}>
 | 
			
		||||
                        <Link to={'/media/categories/' + m.TagId} key={m.TagId}>
 | 
			
		||||
                            <TagPreview name={m.TagName} />
 | 
			
		||||
                        </Link>
 | 
			
		||||
                    )}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,52 +3,52 @@ import MovieSettings from './MovieSettings';
 | 
			
		||||
import GeneralSettings from './GeneralSettings';
 | 
			
		||||
import style from './SettingsPage.module.css';
 | 
			
		||||
import GlobalInfos from '../../utils/GlobalInfos';
 | 
			
		||||
import {NavLink, Redirect, Route, Switch} from 'react-router-dom';
 | 
			
		||||
import {NavLink, Redirect, Route, Switch, useRouteMatch} from 'react-router-dom';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The Settingspage handles all kinds of settings for the mediacenter
 | 
			
		||||
 * and is basically a wrapper for child-tabs
 | 
			
		||||
 */
 | 
			
		||||
class SettingsPage extends React.Component {
 | 
			
		||||
    render(): JSX.Element {
 | 
			
		||||
        const themestyle = GlobalInfos.getThemeStyle();
 | 
			
		||||
        return (
 | 
			
		||||
            <div>
 | 
			
		||||
                <div className={style.SettingsSidebar + ' ' + themestyle.secbackground}>
 | 
			
		||||
                    <div className={style.SettingsSidebarTitle + ' ' + themestyle.lighttextcolor}>Settings</div>
 | 
			
		||||
                    <NavLink to='/settings/general'>
 | 
			
		||||
                        <div className={style.SettingSidebarElement}>General</div>
 | 
			
		||||
const SettingsPage = (): JSX.Element => {
 | 
			
		||||
    const themestyle = GlobalInfos.getThemeStyle();
 | 
			
		||||
    const match = useRouteMatch();
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div>
 | 
			
		||||
            <div className={style.SettingsSidebar + ' ' + themestyle.secbackground}>
 | 
			
		||||
                <div className={style.SettingsSidebarTitle + ' ' + themestyle.lighttextcolor}>Settings</div>
 | 
			
		||||
                <NavLink to='/media/settings/general'>
 | 
			
		||||
                    <div className={style.SettingSidebarElement}>General</div>
 | 
			
		||||
                </NavLink>
 | 
			
		||||
                <NavLink to='/media/settings/movies'>
 | 
			
		||||
                    <div className={style.SettingSidebarElement}>Movies</div>
 | 
			
		||||
                </NavLink>
 | 
			
		||||
                {GlobalInfos.isTVShowEnabled() ? (
 | 
			
		||||
                    <NavLink to='/media/settings/tv'>
 | 
			
		||||
                        <div className={style.SettingSidebarElement}>TV Shows</div>
 | 
			
		||||
                    </NavLink>
 | 
			
		||||
                    <NavLink to='/settings/movies'>
 | 
			
		||||
                        <div className={style.SettingSidebarElement}>Movies</div>
 | 
			
		||||
                    </NavLink>
 | 
			
		||||
                    {GlobalInfos.isTVShowEnabled() ? (
 | 
			
		||||
                        <NavLink to='/settings/tv'>
 | 
			
		||||
                            <div className={style.SettingSidebarElement}>TV Shows</div>
 | 
			
		||||
                        </NavLink>
 | 
			
		||||
                    ) : null}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className={style.SettingsContent}>
 | 
			
		||||
                    <Switch>
 | 
			
		||||
                        <Route path='/settings/general'>
 | 
			
		||||
                            <GeneralSettings />
 | 
			
		||||
                        </Route>
 | 
			
		||||
                        <Route path='/settings/movies'>
 | 
			
		||||
                            <MovieSettings />
 | 
			
		||||
                        </Route>
 | 
			
		||||
                        {GlobalInfos.isTVShowEnabled() ? (
 | 
			
		||||
                            <Route path='/settings/tv'>
 | 
			
		||||
                                <span />
 | 
			
		||||
                            </Route>
 | 
			
		||||
                        ) : null}
 | 
			
		||||
                        <Route path='/settings'>
 | 
			
		||||
                            <Redirect to='/settings/general' />
 | 
			
		||||
                        </Route>
 | 
			
		||||
                    </Switch>
 | 
			
		||||
                </div>
 | 
			
		||||
                ) : null}
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
            <div className={style.SettingsContent}>
 | 
			
		||||
                <Switch>
 | 
			
		||||
                    <Route path={`${match.url}/general`}>
 | 
			
		||||
                        <GeneralSettings />
 | 
			
		||||
                    </Route>
 | 
			
		||||
                    <Route path={`${match.url}/movies`}>
 | 
			
		||||
                        <MovieSettings />
 | 
			
		||||
                    </Route>
 | 
			
		||||
                    {GlobalInfos.isTVShowEnabled() ? (
 | 
			
		||||
                        <Route path={`${match.url}/tv`}>
 | 
			
		||||
                            <span />
 | 
			
		||||
                        </Route>
 | 
			
		||||
                    ) : null}
 | 
			
		||||
                    <Route path={`${match.url}/`}>
 | 
			
		||||
                        <Redirect to='/media/settings/general' />
 | 
			
		||||
                    </Route>
 | 
			
		||||
                </Switch>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default SettingsPage;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import GlobalInfos from './GlobalInfos';
 | 
			
		||||
import {token} from './TokenHandler';
 | 
			
		||||
import {cookie} from './context/Cookie';
 | 
			
		||||
 | 
			
		||||
const APIPREFIX: string = '/api/';
 | 
			
		||||
 | 
			
		||||
@@ -25,9 +25,7 @@ export function callAPI<T>(
 | 
			
		||||
    callback: (_: T) => void,
 | 
			
		||||
    errorcallback: (_: string) => void = (_: string): void => {}
 | 
			
		||||
): void {
 | 
			
		||||
    token.checkAPITokenValid((mytoken) => {
 | 
			
		||||
        generalAPICall<T>(apinode, fd, callback, errorcallback, false, true, mytoken);
 | 
			
		||||
    });
 | 
			
		||||
    generalAPICall<T>(apinode, fd, callback, errorcallback, false, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -43,7 +41,7 @@ export function callApiUnsafe<T>(
 | 
			
		||||
    callback: (_: T) => void,
 | 
			
		||||
    errorcallback?: (_: string) => void
 | 
			
		||||
): void {
 | 
			
		||||
    generalAPICall(apinode, fd, callback, errorcallback, true, true, '');
 | 
			
		||||
    generalAPICall(apinode, fd, callback, errorcallback, true, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -53,9 +51,7 @@ export function callApiUnsafe<T>(
 | 
			
		||||
 * @param callback the callback with PLAIN text reply from backend
 | 
			
		||||
 */
 | 
			
		||||
export function callAPIPlain(apinode: APINode, fd: ApiBaseRequest, callback: (_: string) => void): void {
 | 
			
		||||
    token.checkAPITokenValid((mytoken) => {
 | 
			
		||||
        generalAPICall(apinode, fd, callback, () => {}, false, false, mytoken);
 | 
			
		||||
    });
 | 
			
		||||
    generalAPICall(apinode, fd, callback, () => {}, false, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function generalAPICall<T>(
 | 
			
		||||
@@ -64,16 +60,16 @@ function generalAPICall<T>(
 | 
			
		||||
    callback: (_: T) => void,
 | 
			
		||||
    errorcallback: (_: string) => void = (_: string): void => {},
 | 
			
		||||
    unsafe: boolean,
 | 
			
		||||
    json: boolean,
 | 
			
		||||
    mytoken: string
 | 
			
		||||
    json: boolean
 | 
			
		||||
): void {
 | 
			
		||||
    (async function (): Promise<void> {
 | 
			
		||||
        const tkn = cookie.Load();
 | 
			
		||||
        const response = await fetch(APIPREFIX + apinode + '/' + fd.action, {
 | 
			
		||||
            method: 'POST',
 | 
			
		||||
            body: JSON.stringify(fd),
 | 
			
		||||
            headers: new Headers({
 | 
			
		||||
                'Content-Type': json ? 'application/json' : 'text/plain',
 | 
			
		||||
                ...(!unsafe && {Authorization: 'Bearer ' + mytoken})
 | 
			
		||||
                ...(!unsafe && tkn !== null && {Token: tkn.Token})
 | 
			
		||||
            })
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@@ -110,6 +106,7 @@ function generalAPICall<T>(
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line no-shadow
 | 
			
		||||
export enum APINode {
 | 
			
		||||
    Login = 'login',
 | 
			
		||||
    Settings = 'settings',
 | 
			
		||||
    Tags = 'tags',
 | 
			
		||||
    Actor = 'actor',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,135 +0,0 @@
 | 
			
		||||
import {TokenStore} from './TokenStore/TokenStore';
 | 
			
		||||
 | 
			
		||||
export namespace token {
 | 
			
		||||
    // store api token - empty if not set
 | 
			
		||||
    let apiToken = '';
 | 
			
		||||
 | 
			
		||||
    // a callback que to be called after api token refresh
 | 
			
		||||
    let callQue: ((error: string) => void)[] = [];
 | 
			
		||||
    // flag to check wheter a api refresh is currently pending
 | 
			
		||||
    let refreshInProcess = false;
 | 
			
		||||
    // store the expire seconds of token
 | 
			
		||||
    let expireSeconds = -1;
 | 
			
		||||
 | 
			
		||||
    let tokenStore: TokenStore;
 | 
			
		||||
    let APiHost: string = '/';
 | 
			
		||||
 | 
			
		||||
    export function init(ts: TokenStore, apiHost?: string): void {
 | 
			
		||||
        tokenStore = ts;
 | 
			
		||||
        if (apiHost) {
 | 
			
		||||
            APiHost = apiHost;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * refresh the api token or use that one in cookie if still valid
 | 
			
		||||
     * @param callback to be called after successful refresh
 | 
			
		||||
     * @param password
 | 
			
		||||
     * @param force
 | 
			
		||||
     */
 | 
			
		||||
    export function refreshAPIToken(callback: (error: string) => void, force?: boolean, password?: string): void {
 | 
			
		||||
        callQue.push(callback);
 | 
			
		||||
 | 
			
		||||
        // check if already is a token refresh is in process
 | 
			
		||||
        if (refreshInProcess) {
 | 
			
		||||
            // if yes return
 | 
			
		||||
            return;
 | 
			
		||||
        } else {
 | 
			
		||||
            // if not set flat
 | 
			
		||||
            refreshInProcess = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (apiTokenValid() && !force) {
 | 
			
		||||
            console.log('token still valid...');
 | 
			
		||||
            callFuncQue('');
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const formData = new FormData();
 | 
			
		||||
        formData.append('grant_type', 'client_credentials');
 | 
			
		||||
        formData.append('client_id', 'openmediacenter');
 | 
			
		||||
        formData.append('client_secret', password ? password : 'openmediacenter');
 | 
			
		||||
        formData.append('scope', 'all');
 | 
			
		||||
 | 
			
		||||
        interface APIToken {
 | 
			
		||||
            error?: string;
 | 
			
		||||
            // eslint-disable-next-line camelcase
 | 
			
		||||
            access_token: string; // no camel case allowed because of backendlib
 | 
			
		||||
            // eslint-disable-next-line camelcase
 | 
			
		||||
            expires_in: number; // no camel case allowed because of backendlib
 | 
			
		||||
            scope: string;
 | 
			
		||||
            // eslint-disable-next-line camelcase
 | 
			
		||||
            token_type: string; // no camel case allowed because of backendlib
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        console.log(APiHost);
 | 
			
		||||
 | 
			
		||||
        fetch(APiHost + 'token', {method: 'POST', body: formData})
 | 
			
		||||
            .then((response) =>
 | 
			
		||||
                response.json().then((result: APIToken) => {
 | 
			
		||||
                    if (result.error) {
 | 
			
		||||
                        callFuncQue(result.error);
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    // set api token
 | 
			
		||||
                    apiToken = result.access_token;
 | 
			
		||||
                    // set expire time
 | 
			
		||||
                    expireSeconds = new Date().getTime() / 1000 + result.expires_in;
 | 
			
		||||
                    // setTokenCookie(apiToken, expireSeconds);
 | 
			
		||||
                    tokenStore.setToken({accessToken: apiToken, expireTime: expireSeconds, tokenType: '', scope: ''});
 | 
			
		||||
                    // call all handlers and release flag
 | 
			
		||||
                    callFuncQue('');
 | 
			
		||||
                })
 | 
			
		||||
            )
 | 
			
		||||
            .catch((e) => {
 | 
			
		||||
                callback(e);
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    export function apiTokenValid(): boolean {
 | 
			
		||||
        // check if a cookie with token is available
 | 
			
		||||
        // const token = getTokenCookie();
 | 
			
		||||
        const tmptoken = tokenStore.loadToken();
 | 
			
		||||
        if (tmptoken !== null) {
 | 
			
		||||
            // check if token is at least valid for the next minute
 | 
			
		||||
            if (tmptoken.expireTime > new Date().getTime() / 1000 + 60) {
 | 
			
		||||
                apiToken = tmptoken.accessToken;
 | 
			
		||||
                expireSeconds = tmptoken.expireTime;
 | 
			
		||||
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * call all qued callbacks
 | 
			
		||||
     */
 | 
			
		||||
    function callFuncQue(error: string): void {
 | 
			
		||||
        // call all pending handlers
 | 
			
		||||
        callQue.map((func) => {
 | 
			
		||||
            return func(error);
 | 
			
		||||
        });
 | 
			
		||||
        // reset pending que
 | 
			
		||||
        callQue = [];
 | 
			
		||||
        // release flag to be able to start new refresh
 | 
			
		||||
        refreshInProcess = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * check if api token is valid -- if not request new one
 | 
			
		||||
     * when finished call callback
 | 
			
		||||
     * @param callback function to be called afterwards
 | 
			
		||||
     */
 | 
			
		||||
    export function checkAPITokenValid(callback: (mytoken: string) => void): void {
 | 
			
		||||
        // check if token is valid and set
 | 
			
		||||
        if (apiToken === '' || expireSeconds <= new Date().getTime() / 1000) {
 | 
			
		||||
            console.log('token not valid...');
 | 
			
		||||
            refreshAPIToken(() => {
 | 
			
		||||
                callback(apiToken);
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            callback(apiToken);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,48 +0,0 @@
 | 
			
		||||
import {Token, TokenStore} from './TokenStore';
 | 
			
		||||
 | 
			
		||||
export class CookieTokenStore extends TokenStore {
 | 
			
		||||
    loadToken(): Token | null {
 | 
			
		||||
        const token = this.decodeCookie('token');
 | 
			
		||||
        const expireInString = this.decodeCookie('token_expire');
 | 
			
		||||
        const expireIn = parseInt(expireInString, 10);
 | 
			
		||||
 | 
			
		||||
        if (expireIn !== 0 && token !== '') {
 | 
			
		||||
            return {accessToken: token, expireTime: expireIn, scope: '', tokenType: ''};
 | 
			
		||||
        } else {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * set the cookie for the currently gotten token
 | 
			
		||||
     * @param token the token to set
 | 
			
		||||
     */
 | 
			
		||||
    setToken(token: Token): void {
 | 
			
		||||
        let d = new Date();
 | 
			
		||||
        d.setTime(token.expireTime * 1000);
 | 
			
		||||
        console.log('token set' + d.toUTCString());
 | 
			
		||||
        let expires = 'expires=' + d.toUTCString();
 | 
			
		||||
        document.cookie = 'token=' + token.accessToken + ';' + expires + ';path=/';
 | 
			
		||||
        document.cookie = 'token_expire=' + token.expireTime + ';' + expires + ';path=/';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * decode a simple cookie with key specified
 | 
			
		||||
     * @param key cookie key
 | 
			
		||||
     */
 | 
			
		||||
    decodeCookie(key: string): string {
 | 
			
		||||
        let name = key + '=';
 | 
			
		||||
        let decodedCookie = decodeURIComponent(document.cookie);
 | 
			
		||||
        let ca = decodedCookie.split(';');
 | 
			
		||||
        for (let i = 0; i < ca.length; i++) {
 | 
			
		||||
            let c = ca[i];
 | 
			
		||||
            while (c.charAt(0) === ' ') {
 | 
			
		||||
                c = c.substring(1);
 | 
			
		||||
            }
 | 
			
		||||
            if (c.indexOf(name) === 0) {
 | 
			
		||||
                return c.substring(name.length, c.length);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
export interface Token {
 | 
			
		||||
    accessToken: string;
 | 
			
		||||
    expireTime: number; // second time when token will be invalidated
 | 
			
		||||
    scope: string;
 | 
			
		||||
    tokenType: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export abstract class TokenStore {
 | 
			
		||||
    abstract loadToken(): Token | null;
 | 
			
		||||
    abstract setToken(token: Token): void;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										55
									
								
								src/utils/context/Cookie.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/utils/context/Cookie.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
export interface Token {
 | 
			
		||||
    Token: string;
 | 
			
		||||
    ExpiresAt: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export namespace cookie {
 | 
			
		||||
    const jwtcookiename = 'jwt';
 | 
			
		||||
 | 
			
		||||
    export function Store(data: Token): void {
 | 
			
		||||
        const d = new Date();
 | 
			
		||||
        d.setTime(data.ExpiresAt * 1000);
 | 
			
		||||
        const expires = 'expires=' + d.toUTCString();
 | 
			
		||||
 | 
			
		||||
        document.cookie = jwtcookiename + '=' + JSON.stringify(data) + ';' + expires + ';path=/';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    export function Load(): Token | null {
 | 
			
		||||
        const datastr = decodeCookie(jwtcookiename);
 | 
			
		||||
        if (datastr === '') {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            return JSON.parse(datastr);
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            // if cookie not decodeable delete it and return null
 | 
			
		||||
            Delete();
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    export function Delete(): void {
 | 
			
		||||
        document.cookie = `${jwtcookiename}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * decode a simple cookie with key specified
 | 
			
		||||
     * @param key cookie key
 | 
			
		||||
     */
 | 
			
		||||
    function decodeCookie(key: string): string {
 | 
			
		||||
        let name = key + '=';
 | 
			
		||||
        let decodedCookie = decodeURIComponent(document.cookie);
 | 
			
		||||
        let ca = decodedCookie.split(';');
 | 
			
		||||
        for (let i = 0; i < ca.length; i++) {
 | 
			
		||||
            let c = ca[i];
 | 
			
		||||
            while (c.charAt(0) === ' ') {
 | 
			
		||||
                c = c.substring(1);
 | 
			
		||||
            }
 | 
			
		||||
            if (c.indexOf(name) === 0) {
 | 
			
		||||
                return c.substring(name.length, c.length);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										34
									
								
								src/utils/context/LoginContext.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/utils/context/LoginContext.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * global context definitions
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export enum LoginState {
 | 
			
		||||
    LoggedIn,
 | 
			
		||||
    LoggedOut
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum LoginPerm {
 | 
			
		||||
    Admin,
 | 
			
		||||
    User
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface LoginContextType {
 | 
			
		||||
    logout: () => void;
 | 
			
		||||
    setPerm: (permission: LoginPerm) => void;
 | 
			
		||||
    loginstate: LoginState;
 | 
			
		||||
    setLoginState: (state: LoginState) => void;
 | 
			
		||||
    permission: LoginPerm;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A global context providing a way to interact with user login states
 | 
			
		||||
 */
 | 
			
		||||
export const LoginContext = React.createContext<LoginContextType>({
 | 
			
		||||
    setLoginState(): void {},
 | 
			
		||||
    setPerm(): void {},
 | 
			
		||||
    logout: () => {},
 | 
			
		||||
    loginstate: LoginState.LoggedOut,
 | 
			
		||||
    permission: LoginPerm.User
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										105
									
								
								src/utils/context/LoginContextProvider.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								src/utils/context/LoginContextProvider.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,105 @@
 | 
			
		||||
import {LoginContext, LoginPerm, LoginState} from './LoginContext';
 | 
			
		||||
import React, {FunctionComponent, useContext, useEffect, useState} from 'react';
 | 
			
		||||
import {useHistory, useLocation} from 'react-router';
 | 
			
		||||
import {cookie} from './Cookie';
 | 
			
		||||
import {APINode, callAPI} from '../Api';
 | 
			
		||||
import {SettingsTypes} from '../../types/ApiTypes';
 | 
			
		||||
import GlobalInfos from '../GlobalInfos';
 | 
			
		||||
 | 
			
		||||
export const LoginContextProvider: FunctionComponent = (props): JSX.Element => {
 | 
			
		||||
    let initialLoginState = LoginState.LoggedIn;
 | 
			
		||||
    let initialUserPerm = LoginPerm.User;
 | 
			
		||||
 | 
			
		||||
    const t = cookie.Load();
 | 
			
		||||
    // we are already logged in so we can set the token and redirect to dashboard
 | 
			
		||||
    if (t !== null) {
 | 
			
		||||
        initialLoginState = LoginState.LoggedIn;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const initialAPICall = (): void => {
 | 
			
		||||
        // this is the first api call so if it fails we know there is no connection to backend
 | 
			
		||||
        callAPI(
 | 
			
		||||
            APINode.Settings,
 | 
			
		||||
            {action: 'loadInitialData'},
 | 
			
		||||
            (result: SettingsTypes.initialApiCallData) => {
 | 
			
		||||
                // set theme
 | 
			
		||||
                GlobalInfos.enableDarkTheme(result.DarkMode);
 | 
			
		||||
 | 
			
		||||
                GlobalInfos.setVideoPaths(result.VideoPath, result.TVShowPath);
 | 
			
		||||
 | 
			
		||||
                GlobalInfos.setTVShowsEnabled(result.TVShowEnabled);
 | 
			
		||||
                GlobalInfos.setFullDeleteEnabled(result.FullDeleteEnabled);
 | 
			
		||||
                //
 | 
			
		||||
                // this.setState({
 | 
			
		||||
                //     mediacentername: result.MediacenterName
 | 
			
		||||
                // });
 | 
			
		||||
                // set tab title to received mediacenter name
 | 
			
		||||
                document.title = result.MediacenterName;
 | 
			
		||||
 | 
			
		||||
                setLoginState(LoginState.LoggedIn);
 | 
			
		||||
            },
 | 
			
		||||
            (_) => {
 | 
			
		||||
                setLoginState(LoginState.LoggedOut);
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        initialAPICall();
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
    const [loginState, setLoginState] = useState<LoginState>(initialLoginState);
 | 
			
		||||
    const [permission, setPermission] = useState<LoginPerm>(initialUserPerm);
 | 
			
		||||
 | 
			
		||||
    const hist = useHistory();
 | 
			
		||||
    const loc = useLocation();
 | 
			
		||||
 | 
			
		||||
    // trigger redirect on loginstate change
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (loginState === LoginState.LoggedIn) {
 | 
			
		||||
            // if we arent already in dashboard tree we want to redirect to default dashboard page
 | 
			
		||||
            console.log('redirecting to dashboard' + loc.pathname);
 | 
			
		||||
            if (!loc.pathname.startsWith('/media')) {
 | 
			
		||||
                hist.replace('/media');
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            if (!loc.pathname.startsWith('/login')) {
 | 
			
		||||
                hist.replace('/login');
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }, [hist, loc.pathname, loginState]);
 | 
			
		||||
 | 
			
		||||
    const value = {
 | 
			
		||||
        logout: (): void => {
 | 
			
		||||
            setLoginState(LoginState.LoggedOut);
 | 
			
		||||
            cookie.Delete();
 | 
			
		||||
        },
 | 
			
		||||
        setPerm: (perm: LoginPerm): void => {
 | 
			
		||||
            setPermission(perm);
 | 
			
		||||
        },
 | 
			
		||||
        setLoginState: (state: LoginState): void => {
 | 
			
		||||
            setLoginState(state);
 | 
			
		||||
        },
 | 
			
		||||
        loginstate: loginState,
 | 
			
		||||
        permission: permission
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return <LoginContext.Provider value={value}>{props.children}</LoginContext.Provider>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
    perm: LoginPerm;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Wrapper element to render children only if permissions are sufficient
 | 
			
		||||
 */
 | 
			
		||||
export const AuthorizedContext: FunctionComponent<Props> = (props): JSX.Element => {
 | 
			
		||||
    const loginctx = useContext(LoginContext);
 | 
			
		||||
 | 
			
		||||
    if (loginctx.permission <= props.perm) {
 | 
			
		||||
        return props.children as JSX.Element;
 | 
			
		||||
    } else {
 | 
			
		||||
        return <></>;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
		Reference in New Issue
	
	Block a user