abstract tokenstore to support different storage methods of tokenstore
This commit is contained in:
parent
ab02a49b8f
commit
0797632c44
11
src/App.tsx
11
src/App.tsx
@ -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, 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 {BrowserRouter as Router, NavLink, Route, Switch} from 'react-router-dom';
|
||||||
import Player from './pages/Player/Player';
|
import Player from './pages/Player/Player';
|
||||||
@ -19,6 +19,7 @@ import {SettingsTypes} from './types/ApiTypes';
|
|||||||
import AuthenticationPage from './pages/AuthenticationPage/AuthenticationPage';
|
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';
|
||||||
|
|
||||||
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
|
||||||
@ -32,12 +33,14 @@ class App extends React.Component<{}, state> {
|
|||||||
constructor(props: {}) {
|
constructor(props: {}) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
token.setTokenStore(new CookieTokenStore());
|
||||||
|
|
||||||
let pwdneeded: boolean | null = null;
|
let pwdneeded: boolean | null = null;
|
||||||
|
|
||||||
if (apiTokenValid()) {
|
if (token.apiTokenValid()) {
|
||||||
pwdneeded = false;
|
pwdneeded = false;
|
||||||
} else {
|
} else {
|
||||||
refreshAPIToken((err) => {
|
token.refreshAPIToken((err) => {
|
||||||
if (err === 'invalid_client') {
|
if (err === 'invalid_client') {
|
||||||
this.setState({password: true});
|
this.setState({password: true});
|
||||||
} else if (err === '') {
|
} else if (err === '') {
|
||||||
@ -61,7 +64,7 @@ class App extends React.Component<{}, state> {
|
|||||||
// set the hook to load passwordfield on global func call
|
// set the hook to load passwordfield on global func call
|
||||||
GlobalInfos.loadPasswordPage = (callback?: () => void): void => {
|
GlobalInfos.loadPasswordPage = (callback?: () => void): void => {
|
||||||
// try refreshing the token
|
// try refreshing the token
|
||||||
refreshAPIToken((err) => {
|
token.refreshAPIToken((err) => {
|
||||||
if (err !== '') {
|
if (err !== '') {
|
||||||
this.setState({password: true});
|
this.setState({password: true});
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import AuthenticationPage from './AuthenticationPage';
|
import AuthenticationPage from './AuthenticationPage';
|
||||||
import {shallow} from 'enzyme';
|
import {shallow} from 'enzyme';
|
||||||
|
import {token} from "../../utils/Api";
|
||||||
|
|
||||||
describe('<AuthenticationPage/>', function () {
|
describe('<AuthenticationPage/>', function () {
|
||||||
it('renders without crashing ', function () {
|
it('renders without crashing ', function () {
|
||||||
@ -25,8 +26,7 @@ describe('<AuthenticationPage/>', function () {
|
|||||||
it('test fail authenticate', function () {
|
it('test fail authenticate', function () {
|
||||||
const events = mockKeyPress();
|
const events = mockKeyPress();
|
||||||
|
|
||||||
const helpers = require('../../utils/Api');
|
token.refreshAPIToken = jest.fn().mockImplementation((callback, force, pwd) => {
|
||||||
helpers.refreshAPIToken = jest.fn().mockImplementation((callback, force, pwd) => {
|
|
||||||
callback('there was an error')
|
callback('there was an error')
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -41,8 +41,7 @@ describe('<AuthenticationPage/>', function () {
|
|||||||
const events = mockKeyPress();
|
const events = mockKeyPress();
|
||||||
const func = jest.fn()
|
const func = jest.fn()
|
||||||
|
|
||||||
const helpers = require('../../utils/Api');
|
token.refreshAPIToken = jest.fn().mockImplementation((callback, force, pwd) => {
|
||||||
helpers.refreshAPIToken = jest.fn().mockImplementation((callback, force, pwd) => {
|
|
||||||
callback('')
|
callback('')
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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 {refreshAPIToken} from '../../utils/Api';
|
import {token} from '../../utils/Api';
|
||||||
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';
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ class AuthenticationPage extends React.Component<Props, state> {
|
|||||||
* request a new token and check if pwd was valid
|
* request a new token and check if pwd was valid
|
||||||
*/
|
*/
|
||||||
authenticate(): void {
|
authenticate(): void {
|
||||||
refreshAPIToken(
|
token.refreshAPIToken(
|
||||||
(error) => {
|
(error) => {
|
||||||
if (error !== '') {
|
if (error !== '') {
|
||||||
this.setState({wrongPWDInfo: true});
|
this.setState({wrongPWDInfo: true});
|
||||||
|
@ -7,6 +7,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 GlobalInfos from './utils/GlobalInfos';
|
||||||
|
import {CookieTokenStore} from "./utils/TokenStore/CookieTokenStore";
|
||||||
|
import {token} from "./utils/Api";
|
||||||
|
|
||||||
configure({adapter: new Adapter()});
|
configure({adapter: new Adapter()});
|
||||||
|
|
||||||
@ -44,6 +46,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());
|
||||||
// todo with callAPIMock
|
// todo with callAPIMock
|
||||||
});
|
});
|
||||||
|
|
||||||
|
261
src/utils/Api.ts
261
src/utils/Api.ts
@ -1,4 +1,5 @@
|
|||||||
import GlobalInfos from './GlobalInfos';
|
import GlobalInfos from './GlobalInfos';
|
||||||
|
import {TokenStore} from './TokenStore/TokenStore';
|
||||||
|
|
||||||
const APIPREFIX: string = '/api/';
|
const APIPREFIX: string = '/api/';
|
||||||
|
|
||||||
@ -11,165 +12,127 @@ interface ApiBaseRequest {
|
|||||||
[_: string]: string | number | boolean | object;
|
[_: string]: string | number | boolean | object;
|
||||||
}
|
}
|
||||||
|
|
||||||
// store api token - empty if not set
|
export namespace token {
|
||||||
let apiToken = '';
|
// store api token - empty if not set
|
||||||
|
let apiToken = '';
|
||||||
|
|
||||||
// a callback que to be called after api token refresh
|
// a callback que to be called after api token refresh
|
||||||
let callQue: ((error: string) => void)[] = [];
|
let callQue: ((error: string) => void)[] = [];
|
||||||
// flag to check wheter a api refresh is currently pending
|
// flag to check wheter a api refresh is currently pending
|
||||||
let refreshInProcess = false;
|
let refreshInProcess = false;
|
||||||
// store the expire seconds of token
|
// store the expire seconds of token
|
||||||
let expireSeconds = -1;
|
let expireSeconds = -1;
|
||||||
|
|
||||||
/**
|
let tokenStore: TokenStore;
|
||||||
* 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
|
export function setTokenStore(ts: TokenStore): void {
|
||||||
if (refreshInProcess) {
|
tokenStore = ts;
|
||||||
// if yes return
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
// if not set flat
|
|
||||||
refreshInProcess = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (apiTokenValid() && !force) {
|
/**
|
||||||
console.log('token still valid...');
|
* refresh the api token or use that one in cookie if still valid
|
||||||
callFuncQue('');
|
* @param callback to be called after successful refresh
|
||||||
return;
|
* @param password
|
||||||
}
|
* @param force
|
||||||
|
*/
|
||||||
|
export function refreshAPIToken(callback: (error: string) => void, force?: boolean, password?: string): void {
|
||||||
|
callQue.push(callback);
|
||||||
|
|
||||||
const formData = new FormData();
|
// check if already is a token refresh is in process
|
||||||
formData.append('grant_type', 'client_credentials');
|
if (refreshInProcess) {
|
||||||
formData.append('client_id', 'openmediacenter');
|
// if yes return
|
||||||
formData.append('client_secret', password ? password : 'openmediacenter');
|
return;
|
||||||
formData.append('scope', 'all');
|
} else {
|
||||||
|
// if not set flat
|
||||||
|
refreshInProcess = true;
|
||||||
|
}
|
||||||
|
|
||||||
interface APIToken {
|
if (apiTokenValid() && !force) {
|
||||||
error?: string;
|
console.log('token still valid...');
|
||||||
// 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
|
|
||||||
callFuncQue('');
|
callFuncQue('');
|
||||||
})
|
return;
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
const formData = new FormData();
|
||||||
* call all qued callbacks
|
formData.append('grant_type', 'client_credentials');
|
||||||
*/
|
formData.append('client_id', 'openmediacenter');
|
||||||
function callFuncQue(error: string): void {
|
formData.append('client_secret', password ? password : 'openmediacenter');
|
||||||
// call all pending handlers
|
formData.append('scope', 'all');
|
||||||
callQue.map((func) => {
|
|
||||||
return func(error);
|
|
||||||
});
|
|
||||||
// reset pending que
|
|
||||||
callQue = [];
|
|
||||||
// release flag to be able to start new refresh
|
|
||||||
refreshInProcess = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
interface APIToken {
|
||||||
* set the cookie for the currently gotten token
|
error?: string;
|
||||||
* @param token token string
|
// eslint-disable-next-line camelcase
|
||||||
* @param validSec second time when the token will be invalid
|
access_token: string; // no camel case allowed because of backendlib
|
||||||
*/
|
// eslint-disable-next-line camelcase
|
||||||
function setTokenCookie(token: string, validSec: number): void {
|
expires_in: number; // no camel case allowed because of backendlib
|
||||||
let d = new Date();
|
scope: string;
|
||||||
d.setTime(validSec * 1000);
|
// eslint-disable-next-line camelcase
|
||||||
console.log('token set' + d.toUTCString());
|
token_type: string; // no camel case allowed because of backendlib
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
if (c.indexOf(name) === 0) {
|
|
||||||
return c.substring(name.length, c.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
fetch('/token', {method: 'POST', body: formData}).then((response) =>
|
||||||
* check if api token is valid -- if not request new one
|
response.json().then((result: APIToken) => {
|
||||||
* when finished call callback
|
if (result.error) {
|
||||||
* @param callback function to be called afterwards
|
callFuncQue(result.error);
|
||||||
*/
|
return;
|
||||||
function checkAPITokenValid(callback: () => void): void {
|
}
|
||||||
// check if token is valid and set
|
// set api token
|
||||||
if (apiToken === '' || expireSeconds <= new Date().getTime() / 1000) {
|
apiToken = result.access_token;
|
||||||
refreshAPIToken(() => {
|
// set expire time
|
||||||
callback();
|
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 {
|
// reset pending que
|
||||||
callback();
|
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<T>(
|
|||||||
callback: (_: T) => void,
|
callback: (_: T) => void,
|
||||||
errorcallback: (_: string) => void = (_: string): void => {}
|
errorcallback: (_: string) => void = (_: string): void => {}
|
||||||
): void {
|
): void {
|
||||||
checkAPITokenValid(() => {
|
token.checkAPITokenValid((mytoken) => {
|
||||||
fetch(APIPREFIX + apinode, {
|
fetch(APIPREFIX + apinode, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(fd),
|
body: JSON.stringify(fd),
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Authorization: 'Bearer ' + apiToken
|
Authorization: 'Bearer ' + mytoken
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
@ -252,13 +215,13 @@ export function callApiUnsafe<T>(
|
|||||||
* @param callback the callback with PLAIN text reply from backend
|
* @param callback the callback with PLAIN text reply from backend
|
||||||
*/
|
*/
|
||||||
export function callAPIPlain(apinode: APINode, fd: ApiBaseRequest, callback: (_: string) => void): void {
|
export function callAPIPlain(apinode: APINode, fd: ApiBaseRequest, callback: (_: string) => void): void {
|
||||||
checkAPITokenValid(() => {
|
token.checkAPITokenValid((mytoken) => {
|
||||||
fetch(APIPREFIX + apinode, {
|
fetch(APIPREFIX + apinode, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(fd),
|
body: JSON.stringify(fd),
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Authorization: 'Bearer ' + apiToken
|
Authorization: 'Bearer ' + mytoken
|
||||||
})
|
})
|
||||||
}).then((response) =>
|
}).then((response) =>
|
||||||
response.text().then((result) => {
|
response.text().then((result) => {
|
||||||
|
48
src/utils/TokenStore/CookieTokenStore.ts
Normal file
48
src/utils/TokenStore/CookieTokenStore.ts
Normal file
@ -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 '';
|
||||||
|
}
|
||||||
|
}
|
11
src/utils/TokenStore/TokenStore.ts
Normal file
11
src/utils/TokenStore/TokenStore.ts
Normal file
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user