diff --git a/src/App.tsx b/src/App.tsx
index 6232cfe..fbdd9a7 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -9,7 +9,7 @@ import style from './App.module.css';
import SettingsPage from './pages/SettingsPage/SettingsPage';
import CategoryPage from './pages/CategoryPage/CategoryPage';
-import {APINode, apiTokenValid, callAPI, refreshAPIToken} from './utils/Api';
+import {APINode, callAPI, token} from './utils/Api';
import {BrowserRouter as Router, NavLink, Route, Switch} from 'react-router-dom';
import Player from './pages/Player/Player';
@@ -19,6 +19,7 @@ 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';
interface state {
password: boolean | null; // null if uninitialized - true if pwd needed false if not needed
@@ -32,12 +33,14 @@ class App extends React.Component<{}, state> {
constructor(props: {}) {
super(props);
+ token.setTokenStore(new CookieTokenStore());
+
let pwdneeded: boolean | null = null;
- if (apiTokenValid()) {
+ if (token.apiTokenValid()) {
pwdneeded = false;
} else {
- refreshAPIToken((err) => {
+ token.refreshAPIToken((err) => {
if (err === 'invalid_client') {
this.setState({password: true});
} else if (err === '') {
@@ -61,7 +64,7 @@ class App extends React.Component<{}, state> {
// set the hook to load passwordfield on global func call
GlobalInfos.loadPasswordPage = (callback?: () => void): void => {
// try refreshing the token
- refreshAPIToken((err) => {
+ token.refreshAPIToken((err) => {
if (err !== '') {
this.setState({password: true});
} else {
diff --git a/src/pages/AuthenticationPage/AuthenticationPage.test.js b/src/pages/AuthenticationPage/AuthenticationPage.test.js
index 679aeb6..0762bfb 100644
--- a/src/pages/AuthenticationPage/AuthenticationPage.test.js
+++ b/src/pages/AuthenticationPage/AuthenticationPage.test.js
@@ -1,6 +1,7 @@
import React from 'react';
import AuthenticationPage from './AuthenticationPage';
import {shallow} from 'enzyme';
+import {token} from "../../utils/Api";
describe('', function () {
it('renders without crashing ', function () {
@@ -25,8 +26,7 @@ describe('', function () {
it('test fail authenticate', function () {
const events = mockKeyPress();
- const helpers = require('../../utils/Api');
- helpers.refreshAPIToken = jest.fn().mockImplementation((callback, force, pwd) => {
+ token.refreshAPIToken = jest.fn().mockImplementation((callback, force, pwd) => {
callback('there was an error')
});
@@ -41,8 +41,7 @@ describe('', function () {
const events = mockKeyPress();
const func = jest.fn()
- const helpers = require('../../utils/Api');
- helpers.refreshAPIToken = jest.fn().mockImplementation((callback, force, pwd) => {
+ token.refreshAPIToken = jest.fn().mockImplementation((callback, force, pwd) => {
callback('')
});
diff --git a/src/pages/AuthenticationPage/AuthenticationPage.tsx b/src/pages/AuthenticationPage/AuthenticationPage.tsx
index 5aef7ee..65f3dd3 100644
--- a/src/pages/AuthenticationPage/AuthenticationPage.tsx
+++ b/src/pages/AuthenticationPage/AuthenticationPage.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import {Button} from '../../elements/GPElements/Button';
import style from './AuthenticationPage.module.css';
import {addKeyHandler, removeKeyHandler} from '../../utils/ShortkeyHandler';
-import {refreshAPIToken} from '../../utils/Api';
+import {token} from '../../utils/Api';
import {faTimes} from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
@@ -76,7 +76,7 @@ class AuthenticationPage extends React.Component {
* request a new token and check if pwd was valid
*/
authenticate(): void {
- refreshAPIToken(
+ token.refreshAPIToken(
(error) => {
if (error !== '') {
this.setState({wrongPWDInfo: true});
diff --git a/src/setupTests.js b/src/setupTests.js
index 47cf44f..0bedd82 100644
--- a/src/setupTests.js
+++ b/src/setupTests.js
@@ -7,6 +7,8 @@ import '@testing-library/jest-dom/extend-expect';
import {configure} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import GlobalInfos from './utils/GlobalInfos';
+import {CookieTokenStore} from "./utils/TokenStore/CookieTokenStore";
+import {token} from "./utils/Api";
configure({adapter: new Adapter()});
@@ -44,6 +46,7 @@ global.callAPIMock = (resonse) => {
global.beforeEach(() => {
// empty fetch response implementation for each test
global.fetch = prepareFetchApi({});
+ token.setTokenStore(new CookieTokenStore());
// todo with callAPIMock
});
diff --git a/src/utils/Api.ts b/src/utils/Api.ts
index a7c27b3..c126639 100644
--- a/src/utils/Api.ts
+++ b/src/utils/Api.ts
@@ -1,4 +1,5 @@
import GlobalInfos from './GlobalInfos';
+import {TokenStore} from './TokenStore/TokenStore';
const APIPREFIX: string = '/api/';
@@ -11,165 +12,127 @@ interface ApiBaseRequest {
[_: string]: string | number | boolean | object;
}
-// store api token - empty if not set
-let apiToken = '';
+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;
+ // 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;
-/**
- * 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);
+ let tokenStore: TokenStore;
- // check if already is a token refresh is in process
- if (refreshInProcess) {
- // if yes return
- return;
- } else {
- // if not set flat
- refreshInProcess = true;
+ export function setTokenStore(ts: TokenStore): void {
+ tokenStore = ts;
}
- if (apiTokenValid() && !force) {
- console.log('token still valid...');
- callFuncQue('');
- return;
- }
+ /**
+ * 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);
- 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');
+ // check if already is a token refresh is in process
+ if (refreshInProcess) {
+ // if yes return
+ return;
+ } else {
+ // if not set flat
+ refreshInProcess = true;
+ }
- 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);
- // call all handlers and release flag
+ if (apiTokenValid() && !force) {
+ console.log('token still valid...');
callFuncQue('');
- })
- );
-}
-
-export function apiTokenValid(): boolean {
- // check if a cookie with token is available
- const token = getTokenCookie();
- if (token !== null) {
- // check if token is at least valid for the next minute
- if (token.expire > new Date().getTime() / 1000 + 60) {
- apiToken = token.token;
- expireSeconds = token.expire;
-
- return true;
+ return;
}
- }
- 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;
-}
+ 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');
-/**
- * set the cookie for the currently gotten token
- * @param token token string
- * @param validSec second time when the token will be invalid
- */
-function setTokenCookie(token: string, validSec: number): void {
- let d = new Date();
- d.setTime(validSec * 1000);
- console.log('token set' + d.toUTCString());
- let expires = 'expires=' + d.toUTCString();
- document.cookie = 'token=' + token + ';' + expires + ';path=/';
- document.cookie = 'token_expire=' + validSec + ';' + expires + ';path=/';
-}
-
-/**
- * get all required cookies for the token
- */
-function getTokenCookie(): {token: string; expire: number} | null {
- const token = decodeCookie('token');
- const expireInString = decodeCookie('token_expire');
- const expireIn = parseInt(expireInString, 10);
-
- if (expireIn !== 0 && token !== '') {
- return {token: token, expire: expireIn};
- } else {
- return null;
- }
-}
-
-/**
- * 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);
+ 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
}
- if (c.indexOf(name) === 0) {
- return c.substring(name.length, c.length);
- }
- }
- return '';
-}
-/**
- * check if api token is valid -- if not request new one
- * when finished call callback
- * @param callback function to be called afterwards
- */
-function checkAPITokenValid(callback: () => void): void {
- // check if token is valid and set
- if (apiToken === '' || expireSeconds <= new Date().getTime() / 1000) {
- refreshAPIToken(() => {
- callback();
+ 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);
});
- } else {
- callback();
+ // 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);
+ }
}
}
@@ -186,13 +149,13 @@ export function callAPI(
callback: (_: T) => void,
errorcallback: (_: string) => void = (_: string): void => {}
): void {
- checkAPITokenValid(() => {
+ token.checkAPITokenValid((mytoken) => {
fetch(APIPREFIX + apinode, {
method: 'POST',
body: JSON.stringify(fd),
headers: new Headers({
'Content-Type': 'application/json',
- Authorization: 'Bearer ' + apiToken
+ Authorization: 'Bearer ' + mytoken
})
})
.then((response) => {
@@ -252,13 +215,13 @@ export function callApiUnsafe(
* @param callback the callback with PLAIN text reply from backend
*/
export function callAPIPlain(apinode: APINode, fd: ApiBaseRequest, callback: (_: string) => void): void {
- checkAPITokenValid(() => {
+ token.checkAPITokenValid((mytoken) => {
fetch(APIPREFIX + apinode, {
method: 'POST',
body: JSON.stringify(fd),
headers: new Headers({
'Content-Type': 'application/json',
- Authorization: 'Bearer ' + apiToken
+ Authorization: 'Bearer ' + mytoken
})
}).then((response) =>
response.text().then((result) => {
diff --git a/src/utils/TokenStore/CookieTokenStore.ts b/src/utils/TokenStore/CookieTokenStore.ts
new file mode 100644
index 0000000..efdff79
--- /dev/null
+++ b/src/utils/TokenStore/CookieTokenStore.ts
@@ -0,0 +1,48 @@
+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 '';
+ }
+}
diff --git a/src/utils/TokenStore/TokenStore.ts b/src/utils/TokenStore/TokenStore.ts
new file mode 100644
index 0000000..7d36fb4
--- /dev/null
+++ b/src/utils/TokenStore/TokenStore.ts
@@ -0,0 +1,11 @@
+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;
+}