Skip to content

Commit 7a8c86b

Browse files
Clear tokens and store expiration time.
1 parent 91ebd41 commit 7a8c86b

File tree

2 files changed

+88
-47
lines changed

2 files changed

+88
-47
lines changed

src/wrapper/SquidexClient.ts

Lines changed: 87 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -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

4931
export 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
}

tests/globalSetup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { getEnvironment, getClient, delay } from "./_utils";
44
async function setup() {
55
console.log('SETUP');
66

7-
const { client } = getClient(true);
7+
const { client } = getClient();
88

99
const waitTime = parseInt(getEnvironment("CONFIG__WAIT", "0"), 10);
1010

0 commit comments

Comments
 (0)