@flexbe/sdk 0.1.1 → 0.1.3
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/README.md +14 -3
- package/dist/browser/client/api-client.js +86 -0
- package/dist/browser/client/auth.js +74 -0
- package/dist/browser/client/client.js +7 -91
- package/dist/browser/client/pages.js +4 -4
- package/dist/browser/client/token-manager.js +234 -0
- package/dist/browser/types/index.js +5 -1
- package/dist/cjs/client/api-client.js +86 -0
- package/dist/cjs/client/auth.js +63 -0
- package/dist/cjs/client/client.js +7 -88
- package/dist/cjs/client/pages.js +4 -4
- package/dist/cjs/client/token-manager.js +226 -0
- package/dist/cjs/types/index.js +6 -0
- package/dist/esm/client/api-client.js +82 -0
- package/dist/esm/client/auth.js +59 -0
- package/dist/esm/client/client.js +7 -88
- package/dist/esm/client/pages.js +4 -4
- package/dist/esm/client/token-manager.js +222 -0
- package/dist/esm/types/index.js +5 -1
- package/dist/types/client/api-client.d.ts +21 -0
- package/dist/types/client/auth.d.ts +11 -0
- package/dist/types/client/client.d.ts +3 -20
- package/dist/types/client/pages.d.ts +3 -3
- package/dist/types/client/token-manager.d.ts +23 -0
- package/dist/types/types/index.d.ts +12 -1
- package/package.json +2 -1
package/dist/esm/client/pages.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
export class Pages {
|
|
2
|
-
constructor(
|
|
3
|
-
this.
|
|
2
|
+
constructor(api) {
|
|
3
|
+
this.api = api;
|
|
4
4
|
}
|
|
5
5
|
/**
|
|
6
6
|
* Get list of pages for a site
|
|
7
7
|
*/
|
|
8
8
|
async getPages(params) {
|
|
9
|
-
const response = await this.
|
|
9
|
+
const response = await this.api.get('/sites/:siteId:/pages', { params });
|
|
10
10
|
return response.data;
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
13
|
* Get a single page by ID
|
|
14
14
|
*/
|
|
15
15
|
async getPage(pageId) {
|
|
16
|
-
const response = await this.
|
|
16
|
+
const response = await this.api.get(`/sites/:siteId:/pages/${pageId}`);
|
|
17
17
|
return response.data;
|
|
18
18
|
}
|
|
19
19
|
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
const TOKEN_STORAGE_KEY = 'flexbe_jwt_token';
|
|
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
|
|
5
|
+
export class TokenManager {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.token = null;
|
|
8
|
+
this.refreshInterval = null;
|
|
9
|
+
this.refreshTimeout = null;
|
|
10
|
+
this.tokenPromise = null;
|
|
11
|
+
this.debug = false;
|
|
12
|
+
this.initializeFromStorage();
|
|
13
|
+
this.setupStorageListener();
|
|
14
|
+
}
|
|
15
|
+
static getInstance() {
|
|
16
|
+
if (!TokenManager.instance) {
|
|
17
|
+
TokenManager.instance = new TokenManager();
|
|
18
|
+
}
|
|
19
|
+
return TokenManager.instance;
|
|
20
|
+
}
|
|
21
|
+
initializeFromStorage() {
|
|
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();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
console.error('Failed to parse stored token:', error);
|
|
39
|
+
this.clearToken();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
setupStorageListener() {
|
|
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;
|
|
60
|
+
}
|
|
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
|
+
});
|
|
77
|
+
}
|
|
78
|
+
getExpirationFromToken(token) {
|
|
79
|
+
try {
|
|
80
|
+
const [, payload] = token.split('.');
|
|
81
|
+
const decodedPayload = JSON.parse(atob(payload));
|
|
82
|
+
return decodedPayload.exp * 1000; // Convert to milliseconds
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
console.error('Failed to parse token expiration:', error);
|
|
86
|
+
return Date.now() + (4 * 60 * 1000); // Default to 4 minutes if parsing fails
|
|
87
|
+
}
|
|
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
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
startRefreshInterval() {
|
|
136
|
+
this.clearRefreshTimers();
|
|
137
|
+
if (!this.token)
|
|
138
|
+
return;
|
|
139
|
+
const tokenLifetime = this.token.expiresAt - Date.now();
|
|
140
|
+
const refreshThreshold = Math.round(tokenLifetime * REFRESH_THRESHOLD);
|
|
141
|
+
const timeUntilRefresh = refreshThreshold - (Math.random() * MAX_REFRESH_DELAY);
|
|
142
|
+
this.logTokenStatus('Starting refresh interval', {
|
|
143
|
+
tokenLifetime: `${Math.round(tokenLifetime / 1000)} seconds`,
|
|
144
|
+
refreshThreshold: `${Math.round(refreshThreshold / 1000)} seconds`,
|
|
145
|
+
timeUntilRefresh: `${Math.round(timeUntilRefresh / 1000)} seconds`,
|
|
146
|
+
});
|
|
147
|
+
this.scheduleRefresh(timeUntilRefresh);
|
|
148
|
+
this.refreshInterval = window.setInterval(() => {
|
|
149
|
+
if (!this.token)
|
|
150
|
+
return;
|
|
151
|
+
const timeUntilExpiry = this.token.expiresAt - Date.now();
|
|
152
|
+
if (timeUntilExpiry <= 0) {
|
|
153
|
+
this.logTokenStatus('Token expired');
|
|
154
|
+
this.clearToken();
|
|
155
|
+
void this.retrieveToken();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const refreshThreshold = Math.round(timeUntilExpiry * REFRESH_THRESHOLD);
|
|
159
|
+
if (timeUntilExpiry <= refreshThreshold) {
|
|
160
|
+
this.logTokenStatus('Refreshing token', {
|
|
161
|
+
timeUntilExpiry: `${Math.round(timeUntilExpiry / 1000)} seconds`,
|
|
162
|
+
refreshThreshold: `${Math.round(refreshThreshold / 1000)} seconds`,
|
|
163
|
+
});
|
|
164
|
+
this.scheduleRefresh(refreshThreshold - (Math.random() * MAX_REFRESH_DELAY));
|
|
165
|
+
}
|
|
166
|
+
}, REFRESH_CHECK_INTERVAL);
|
|
167
|
+
}
|
|
168
|
+
scheduleRefresh(delay) {
|
|
169
|
+
if (this.refreshTimeout) {
|
|
170
|
+
window.clearTimeout(this.refreshTimeout);
|
|
171
|
+
}
|
|
172
|
+
this.refreshTimeout = window.setTimeout(() => {
|
|
173
|
+
const token = this.token;
|
|
174
|
+
if (token && token.expiresAt - Date.now() <= token.expiresAt * REFRESH_THRESHOLD) {
|
|
175
|
+
void this.retrieveToken();
|
|
176
|
+
}
|
|
177
|
+
}, delay);
|
|
178
|
+
}
|
|
179
|
+
clearRefreshTimers() {
|
|
180
|
+
if (this.refreshInterval) {
|
|
181
|
+
clearInterval(this.refreshInterval);
|
|
182
|
+
this.refreshInterval = null;
|
|
183
|
+
}
|
|
184
|
+
if (this.refreshTimeout) {
|
|
185
|
+
window.clearTimeout(this.refreshTimeout);
|
|
186
|
+
this.refreshTimeout = null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
logTokenStatus(message, additionalInfo = {}) {
|
|
190
|
+
if (!this.debug)
|
|
191
|
+
return;
|
|
192
|
+
const token = this.token;
|
|
193
|
+
if (!token)
|
|
194
|
+
return;
|
|
195
|
+
console.log(message, {
|
|
196
|
+
expiresIn: `${Math.round((token.expiresAt - Date.now()) / 1000)} seconds`,
|
|
197
|
+
expiresAt: new Date(token.expiresAt).toISOString(),
|
|
198
|
+
...additionalInfo,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
setToken(tokenResponse) {
|
|
202
|
+
const expiresAt = this.getExpirationFromToken(tokenResponse.accessToken);
|
|
203
|
+
this.token = {
|
|
204
|
+
accessToken: tokenResponse.accessToken,
|
|
205
|
+
expiresAt,
|
|
206
|
+
};
|
|
207
|
+
this.logTokenStatus('Token set', {
|
|
208
|
+
expiresAt: new Date(expiresAt).toISOString(),
|
|
209
|
+
});
|
|
210
|
+
if (typeof window !== 'undefined') {
|
|
211
|
+
localStorage.setItem(TOKEN_STORAGE_KEY, JSON.stringify(this.token));
|
|
212
|
+
}
|
|
213
|
+
this.startRefreshInterval();
|
|
214
|
+
}
|
|
215
|
+
clearToken() {
|
|
216
|
+
this.token = null;
|
|
217
|
+
this.clearRefreshTimers();
|
|
218
|
+
if (typeof window !== 'undefined') {
|
|
219
|
+
localStorage.removeItem(TOKEN_STORAGE_KEY);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
package/dist/esm/types/index.js
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { FlexbeConfig, FlexbeResponse } from '../types';
|
|
2
|
+
export declare class ApiClient {
|
|
3
|
+
private readonly config;
|
|
4
|
+
private readonly auth;
|
|
5
|
+
constructor(config: FlexbeConfig);
|
|
6
|
+
private replaceSiteId;
|
|
7
|
+
private buildUrl;
|
|
8
|
+
private request;
|
|
9
|
+
get<T>(url: string, config?: RequestInit & {
|
|
10
|
+
params?: Record<string, unknown>;
|
|
11
|
+
}): Promise<FlexbeResponse<T>>;
|
|
12
|
+
post<T>(url: string, data?: unknown, config?: RequestInit & {
|
|
13
|
+
params?: Record<string, unknown>;
|
|
14
|
+
}): Promise<FlexbeResponse<T>>;
|
|
15
|
+
put<T>(url: string, data?: unknown, config?: RequestInit & {
|
|
16
|
+
params?: Record<string, unknown>;
|
|
17
|
+
}): Promise<FlexbeResponse<T>>;
|
|
18
|
+
delete<T>(url: string, config?: RequestInit & {
|
|
19
|
+
params?: Record<string, unknown>;
|
|
20
|
+
}): Promise<FlexbeResponse<T>>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FlexbeConfig } from '../types';
|
|
2
|
+
export declare class FlexbeAuth {
|
|
3
|
+
private readonly config;
|
|
4
|
+
private readonly tokenManager;
|
|
5
|
+
private initialized;
|
|
6
|
+
private initializationPromise;
|
|
7
|
+
constructor(config: FlexbeConfig);
|
|
8
|
+
private initialize;
|
|
9
|
+
ensureInitialized(): Promise<void>;
|
|
10
|
+
getAuthHeaders(): Promise<Record<string, string>>;
|
|
11
|
+
}
|
|
@@ -1,26 +1,9 @@
|
|
|
1
|
-
import { FlexbeConfig
|
|
1
|
+
import { FlexbeConfig } from '../types';
|
|
2
2
|
import { Pages } from './pages';
|
|
3
|
+
import { ApiClient } from './api-client';
|
|
3
4
|
export declare class FlexbeClient {
|
|
4
5
|
private readonly config;
|
|
5
6
|
readonly pages: Pages;
|
|
7
|
+
readonly api: ApiClient;
|
|
6
8
|
constructor(config?: Partial<FlexbeConfig>);
|
|
7
|
-
private buildUrl;
|
|
8
|
-
private request;
|
|
9
|
-
private get;
|
|
10
|
-
private post;
|
|
11
|
-
private put;
|
|
12
|
-
private delete;
|
|
13
|
-
private getSiteUrl;
|
|
14
|
-
sitesGet<T>(path: string, config?: RequestInit & {
|
|
15
|
-
params?: Record<string, unknown>;
|
|
16
|
-
}): Promise<FlexbeResponse<T>>;
|
|
17
|
-
sitesPost<T>(path: string, data?: unknown, config?: RequestInit & {
|
|
18
|
-
params?: Record<string, unknown>;
|
|
19
|
-
}): Promise<FlexbeResponse<T>>;
|
|
20
|
-
sitesPut<T>(path: string, data?: unknown, config?: RequestInit & {
|
|
21
|
-
params?: Record<string, unknown>;
|
|
22
|
-
}): Promise<FlexbeResponse<T>>;
|
|
23
|
-
sitesDelete<T>(path: string, config?: RequestInit & {
|
|
24
|
-
params?: Record<string, unknown>;
|
|
25
|
-
}): Promise<FlexbeResponse<T>>;
|
|
26
9
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Page, GetPagesParams, PageListResponse } from '../types/pages';
|
|
2
|
-
import {
|
|
2
|
+
import { ApiClient } from './api-client';
|
|
3
3
|
export declare class Pages {
|
|
4
|
-
private readonly
|
|
5
|
-
constructor(
|
|
4
|
+
private readonly api;
|
|
5
|
+
constructor(api: ApiClient);
|
|
6
6
|
/**
|
|
7
7
|
* Get list of pages for a site
|
|
8
8
|
*/
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { TokenResponse } from '../types';
|
|
2
|
+
export declare class TokenManager {
|
|
3
|
+
private static instance;
|
|
4
|
+
private token;
|
|
5
|
+
private refreshInterval;
|
|
6
|
+
private refreshTimeout;
|
|
7
|
+
private tokenPromise;
|
|
8
|
+
private debug;
|
|
9
|
+
private constructor();
|
|
10
|
+
static getInstance(): TokenManager;
|
|
11
|
+
private initializeFromStorage;
|
|
12
|
+
private setupStorageListener;
|
|
13
|
+
private getExpirationFromToken;
|
|
14
|
+
getToken(): Promise<string | null>;
|
|
15
|
+
private retrieveToken;
|
|
16
|
+
private doRetrieveToken;
|
|
17
|
+
private startRefreshInterval;
|
|
18
|
+
private scheduleRefresh;
|
|
19
|
+
private clearRefreshTimers;
|
|
20
|
+
private logTokenStatus;
|
|
21
|
+
setToken(tokenResponse: TokenResponse): void;
|
|
22
|
+
clearToken(): void;
|
|
23
|
+
}
|
|
@@ -1,8 +1,13 @@
|
|
|
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;
|
|
4
8
|
timeout?: number;
|
|
5
9
|
siteId?: string;
|
|
10
|
+
authType?: FlexbeAuthType;
|
|
6
11
|
}
|
|
7
12
|
export interface FlexbeResponse<T> {
|
|
8
13
|
data: T;
|
|
@@ -20,4 +25,10 @@ export interface FlexbeError {
|
|
|
20
25
|
status?: number;
|
|
21
26
|
details?: unknown;
|
|
22
27
|
}
|
|
23
|
-
export
|
|
28
|
+
export interface JwtToken {
|
|
29
|
+
accessToken: string;
|
|
30
|
+
expiresAt: number;
|
|
31
|
+
}
|
|
32
|
+
export interface TokenResponse {
|
|
33
|
+
accessToken: string;
|
|
34
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flexbe/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "TypeScript SDK for Flexbe API",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"build:esm": "tsc -p tsconfig.esm.json",
|
|
19
19
|
"build:cjs": "tsc -p tsconfig.cjs.json",
|
|
20
20
|
"build:browser": "tsc -p tsconfig.browser.json",
|
|
21
|
+
"dev": "tsc -p tsconfig.esm.json --watch",
|
|
21
22
|
"test": "dotenv -e test/.env.test jest",
|
|
22
23
|
"lint": "eslint src --ext .ts",
|
|
23
24
|
"format": "prettier --write \"src/**/*.ts\"",
|