outsource Token namespace in seperate file to release dependency to GlobalInfos

This commit is contained in:
lukas 2021-05-08 15:19:13 +02:00
parent 0797632c44
commit b13a10f37b
5 changed files with 138 additions and 131 deletions

View File

@ -9,7 +9,7 @@ import style from './App.module.css';
import SettingsPage from './pages/SettingsPage/SettingsPage'; import SettingsPage from './pages/SettingsPage/SettingsPage';
import CategoryPage from './pages/CategoryPage/CategoryPage'; import CategoryPage from './pages/CategoryPage/CategoryPage';
import {APINode, callAPI, token} from './utils/Api'; import {APINode, callAPI} from './utils/Api';
import {BrowserRouter as Router, NavLink, Route, Switch} from 'react-router-dom'; import {BrowserRouter as Router, NavLink, Route, Switch} from 'react-router-dom';
import Player from './pages/Player/Player'; import Player from './pages/Player/Player';
@ -20,6 +20,7 @@ import AuthenticationPage from './pages/AuthenticationPage/AuthenticationPage';
import TVShowPage from './pages/TVShowPage/TVShowPage'; import TVShowPage from './pages/TVShowPage/TVShowPage';
import TVPlayer from './pages/TVShowPage/TVPlayer'; import TVPlayer from './pages/TVShowPage/TVPlayer';
import {CookieTokenStore} from './utils/TokenStore/CookieTokenStore'; import {CookieTokenStore} from './utils/TokenStore/CookieTokenStore';
import {token} from './utils/TokenHandler';
interface state { interface state {
password: boolean | null; // null if uninitialized - true if pwd needed false if not needed password: boolean | null; // null if uninitialized - true if pwd needed false if not needed
@ -33,7 +34,7 @@ class App extends React.Component<{}, state> {
constructor(props: {}) { constructor(props: {}) {
super(props); super(props);
token.setTokenStore(new CookieTokenStore()); token.init(new CookieTokenStore());
let pwdneeded: boolean | null = null; let pwdneeded: boolean | null = null;

View File

@ -2,7 +2,7 @@ import React from 'react';
import {Button} from '../../elements/GPElements/Button'; import {Button} from '../../elements/GPElements/Button';
import style from './AuthenticationPage.module.css'; import style from './AuthenticationPage.module.css';
import {addKeyHandler, removeKeyHandler} from '../../utils/ShortkeyHandler'; import {addKeyHandler, removeKeyHandler} from '../../utils/ShortkeyHandler';
import {token} from '../../utils/Api'; import {token} from '../../utils/TokenHandler';
import {faTimes} from '@fortawesome/free-solid-svg-icons'; import {faTimes} from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';

View File

@ -6,9 +6,8 @@ import '@testing-library/jest-dom/extend-expect';
import {configure} from 'enzyme'; import {configure} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16'; import Adapter from 'enzyme-adapter-react-16';
import GlobalInfos from './utils/GlobalInfos';
import {CookieTokenStore} from "./utils/TokenStore/CookieTokenStore"; import {CookieTokenStore} from "./utils/TokenStore/CookieTokenStore";
import {token} from "./utils/Api"; import {token} from "./utils/TokenHandler";
configure({adapter: new Adapter()}); configure({adapter: new Adapter()});
@ -46,7 +45,7 @@ global.callAPIMock = (resonse) => {
global.beforeEach(() => { global.beforeEach(() => {
// empty fetch response implementation for each test // empty fetch response implementation for each test
global.fetch = prepareFetchApi({}); global.fetch = prepareFetchApi({});
token.setTokenStore(new CookieTokenStore()); token.init(new CookieTokenStore());
// todo with callAPIMock // todo with callAPIMock
}); });

View File

@ -1,5 +1,5 @@
import GlobalInfos from './GlobalInfos'; import GlobalInfos from './GlobalInfos';
import {TokenStore} from './TokenStore/TokenStore'; import {token} from './TokenHandler';
const APIPREFIX: string = '/api/'; const APIPREFIX: string = '/api/';
@ -12,130 +12,6 @@ interface ApiBaseRequest {
[_: string]: string | number | boolean | object; [_: string]: string | number | boolean | object;
} }
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;
export function setTokenStore(ts: TokenStore): void {
tokenStore = ts;
}
/**
* 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
}
fetch('/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('');
})
);
}
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);
}
}
}
/** /**
* A backend api call * A backend api call
* @param apinode which api backend handler to call * @param apinode which api backend handler to call

131
src/utils/TokenHandler.ts Normal file
View File

@ -0,0 +1,131 @@
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('');
})
);
}
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);
}
}
}