@@ -10,12 +10,43 @@ export declare namespace SquidexClient {
1010 clientSecret : string ;
1111 environment ?: environments . SquidexEnvironment | string ;
1212 appName : string ;
13+ tokenStore ?: TokenStore ;
14+ }
15+
16+ export interface TokenStore {
17+ get ( ) : string | undefined ;
18+
19+ set ( token : string ) : void ;
1320 }
1421}
1522
16- export class SquidexClient extends FernClient {
23+
24+ export class SquidexInMemoryTokenStore implements SquidexClient . TokenStore {
1725 private token : string | undefined ;
1826
27+ get ( ) : string | undefined {
28+ return this . token ;
29+ }
30+
31+ set ( token : string ) : void {
32+ this . token = token ;
33+ }
34+ }
35+
36+ export class SquidexStorageTokenStore implements SquidexClient . TokenStore {
37+ constructor ( readonly store : Storage = localStorage , readonly key = 'SquidexToken' ) {
38+ }
39+
40+ get ( ) : string | undefined {
41+ return this . store . getItem ( this . key ) || undefined ;
42+ }
43+
44+ set ( token : string ) : void {
45+ this . store . setItem ( this . key , token ) ;
46+ }
47+ }
48+
49+ export class SquidexClient extends FernClient {
1950 public get appName ( ) {
2051 return this . options . appName ;
2152 }
@@ -36,60 +67,87 @@ export class SquidexClient extends FernClient {
3667 super ( {
3768 environment : clientOptions . environment ,
3869 appName : clientOptions . appName ,
39- token : async ( ) => {
40- if ( this . token == null ) {
41- const response = await core . fetcher ( {
42- url : urlJoin (
43- this . options . environment ?? environments . SquidexEnvironment . Default ,
44- "/identity-server/connect/token"
45- ) ,
46- contentType : "application/x-www-form-urlencoded" ,
47- method : "POST" ,
48- body : new URLSearchParams ( {
49- grant_type : "client_credentials" ,
50- client_id : clientOptions . clientId ,
51- client_secret : clientOptions . clientSecret ,
52- scope : "squidex-api" ,
53- } ) ,
54- } ) ;
55- if ( response . ok ) {
56- const accessToken = ( response . body as any ) ?. [ "access_token" ] ;
57- if ( typeof accessToken !== "string" ) {
70+ token : buildTokenResolver ( clientOptions )
71+ } ) ;
72+ }
73+ }
74+
75+ function buildTokenResolver ( options : SquidexClient . Options ) {
76+ const store = options . tokenStore || new SquidexInMemoryTokenStore ( ) ;
77+
78+ const cachedPromise : {
79+ promise ?: Promise < string >
80+ } = { } ;
81+
82+ return ( ) => {
83+ const promise = ( cachedPromise . promise ||= ( async ( ) => {
84+ try {
85+ let token = store . get ( ) ;
86+
87+ if ( token != null ) {
88+ return token ;
89+ }
90+
91+ const response = await core . fetcher ( {
92+ url : urlJoin (
93+ options . environment ?? environments . SquidexEnvironment . Default ,
94+ "/identity-server/connect/token"
95+ ) ,
96+ contentType : "application/x-www-form-urlencoded" ,
97+ body : new URLSearchParams ( {
98+ grant_type : "client_credentials" ,
99+ client_id : options . clientId ,
100+ client_secret : options . clientSecret ,
101+ scope : "squidex-api" ,
102+ } ) ,
103+ method : "POST" ,
104+ } ) ;
105+
106+ if ( response . ok ) {
107+ const accessToken = ( response . body as any ) ?. [ "access_token" ] ;
108+ if ( typeof accessToken !== "string" ) {
109+ throw new errors . SquidexError ( {
110+ message : "Token is not a string" ,
111+ } ) ;
112+ }
113+ token = accessToken ;
114+ } else {
115+ switch ( response . error . reason ) {
116+ case "non-json" :
58117 throw new errors . SquidexError ( {
59- message : "Token is not a string" ,
118+ message : 'Token request does not return a valid JSON object.' ,
119+ statusCode : response . error . statusCode ,
120+ body : response . error . rawBody ,
60121 } ) ;
61- }
62- this . token = accessToken ;
63- } else {
64- switch ( response . error . reason ) {
65- case "non-json" :
66- throw new errors . SquidexError ( {
67- message : 'Token request does not return a valid JSON object.' ,
68- statusCode : response . error . statusCode ,
69- body : response . error . rawBody ,
70- } ) ;
71- case "status-code" :
72- throw new errors . SquidexError ( {
73- message : `Token request returns invalid status code: ${ response . error . statusCode } .` ,
74- statusCode : response . error . statusCode ,
75- body : response . error [ 'body' ] ,
76- } ) ;
77- case "unknown" :
78- throw new errors . SquidexError ( {
79- message : response . error . errorMessage ,
80- } ) ;
81- case "timeout" :
82- throw new errors . SquidexTimeoutError ( ) ;
83- }
122+ case "status-code" :
123+ throw new errors . SquidexError ( {
124+ message : `Token request returns invalid status code: ${ response . error . statusCode } .` ,
125+ statusCode : response . error . statusCode ,
126+ body : response . error [ 'body' ] ,
127+ } ) ;
128+ case "unknown" :
129+ throw new errors . SquidexError ( {
130+ message : response . error . errorMessage ,
131+ } ) ;
132+ case "timeout" :
133+ throw new errors . SquidexTimeoutError ( ) ;
84134 }
85135 }
86- if ( this . token == null ) {
136+
137+ store . set ( token ) ;
138+
139+ if ( token == null ) {
87140 throw new errors . SquidexError ( {
88141 message : "Token is null despite trying to fetch" ,
89142 } ) ;
90143 }
91- return this . token ;
92- } ,
93- } ) ;
144+
145+ return token ;
146+ } finally {
147+ cachedPromise . promise = undefined ;
148+ }
149+ } ) ( ) ) ;
150+
151+ return promise ;
94152 }
95- }
153+ }
0 commit comments