@@ -14,39 +14,24 @@ export declare namespace SquidexClient {
1414 }
1515
1616 export interface TokenStore {
17- get ( ) : string | undefined ;
18-
19- set ( token : string ) : void ;
20- }
21- }
17+ get ( ) : Token | undefined ;
2218
19+ set ( token : Token ) : void ;
2320
24- export class SquidexInMemoryTokenStore implements SquidexClient . TokenStore {
25- private token : string | undefined ;
26-
27- get ( ) : string | undefined {
28- return this . token ;
21+ clear ( ) : void ;
2922 }
3023
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 ) ;
24+ export interface Token {
25+ accessToken : string ;
26+ expiresIn : number ;
27+ expiresAt : number ;
4628 }
4729}
4830
4931export class SquidexClient extends FernClient {
32+ private tokenPromise ?: Promise < string > ;
33+ private tokenStore ?: SquidexClient . TokenStore ;
34+
5035 public get appName ( ) {
5136 return this . options . appName ;
5237 }
@@ -63,41 +48,44 @@ export class SquidexClient extends FernClient {
6348 return this . clientOptions . environment || environments . SquidexEnvironment . Default ;
6449 }
6550
51+ private get actualTokenStore ( ) {
52+ return this . tokenStore ||= ( this . clientOptions . tokenStore || new SquidexClient . InMemoryTokenStore ( ) ) ;
53+ }
54+
6655 constructor ( readonly clientOptions : SquidexClient . Options ) {
6756 super ( {
68- environment : clientOptions . environment ,
6957 appName : clientOptions . appName ,
70- token : buildTokenResolver ( clientOptions )
58+ token : ( ) => {
59+ return this . getToken ( ) ;
60+ } ,
61+ environment : clientOptions . environment
7162 } ) ;
7263 }
73- }
7464
75- function buildTokenResolver ( options : SquidexClient . Options ) {
76- const store = options . tokenStore || new SquidexInMemoryTokenStore ( ) ;
77-
78- const cachedPromise : {
79- promise ?: Promise < string >
80- } = { } ;
65+ clearToken ( ) {
66+ this . actualTokenStore . clear ( ) ;
67+ }
8168
82- return ( ) => {
83- const promise = ( cachedPromise . promise ||= ( async ( ) => {
69+ private async getToken ( ) {
70+ const promise = ( this . tokenPromise ||= ( async ( ) => {
71+ const now = new Date ( ) . getTime ( ) ;
8472 try {
85- let token = store . get ( ) ;
73+ let token = this . actualTokenStore . get ( ) ;
8674
87- if ( token != null ) {
88- return token ;
75+ if ( token != null && token . expiresAt > now ) {
76+ return token . accessToken ;
8977 }
9078
9179 const response = await core . fetcher ( {
9280 url : urlJoin (
93- options . environment ?? environments . SquidexEnvironment . Default ,
81+ this . clientOptions . environment ?? environments . SquidexEnvironment . Default ,
9482 "/identity-server/connect/token"
9583 ) ,
9684 contentType : "application/x-www-form-urlencoded" ,
9785 body : new URLSearchParams ( {
9886 grant_type : "client_credentials" ,
99- client_id : options . clientId ,
100- client_secret : options . clientSecret ,
87+ client_id : this . clientOptions . clientId ,
88+ client_secret : this . clientOptions . clientSecret ,
10189 scope : "squidex-api" ,
10290 } ) ,
10391 method : "POST" ,
@@ -110,7 +98,19 @@ function buildTokenResolver(options: SquidexClient.Options) {
11098 message : "Token is not a string" ,
11199 } ) ;
112100 }
113- token = accessToken ;
101+
102+ const expiresIn : number = ( response . body as any ) ?. [ "expires_in" ] ;
103+ if ( typeof expiresIn !== "number" ) {
104+ throw new errors . SquidexError ( {
105+ message : "Token has no valid expiration" ,
106+ } ) ;
107+ }
108+
109+ token = {
110+ accessToken,
111+ expiresIn,
112+ expiresAt : now + expiresIn
113+ } ;
114114 } else {
115115 switch ( response . error . reason ) {
116116 case "non-json" :
@@ -134,20 +134,61 @@ function buildTokenResolver(options: SquidexClient.Options) {
134134 }
135135 }
136136
137- store . set ( token ) ;
137+ this . actualTokenStore . set ( token ) ;
138138
139139 if ( token == null ) {
140140 throw new errors . SquidexError ( {
141141 message : "Token is null despite trying to fetch" ,
142142 } ) ;
143143 }
144144
145- return token ;
145+ return token . accessToken ;
146146 } finally {
147- cachedPromise . promise = undefined ;
147+ this . tokenPromise = undefined ;
148148 }
149149 } ) ( ) ) ;
150150
151151 return promise ;
152+ } ;
153+ }
154+
155+ export namespace SquidexClient {
156+ export class InMemoryTokenStore implements TokenStore {
157+ private token : Token | undefined ;
158+
159+ get ( ) : Token | undefined {
160+ return this . token ;
161+ }
162+
163+ set ( token : Token ) : void {
164+ this . token = token ;
165+ }
166+
167+ clear ( ) {
168+ this . token = undefined ;
169+ }
170+ }
171+
172+ export class StorageTokenStore implements TokenStore {
173+ constructor ( readonly store : Storage = localStorage , readonly key = 'SquidexToken' ) {
174+ }
175+
176+ get ( ) : Token | undefined {
177+ const value = this . store . getItem ( this . key ) ;
178+
179+ if ( ! value ) {
180+ return undefined ;
181+ }
182+
183+ return JSON . parse ( value ) ;
184+ }
185+
186+ set ( token : Token ) : void {
187+ this . store . setItem ( this . key , JSON . stringify ( token ) ) ;
188+ }
189+
190+ clear ( ) {
191+ this . store . removeItem ( this . key ) ;
192+ }
152193 }
153194}
0 commit comments