@flexbe/sdk 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/client/api-client.js +4 -4
- package/dist/browser/client/auth.js +24 -68
- package/dist/browser/client/client.js +2 -1
- package/dist/browser/client/pages.js +18 -0
- package/dist/browser/client/token-manager.js +179 -73
- package/dist/browser/types/index.js +5 -1
- package/dist/cjs/client/api-client.js +4 -4
- package/dist/cjs/client/auth.js +24 -68
- package/dist/cjs/client/client.js +2 -1
- package/dist/cjs/client/pages.js +14 -0
- package/dist/cjs/client/token-manager.js +167 -73
- package/dist/cjs/types/index.js +6 -0
- package/dist/esm/client/api-client.js +4 -4
- package/dist/esm/client/auth.js +24 -68
- package/dist/esm/client/client.js +2 -1
- package/dist/esm/client/pages.js +14 -0
- package/dist/esm/client/token-manager.js +167 -73
- package/dist/esm/types/index.js +5 -1
- package/dist/types/client/api-client.d.ts +4 -4
- package/dist/types/client/auth.d.ts +2 -3
- package/dist/types/client/pages.d.ts +9 -1
- package/dist/types/client/token-manager.d.ts +9 -1
- package/dist/types/types/index.d.ts +9 -1
- package/dist/types/types/pages.d.ts +12 -7
- package/package.json +1 -1
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
const TOKEN_STORAGE_KEY = 'flexbe_jwt_token';
|
|
2
2
|
const REFRESH_THRESHOLD = 0.8; // Refresh when 80% of token lifetime has passed
|
|
3
|
+
const REFRESH_CHECK_INTERVAL = 30 * 1000; // Check every 30 seconds
|
|
4
|
+
const MAX_REFRESH_DELAY = 10000; // Maximum random delay of 10 seconds
|
|
3
5
|
export class TokenManager {
|
|
4
6
|
constructor() {
|
|
5
7
|
this.token = null;
|
|
6
8
|
this.refreshInterval = null;
|
|
9
|
+
this.refreshTimeout = null;
|
|
10
|
+
this.tokenPromise = null;
|
|
11
|
+
this.debug = false;
|
|
7
12
|
this.initializeFromStorage();
|
|
8
13
|
this.setupStorageListener();
|
|
9
14
|
}
|
|
@@ -14,59 +19,61 @@ export class TokenManager {
|
|
|
14
19
|
return TokenManager.instance;
|
|
15
20
|
}
|
|
16
21
|
initializeFromStorage() {
|
|
17
|
-
if (typeof window
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
this.clearToken();
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
catch (error) {
|
|
34
|
-
console.error('Failed to parse stored token:', error);
|
|
35
|
-
this.clearToken();
|
|
36
|
-
}
|
|
22
|
+
if (typeof window === 'undefined')
|
|
23
|
+
return;
|
|
24
|
+
const storedToken = localStorage.getItem(TOKEN_STORAGE_KEY);
|
|
25
|
+
if (!storedToken)
|
|
26
|
+
return;
|
|
27
|
+
try {
|
|
28
|
+
this.token = JSON.parse(storedToken);
|
|
29
|
+
if (this.token.expiresAt > Date.now()) {
|
|
30
|
+
this.logTokenStatus('Token loaded from storage');
|
|
31
|
+
this.startRefreshInterval();
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
this.clearToken();
|
|
37
35
|
}
|
|
38
36
|
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
console.error('Failed to parse stored token:', error);
|
|
39
|
+
this.clearToken();
|
|
40
|
+
}
|
|
39
41
|
}
|
|
40
42
|
setupStorageListener() {
|
|
41
|
-
if (typeof window
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
console.error('Failed to parse token from storage event:', error);
|
|
61
|
-
this.clearToken();
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
this.clearToken();
|
|
66
|
-
}
|
|
43
|
+
if (typeof window === 'undefined')
|
|
44
|
+
return;
|
|
45
|
+
window.addEventListener('storage', (event) => {
|
|
46
|
+
if (event.key !== TOKEN_STORAGE_KEY)
|
|
47
|
+
return;
|
|
48
|
+
if (!event.newValue) {
|
|
49
|
+
this.clearToken();
|
|
50
|
+
void this.retrieveToken();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const newToken = JSON.parse(event.newValue);
|
|
55
|
+
// Skip if the new token is exactly the same as current token
|
|
56
|
+
if (this.token &&
|
|
57
|
+
this.token.accessToken === newToken.accessToken &&
|
|
58
|
+
this.token.expiresAt === newToken.expiresAt) {
|
|
59
|
+
return;
|
|
67
60
|
}
|
|
68
|
-
|
|
69
|
-
|
|
61
|
+
if (newToken.expiresAt > Date.now()) {
|
|
62
|
+
this.token = newToken;
|
|
63
|
+
this.logTokenStatus('Token updated from storage');
|
|
64
|
+
this.startRefreshInterval();
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
this.clearToken();
|
|
68
|
+
void this.retrieveToken();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
console.error('Failed to parse token from storage event:', error);
|
|
73
|
+
this.clearToken();
|
|
74
|
+
void this.retrieveToken();
|
|
75
|
+
}
|
|
76
|
+
});
|
|
70
77
|
}
|
|
71
78
|
getExpirationFromToken(token) {
|
|
72
79
|
try {
|
|
@@ -79,32 +86,129 @@ export class TokenManager {
|
|
|
79
86
|
return Date.now() + (4 * 60 * 1000); // Default to 4 minutes if parsing fails
|
|
80
87
|
}
|
|
81
88
|
}
|
|
89
|
+
async getToken() {
|
|
90
|
+
const token = this.token;
|
|
91
|
+
if (token && token.expiresAt && token.expiresAt > Date.now()) {
|
|
92
|
+
return token.accessToken;
|
|
93
|
+
}
|
|
94
|
+
await this.retrieveToken();
|
|
95
|
+
return this.token?.accessToken ?? null;
|
|
96
|
+
}
|
|
97
|
+
async retrieveToken() {
|
|
98
|
+
if (this.tokenPromise) {
|
|
99
|
+
await this.tokenPromise;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
this.tokenPromise = this.doRetrieveToken();
|
|
103
|
+
try {
|
|
104
|
+
await this.tokenPromise;
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
this.tokenPromise = null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async doRetrieveToken() {
|
|
111
|
+
const controller = new AbortController();
|
|
112
|
+
const timeoutId = setTimeout(() => controller.abort(), 30000);
|
|
113
|
+
try {
|
|
114
|
+
const response = await fetch('/oauth/token', {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
headers: { 'Content-Type': 'application/json' },
|
|
117
|
+
body: JSON.stringify({ grant_type: 'client_credentials' }),
|
|
118
|
+
credentials: 'include',
|
|
119
|
+
signal: controller.signal,
|
|
120
|
+
});
|
|
121
|
+
clearTimeout(timeoutId);
|
|
122
|
+
if (!response.ok) {
|
|
123
|
+
const errorData = await response.json().catch(() => ({ message: response.statusText }));
|
|
124
|
+
throw new Error(errorData.message || response.statusText);
|
|
125
|
+
}
|
|
126
|
+
const data = await response.json();
|
|
127
|
+
this.setToken(data);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
console.error('Failed to retrieve token:', error);
|
|
131
|
+
this.clearToken();
|
|
132
|
+
// Schedule a retry after REFRESH_CHECK_INTERVAL
|
|
133
|
+
setTimeout(() => {
|
|
134
|
+
void this.retrieveToken();
|
|
135
|
+
}, REFRESH_CHECK_INTERVAL);
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
82
139
|
startRefreshInterval() {
|
|
140
|
+
this.clearRefreshTimers();
|
|
141
|
+
if (!this.token)
|
|
142
|
+
return;
|
|
143
|
+
const tokenLifetime = this.token.expiresAt - Date.now();
|
|
144
|
+
const refreshThreshold = Math.round(tokenLifetime * REFRESH_THRESHOLD);
|
|
145
|
+
const timeUntilRefresh = refreshThreshold - (Math.random() * MAX_REFRESH_DELAY);
|
|
146
|
+
this.logTokenStatus('Starting refresh interval', {
|
|
147
|
+
tokenLifetime: `${Math.round(tokenLifetime / 1000)} seconds`,
|
|
148
|
+
refreshThreshold: `${Math.round(refreshThreshold / 1000)} seconds`,
|
|
149
|
+
timeUntilRefresh: `${Math.round(timeUntilRefresh / 1000)} seconds`,
|
|
150
|
+
});
|
|
151
|
+
this.scheduleRefresh(timeUntilRefresh);
|
|
152
|
+
this.refreshInterval = window.setInterval(() => {
|
|
153
|
+
if (!this.token)
|
|
154
|
+
return;
|
|
155
|
+
const timeUntilExpiry = this.token.expiresAt - Date.now();
|
|
156
|
+
if (timeUntilExpiry <= 0) {
|
|
157
|
+
this.logTokenStatus('Token expired');
|
|
158
|
+
this.clearToken();
|
|
159
|
+
void this.retrieveToken();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const refreshThreshold = Math.round(timeUntilExpiry * REFRESH_THRESHOLD);
|
|
163
|
+
if (timeUntilExpiry <= refreshThreshold) {
|
|
164
|
+
this.logTokenStatus('Refreshing token', {
|
|
165
|
+
timeUntilExpiry: `${Math.round(timeUntilExpiry / 1000)} seconds`,
|
|
166
|
+
refreshThreshold: `${Math.round(refreshThreshold / 1000)} seconds`,
|
|
167
|
+
});
|
|
168
|
+
this.scheduleRefresh(refreshThreshold - (Math.random() * MAX_REFRESH_DELAY));
|
|
169
|
+
}
|
|
170
|
+
}, REFRESH_CHECK_INTERVAL);
|
|
171
|
+
}
|
|
172
|
+
scheduleRefresh(delay) {
|
|
173
|
+
if (this.refreshTimeout) {
|
|
174
|
+
window.clearTimeout(this.refreshTimeout);
|
|
175
|
+
}
|
|
176
|
+
this.refreshTimeout = window.setTimeout(() => {
|
|
177
|
+
const token = this.token;
|
|
178
|
+
if (token && token.expiresAt - Date.now() <= token.expiresAt * REFRESH_THRESHOLD) {
|
|
179
|
+
void this.retrieveToken();
|
|
180
|
+
}
|
|
181
|
+
}, delay);
|
|
182
|
+
}
|
|
183
|
+
clearRefreshTimers() {
|
|
83
184
|
if (this.refreshInterval) {
|
|
84
185
|
clearInterval(this.refreshInterval);
|
|
186
|
+
this.refreshInterval = null;
|
|
85
187
|
}
|
|
86
|
-
if (this.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
console.log('Setting up token refresh:', {
|
|
90
|
-
tokenLifetime: `${Math.round(tokenLifetime / 1000)} seconds`,
|
|
91
|
-
refreshIn: `${Math.round(refreshTime / 1000)} seconds`,
|
|
92
|
-
refreshAt: new Date(Date.now() + refreshTime).toISOString(),
|
|
93
|
-
});
|
|
94
|
-
this.refreshInterval = window.setInterval(() => {
|
|
95
|
-
this.clearToken();
|
|
96
|
-
}, refreshTime);
|
|
188
|
+
if (this.refreshTimeout) {
|
|
189
|
+
window.clearTimeout(this.refreshTimeout);
|
|
190
|
+
this.refreshTimeout = null;
|
|
97
191
|
}
|
|
98
192
|
}
|
|
193
|
+
logTokenStatus(message, additionalInfo = {}) {
|
|
194
|
+
if (!this.debug)
|
|
195
|
+
return;
|
|
196
|
+
const token = this.token;
|
|
197
|
+
if (!token)
|
|
198
|
+
return;
|
|
199
|
+
console.log(message, {
|
|
200
|
+
expiresIn: `${Math.round((token.expiresAt - Date.now()) / 1000)} seconds`,
|
|
201
|
+
expiresAt: new Date(token.expiresAt).toISOString(),
|
|
202
|
+
...additionalInfo,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
99
205
|
setToken(tokenResponse) {
|
|
100
206
|
const expiresAt = this.getExpirationFromToken(tokenResponse.accessToken);
|
|
101
207
|
this.token = {
|
|
102
208
|
accessToken: tokenResponse.accessToken,
|
|
103
209
|
expiresAt,
|
|
104
210
|
};
|
|
105
|
-
|
|
106
|
-
console.log('New access token obtained:', {
|
|
107
|
-
expiresIn: `${expiresIn} seconds`,
|
|
211
|
+
this.logTokenStatus('Token set', {
|
|
108
212
|
expiresAt: new Date(expiresAt).toISOString(),
|
|
109
213
|
});
|
|
110
214
|
if (typeof window !== 'undefined') {
|
|
@@ -112,19 +216,9 @@ export class TokenManager {
|
|
|
112
216
|
}
|
|
113
217
|
this.startRefreshInterval();
|
|
114
218
|
}
|
|
115
|
-
getToken() {
|
|
116
|
-
if (this.token && this.token.expiresAt > Date.now()) {
|
|
117
|
-
return this.token.accessToken;
|
|
118
|
-
}
|
|
119
|
-
this.clearToken();
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
219
|
clearToken() {
|
|
123
220
|
this.token = null;
|
|
124
|
-
|
|
125
|
-
clearInterval(this.refreshInterval);
|
|
126
|
-
this.refreshInterval = null;
|
|
127
|
-
}
|
|
221
|
+
this.clearRefreshTimers();
|
|
128
222
|
if (typeof window !== 'undefined') {
|
|
129
223
|
localStorage.removeItem(TOKEN_STORAGE_KEY);
|
|
130
224
|
}
|
package/dist/esm/types/index.js
CHANGED
|
@@ -7,15 +7,15 @@ export declare class ApiClient {
|
|
|
7
7
|
private buildUrl;
|
|
8
8
|
private request;
|
|
9
9
|
get<T>(url: string, config?: RequestInit & {
|
|
10
|
-
params?:
|
|
10
|
+
params?: object;
|
|
11
11
|
}): Promise<FlexbeResponse<T>>;
|
|
12
12
|
post<T>(url: string, data?: unknown, config?: RequestInit & {
|
|
13
|
-
params?:
|
|
13
|
+
params?: object;
|
|
14
14
|
}): Promise<FlexbeResponse<T>>;
|
|
15
15
|
put<T>(url: string, data?: unknown, config?: RequestInit & {
|
|
16
|
-
params?:
|
|
16
|
+
params?: object;
|
|
17
17
|
}): Promise<FlexbeResponse<T>>;
|
|
18
18
|
delete<T>(url: string, config?: RequestInit & {
|
|
19
|
-
params?:
|
|
19
|
+
params?: object;
|
|
20
20
|
}): Promise<FlexbeResponse<T>>;
|
|
21
21
|
}
|
|
@@ -3,10 +3,9 @@ export declare class FlexbeAuth {
|
|
|
3
3
|
private readonly config;
|
|
4
4
|
private readonly tokenManager;
|
|
5
5
|
private initialized;
|
|
6
|
-
private
|
|
6
|
+
private initializationPromise;
|
|
7
7
|
constructor(config: FlexbeConfig);
|
|
8
|
-
private
|
|
8
|
+
private initialize;
|
|
9
9
|
ensureInitialized(): Promise<void>;
|
|
10
10
|
getAuthHeaders(): Promise<Record<string, string>>;
|
|
11
|
-
isInitialized(): boolean;
|
|
12
11
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Page, GetPagesParams, PageListResponse } from '../types/pages';
|
|
1
|
+
import { Page, GetPagesParams, PageListResponse, PageFolder, PageFolderListResponse } from '../types/pages';
|
|
2
2
|
import { ApiClient } from './api-client';
|
|
3
3
|
export declare class Pages {
|
|
4
4
|
private readonly api;
|
|
@@ -11,4 +11,12 @@ export declare class Pages {
|
|
|
11
11
|
* Get a single page by ID
|
|
12
12
|
*/
|
|
13
13
|
getPage(pageId: number): Promise<Page>;
|
|
14
|
+
/**
|
|
15
|
+
* Get list of folders for a site
|
|
16
|
+
*/
|
|
17
|
+
getFolders(): Promise<PageFolderListResponse>;
|
|
18
|
+
/**
|
|
19
|
+
* Get a single folder by ID
|
|
20
|
+
*/
|
|
21
|
+
getFolder(folderId: number): Promise<PageFolder>;
|
|
14
22
|
}
|
|
@@ -3,13 +3,21 @@ export declare class TokenManager {
|
|
|
3
3
|
private static instance;
|
|
4
4
|
private token;
|
|
5
5
|
private refreshInterval;
|
|
6
|
+
private refreshTimeout;
|
|
7
|
+
private tokenPromise;
|
|
8
|
+
private debug;
|
|
6
9
|
private constructor();
|
|
7
10
|
static getInstance(): TokenManager;
|
|
8
11
|
private initializeFromStorage;
|
|
9
12
|
private setupStorageListener;
|
|
10
13
|
private getExpirationFromToken;
|
|
14
|
+
getToken(): Promise<string | null>;
|
|
15
|
+
private retrieveToken;
|
|
16
|
+
private doRetrieveToken;
|
|
11
17
|
private startRefreshInterval;
|
|
18
|
+
private scheduleRefresh;
|
|
19
|
+
private clearRefreshTimers;
|
|
20
|
+
private logTokenStatus;
|
|
12
21
|
setToken(tokenResponse: TokenResponse): void;
|
|
13
|
-
getToken(): string | null;
|
|
14
22
|
clearToken(): void;
|
|
15
23
|
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
export declare enum FlexbeAuthType {
|
|
2
|
+
API_KEY = "apiKey",
|
|
3
|
+
BEARER = "bearer"
|
|
4
|
+
}
|
|
1
5
|
export interface FlexbeConfig {
|
|
2
6
|
apiKey?: string;
|
|
3
7
|
baseUrl?: string;
|
|
@@ -21,7 +25,6 @@ export interface FlexbeError {
|
|
|
21
25
|
status?: number;
|
|
22
26
|
details?: unknown;
|
|
23
27
|
}
|
|
24
|
-
export type FlexbeAuthType = 'apiKey' | 'bearer';
|
|
25
28
|
export interface JwtToken {
|
|
26
29
|
accessToken: string;
|
|
27
30
|
expiresAt: number;
|
|
@@ -29,3 +32,8 @@ export interface JwtToken {
|
|
|
29
32
|
export interface TokenResponse {
|
|
30
33
|
accessToken: string;
|
|
31
34
|
}
|
|
35
|
+
export interface Pagination {
|
|
36
|
+
limit: number;
|
|
37
|
+
offset: number;
|
|
38
|
+
total: number;
|
|
39
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Pagination } from './index';
|
|
1
2
|
export declare enum PageType {
|
|
2
3
|
PAGE = "page",
|
|
3
4
|
FILE = "file",
|
|
@@ -14,28 +15,32 @@ export declare enum PageStatus {
|
|
|
14
15
|
}
|
|
15
16
|
export interface Page {
|
|
16
17
|
id: number;
|
|
17
|
-
siteId: number;
|
|
18
18
|
type: PageType;
|
|
19
19
|
uri: string;
|
|
20
20
|
title: string | null;
|
|
21
21
|
status: PageStatus;
|
|
22
22
|
updatedAt?: Date;
|
|
23
23
|
imgId: number | null;
|
|
24
|
+
folderId: number | null;
|
|
24
25
|
sortIndex: number;
|
|
25
26
|
}
|
|
26
|
-
export interface GetPagesParams
|
|
27
|
+
export interface GetPagesParams {
|
|
27
28
|
offset?: number;
|
|
28
29
|
limit?: number;
|
|
29
30
|
type?: PageType;
|
|
30
31
|
status?: PageStatus;
|
|
31
32
|
search?: string;
|
|
32
|
-
|
|
33
|
-
export interface Pagination {
|
|
34
|
-
limit: number;
|
|
35
|
-
offset: number;
|
|
36
|
-
total: number;
|
|
33
|
+
folderId?: number;
|
|
37
34
|
}
|
|
38
35
|
export interface PageListResponse {
|
|
39
36
|
pages: Page[];
|
|
40
37
|
pagination: Pagination;
|
|
41
38
|
}
|
|
39
|
+
export interface PageFolder {
|
|
40
|
+
id: number;
|
|
41
|
+
title: string;
|
|
42
|
+
sortIndex: number;
|
|
43
|
+
}
|
|
44
|
+
export interface PageFolderListResponse {
|
|
45
|
+
folders: PageFolder[];
|
|
46
|
+
}
|