@flexbe/sdk 0.1.1 → 0.1.2
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 +118 -0
- package/dist/browser/client/client.js +6 -91
- package/dist/browser/client/pages.js +4 -4
- package/dist/browser/client/token-manager.js +132 -0
- package/dist/cjs/client/api-client.js +86 -0
- package/dist/cjs/client/auth.js +107 -0
- package/dist/cjs/client/client.js +6 -88
- package/dist/cjs/client/pages.js +4 -4
- package/dist/cjs/client/token-manager.js +136 -0
- package/dist/esm/client/api-client.js +82 -0
- package/dist/esm/client/auth.js +103 -0
- package/dist/esm/client/client.js +6 -88
- package/dist/esm/client/pages.js +4 -4
- package/dist/esm/client/token-manager.js +132 -0
- package/dist/types/client/api-client.d.ts +21 -0
- package/dist/types/client/auth.d.ts +12 -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 +15 -0
- package/dist/types/types/index.d.ts +9 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ npm install @flexbe/sdk
|
|
|
13
13
|
```typescript
|
|
14
14
|
import { FlexbeClient } from '@flexbe/sdk';
|
|
15
15
|
|
|
16
|
-
// Initialize the client
|
|
16
|
+
// Initialize the client with API Key authentication
|
|
17
17
|
const client = new FlexbeClient({
|
|
18
18
|
apiKey: 'your-api-key',
|
|
19
19
|
siteId: 'your-site-id', // optional, required for site-specific endpoints
|
|
@@ -21,6 +21,14 @@ const client = new FlexbeClient({
|
|
|
21
21
|
timeout: 30000, // optional, defaults to 30 seconds
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
+
// Or initialize with JWT Bearer token authentication
|
|
25
|
+
const client = new FlexbeClient({
|
|
26
|
+
authType: 'bearer',
|
|
27
|
+
siteId: 'your-site-id', // optional, required for site-specific endpoints
|
|
28
|
+
baseUrl: 'https://api.flexbe.com', // optional
|
|
29
|
+
timeout: 30000, // optional, defaults to 30 seconds
|
|
30
|
+
});
|
|
31
|
+
|
|
24
32
|
// Using the Pages API
|
|
25
33
|
try {
|
|
26
34
|
// Get list of pages
|
|
@@ -44,18 +52,21 @@ try {
|
|
|
44
52
|
## Features
|
|
45
53
|
|
|
46
54
|
- TypeScript support with full type definitions
|
|
47
|
-
-
|
|
55
|
+
- Multiple authentication methods:
|
|
56
|
+
- API Key authentication
|
|
57
|
+
- JWT Bearer token authentication with automatic token refresh (uses cookie-based authentication)
|
|
48
58
|
- Automatic error handling
|
|
49
59
|
- Configurable timeout and base URL
|
|
50
60
|
- Native fetch API support (works in both Node.js and browser)
|
|
51
61
|
- Site-specific endpoints support
|
|
52
62
|
- Query parameter handling
|
|
53
63
|
- Request timeout handling
|
|
64
|
+
- Token sharing between browser tabs (for JWT authentication)
|
|
54
65
|
|
|
55
66
|
## Environment Variables
|
|
56
67
|
|
|
57
68
|
The SDK supports the following environment variables:
|
|
58
|
-
- `FLEXBE_API_KEY`: Your API key
|
|
69
|
+
- `FLEXBE_API_KEY`: Your API key (required for API Key authentication)
|
|
59
70
|
- `FLEXBE_API_URL`: Base URL (defaults to 'https://api.flexbe.com')
|
|
60
71
|
- `FLEXBE_SITE_ID`: Your site ID
|
|
61
72
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { FlexbeAuth } from './auth';
|
|
11
|
+
export class ApiClient {
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.auth = new FlexbeAuth(config);
|
|
15
|
+
}
|
|
16
|
+
replaceSiteId(url) {
|
|
17
|
+
if (!this.config.siteId) {
|
|
18
|
+
return url;
|
|
19
|
+
}
|
|
20
|
+
return url.replace(/:siteId:/g, this.config.siteId);
|
|
21
|
+
}
|
|
22
|
+
buildUrl(path, params) {
|
|
23
|
+
const processedPath = this.replaceSiteId(path);
|
|
24
|
+
const searchParams = new URLSearchParams();
|
|
25
|
+
if (params) {
|
|
26
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
27
|
+
if (value !== undefined && value !== null) {
|
|
28
|
+
searchParams.append(key, String(value));
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return `${processedPath}${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
|
|
33
|
+
}
|
|
34
|
+
request(config) {
|
|
35
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
36
|
+
try {
|
|
37
|
+
yield this.auth.ensureInitialized();
|
|
38
|
+
const controller = new AbortController();
|
|
39
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
40
|
+
const url = this.buildUrl(config.url, config.params);
|
|
41
|
+
const headers = Object.assign(Object.assign({}, (yield this.auth.getAuthHeaders())), config.headers);
|
|
42
|
+
const response = yield fetch(this.config.baseUrl + url, Object.assign(Object.assign({}, config), { headers, signal: controller.signal }));
|
|
43
|
+
clearTimeout(timeoutId);
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
const defaultError = { message: response.statusText };
|
|
46
|
+
const errorData = yield response.json().catch(() => defaultError);
|
|
47
|
+
const error = {
|
|
48
|
+
message: errorData.message || response.statusText,
|
|
49
|
+
code: errorData.code,
|
|
50
|
+
status: response.status,
|
|
51
|
+
details: errorData.details,
|
|
52
|
+
};
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
const data = yield response.json();
|
|
56
|
+
return {
|
|
57
|
+
data,
|
|
58
|
+
status: response.status,
|
|
59
|
+
statusText: response.statusText,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
64
|
+
const timeoutError = {
|
|
65
|
+
message: 'Request timeout',
|
|
66
|
+
status: 408,
|
|
67
|
+
};
|
|
68
|
+
throw timeoutError;
|
|
69
|
+
}
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
get(url, config) {
|
|
75
|
+
return this.request(Object.assign(Object.assign({}, config), { method: 'GET', url }));
|
|
76
|
+
}
|
|
77
|
+
post(url, data, config) {
|
|
78
|
+
return this.request(Object.assign(Object.assign({}, config), { method: 'POST', url, body: JSON.stringify(data) }));
|
|
79
|
+
}
|
|
80
|
+
put(url, data, config) {
|
|
81
|
+
return this.request(Object.assign(Object.assign({}, config), { method: 'PUT', url, body: JSON.stringify(data) }));
|
|
82
|
+
}
|
|
83
|
+
delete(url, config) {
|
|
84
|
+
return this.request(Object.assign(Object.assign({}, config), { method: 'DELETE', url }));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { TokenManager } from './token-manager';
|
|
11
|
+
export class FlexbeAuth {
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.initialized = false;
|
|
14
|
+
this.initializing = false;
|
|
15
|
+
this.config = config;
|
|
16
|
+
this.tokenManager = TokenManager.getInstance();
|
|
17
|
+
if (this.config.authType === 'bearer') {
|
|
18
|
+
// Check if we have a valid token in storage
|
|
19
|
+
const existingToken = this.tokenManager.getToken();
|
|
20
|
+
if (existingToken) {
|
|
21
|
+
this.initialized = true;
|
|
22
|
+
}
|
|
23
|
+
// Don't start initialization here, let ensureInitialized handle it
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
this.initialized = true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
initializeBearerAuth() {
|
|
30
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
31
|
+
if (this.initializing) {
|
|
32
|
+
// Wait for the ongoing initialization to complete
|
|
33
|
+
while (this.initializing) {
|
|
34
|
+
yield new Promise(resolve => setTimeout(resolve, 100));
|
|
35
|
+
}
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
this.initializing = true;
|
|
40
|
+
const controller = new AbortController();
|
|
41
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
42
|
+
const response = yield fetch('/oauth/token', {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers: {
|
|
45
|
+
'Content-Type': 'application/json',
|
|
46
|
+
},
|
|
47
|
+
body: JSON.stringify({
|
|
48
|
+
grant_type: 'client_credentials',
|
|
49
|
+
}),
|
|
50
|
+
credentials: 'include',
|
|
51
|
+
signal: controller.signal,
|
|
52
|
+
});
|
|
53
|
+
clearTimeout(timeoutId);
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
const defaultError = { message: response.statusText };
|
|
56
|
+
const errorData = yield response.json().catch(() => defaultError);
|
|
57
|
+
const error = {
|
|
58
|
+
message: errorData.message || response.statusText,
|
|
59
|
+
code: errorData.code,
|
|
60
|
+
status: response.status,
|
|
61
|
+
details: errorData.details,
|
|
62
|
+
};
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
const data = yield response.json();
|
|
66
|
+
this.tokenManager.setToken(data);
|
|
67
|
+
this.initialized = true;
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
console.error('Failed to initialize bearer authentication:', error);
|
|
71
|
+
this.initialized = false; // Reset initialized state on error
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
this.initializing = false;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
ensureInitialized() {
|
|
80
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
81
|
+
if (this.config.authType !== 'bearer' || this.initialized) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
yield this.initializeBearerAuth();
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
getAuthHeaders() {
|
|
88
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
89
|
+
yield this.ensureInitialized();
|
|
90
|
+
const headers = {
|
|
91
|
+
'Content-Type': 'application/json',
|
|
92
|
+
};
|
|
93
|
+
if (this.config.authType === 'apiKey') {
|
|
94
|
+
headers['x-api-key'] = this.config.apiKey;
|
|
95
|
+
}
|
|
96
|
+
else if (this.config.authType === 'bearer') {
|
|
97
|
+
const token = this.tokenManager.getToken();
|
|
98
|
+
if (!token) {
|
|
99
|
+
// If no token is available, try to initialize again
|
|
100
|
+
this.initialized = false;
|
|
101
|
+
yield this.ensureInitialized();
|
|
102
|
+
const newToken = this.tokenManager.getToken();
|
|
103
|
+
if (!newToken) {
|
|
104
|
+
throw new Error('No valid bearer token available');
|
|
105
|
+
}
|
|
106
|
+
headers['Authorization'] = `Bearer ${newToken}`;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return headers;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
isInitialized() {
|
|
116
|
+
return this.initialized;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -1,13 +1,5 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
1
|
import { Pages } from './pages';
|
|
2
|
+
import { ApiClient } from './api-client';
|
|
11
3
|
export class FlexbeClient {
|
|
12
4
|
constructor(config) {
|
|
13
5
|
const getEnvVar = (key) => {
|
|
@@ -21,89 +13,12 @@ export class FlexbeClient {
|
|
|
21
13
|
timeout: (config === null || config === void 0 ? void 0 : config.timeout) || 30000,
|
|
22
14
|
apiKey: (config === null || config === void 0 ? void 0 : config.apiKey) || getEnvVar('FLEXBE_API_KEY') || '',
|
|
23
15
|
siteId: (config === null || config === void 0 ? void 0 : config.siteId) || getEnvVar('FLEXBE_SITE_ID'),
|
|
16
|
+
authType: (config === null || config === void 0 ? void 0 : config.authType) || 'apiKey',
|
|
24
17
|
};
|
|
25
|
-
if (!this.config.apiKey) {
|
|
26
|
-
throw new Error('API key is required. Please provide it either through config or FLEXBE_API_KEY environment variable.');
|
|
18
|
+
if (this.config.authType === 'apiKey' && !this.config.apiKey) {
|
|
19
|
+
throw new Error('API key is required when using apiKey authentication. Please provide it either through config or FLEXBE_API_KEY environment variable.');
|
|
27
20
|
}
|
|
28
|
-
this.
|
|
29
|
-
|
|
30
|
-
buildUrl(path, params) {
|
|
31
|
-
const searchParams = new URLSearchParams();
|
|
32
|
-
if (params) {
|
|
33
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
34
|
-
if (value !== undefined && value !== null) {
|
|
35
|
-
searchParams.append(key, String(value));
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
return `${path}${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
|
|
40
|
-
}
|
|
41
|
-
request(config) {
|
|
42
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
43
|
-
try {
|
|
44
|
-
const controller = new AbortController();
|
|
45
|
-
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
46
|
-
const url = this.buildUrl(config.url, config.params);
|
|
47
|
-
const response = yield fetch(this.config.baseUrl + url, Object.assign(Object.assign({}, config), { headers: Object.assign({ 'Authorization': `Bearer ${this.config.apiKey}`, 'Content-Type': 'application/json' }, config.headers), signal: controller.signal }));
|
|
48
|
-
clearTimeout(timeoutId);
|
|
49
|
-
if (!response.ok) {
|
|
50
|
-
const defaultError = { message: response.statusText };
|
|
51
|
-
const errorData = yield response.json().catch(() => defaultError);
|
|
52
|
-
const error = {
|
|
53
|
-
message: errorData.message || response.statusText,
|
|
54
|
-
code: errorData.code,
|
|
55
|
-
status: response.status,
|
|
56
|
-
details: errorData.details,
|
|
57
|
-
};
|
|
58
|
-
throw error;
|
|
59
|
-
}
|
|
60
|
-
const data = yield response.json();
|
|
61
|
-
return {
|
|
62
|
-
data,
|
|
63
|
-
status: response.status,
|
|
64
|
-
statusText: response.statusText,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
if (error instanceof Error && error.name === 'AbortError') {
|
|
69
|
-
const timeoutError = {
|
|
70
|
-
message: 'Request timeout',
|
|
71
|
-
status: 408,
|
|
72
|
-
};
|
|
73
|
-
throw timeoutError;
|
|
74
|
-
}
|
|
75
|
-
throw error;
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
get(url, config) {
|
|
80
|
-
return this.request(Object.assign(Object.assign({}, config), { method: 'GET', url }));
|
|
81
|
-
}
|
|
82
|
-
post(url, data, config) {
|
|
83
|
-
return this.request(Object.assign(Object.assign({}, config), { method: 'POST', url, body: JSON.stringify(data) }));
|
|
84
|
-
}
|
|
85
|
-
put(url, data, config) {
|
|
86
|
-
return this.request(Object.assign(Object.assign({}, config), { method: 'PUT', url, body: JSON.stringify(data) }));
|
|
87
|
-
}
|
|
88
|
-
delete(url, config) {
|
|
89
|
-
return this.request(Object.assign(Object.assign({}, config), { method: 'DELETE', url }));
|
|
90
|
-
}
|
|
91
|
-
getSiteUrl(path) {
|
|
92
|
-
if (!this.config.siteId) {
|
|
93
|
-
return path;
|
|
94
|
-
}
|
|
95
|
-
return `/sites/${this.config.siteId}${path}`;
|
|
96
|
-
}
|
|
97
|
-
sitesGet(path, config) {
|
|
98
|
-
return this.get(this.getSiteUrl(path), config);
|
|
99
|
-
}
|
|
100
|
-
sitesPost(path, data, config) {
|
|
101
|
-
return this.post(this.getSiteUrl(path), data, config);
|
|
102
|
-
}
|
|
103
|
-
sitesPut(path, data, config) {
|
|
104
|
-
return this.put(this.getSiteUrl(path), data, config);
|
|
105
|
-
}
|
|
106
|
-
sitesDelete(path, config) {
|
|
107
|
-
return this.delete(this.getSiteUrl(path), config);
|
|
21
|
+
this.api = new ApiClient(this.config);
|
|
22
|
+
this.pages = new Pages(this.api);
|
|
108
23
|
}
|
|
109
24
|
}
|
|
@@ -8,15 +8,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
export class Pages {
|
|
11
|
-
constructor(
|
|
12
|
-
this.
|
|
11
|
+
constructor(api) {
|
|
12
|
+
this.api = api;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* Get list of pages for a site
|
|
16
16
|
*/
|
|
17
17
|
getPages(params) {
|
|
18
18
|
return __awaiter(this, void 0, void 0, function* () {
|
|
19
|
-
const response = yield this.
|
|
19
|
+
const response = yield this.api.get('/sites/:siteId:/pages', { params });
|
|
20
20
|
return response.data;
|
|
21
21
|
});
|
|
22
22
|
}
|
|
@@ -25,7 +25,7 @@ export class Pages {
|
|
|
25
25
|
*/
|
|
26
26
|
getPage(pageId) {
|
|
27
27
|
return __awaiter(this, void 0, void 0, function* () {
|
|
28
|
-
const response = yield this.
|
|
28
|
+
const response = yield this.api.get(`/sites/:siteId:/pages/${pageId}`);
|
|
29
29
|
return response.data;
|
|
30
30
|
});
|
|
31
31
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
const TOKEN_STORAGE_KEY = 'flexbe_jwt_token';
|
|
2
|
+
const REFRESH_THRESHOLD = 0.8; // Refresh when 80% of token lifetime has passed
|
|
3
|
+
export class TokenManager {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.token = null;
|
|
6
|
+
this.refreshInterval = null;
|
|
7
|
+
this.initializeFromStorage();
|
|
8
|
+
this.setupStorageListener();
|
|
9
|
+
}
|
|
10
|
+
static getInstance() {
|
|
11
|
+
if (!TokenManager.instance) {
|
|
12
|
+
TokenManager.instance = new TokenManager();
|
|
13
|
+
}
|
|
14
|
+
return TokenManager.instance;
|
|
15
|
+
}
|
|
16
|
+
initializeFromStorage() {
|
|
17
|
+
if (typeof window !== 'undefined') {
|
|
18
|
+
const storedToken = localStorage.getItem(TOKEN_STORAGE_KEY);
|
|
19
|
+
if (storedToken) {
|
|
20
|
+
try {
|
|
21
|
+
this.token = JSON.parse(storedToken);
|
|
22
|
+
if (this.token.expiresAt > Date.now()) {
|
|
23
|
+
console.log('Reusing stored token:', {
|
|
24
|
+
expiresIn: `${Math.round((this.token.expiresAt - Date.now()) / 1000)} seconds`,
|
|
25
|
+
expiresAt: new Date(this.token.expiresAt).toISOString(),
|
|
26
|
+
});
|
|
27
|
+
this.startRefreshInterval();
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
this.clearToken();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
console.error('Failed to parse stored token:', error);
|
|
35
|
+
this.clearToken();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
setupStorageListener() {
|
|
41
|
+
if (typeof window !== 'undefined') {
|
|
42
|
+
window.addEventListener('storage', (event) => {
|
|
43
|
+
if (event.key === TOKEN_STORAGE_KEY) {
|
|
44
|
+
if (event.newValue) {
|
|
45
|
+
try {
|
|
46
|
+
const newToken = JSON.parse(event.newValue);
|
|
47
|
+
if (newToken.expiresAt > Date.now()) {
|
|
48
|
+
this.token = newToken;
|
|
49
|
+
console.log('Token updated from storage:', {
|
|
50
|
+
expiresIn: `${Math.round((newToken.expiresAt - Date.now()) / 1000)} seconds`,
|
|
51
|
+
expiresAt: new Date(newToken.expiresAt).toISOString(),
|
|
52
|
+
});
|
|
53
|
+
this.startRefreshInterval();
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
this.clearToken();
|
|
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
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
getExpirationFromToken(token) {
|
|
72
|
+
try {
|
|
73
|
+
const [, payload] = token.split('.');
|
|
74
|
+
const decodedPayload = JSON.parse(atob(payload));
|
|
75
|
+
return decodedPayload.exp * 1000; // Convert to milliseconds
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.error('Failed to parse token expiration:', error);
|
|
79
|
+
return Date.now() + (4 * 60 * 1000); // Default to 4 minutes if parsing fails
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
startRefreshInterval() {
|
|
83
|
+
if (this.refreshInterval) {
|
|
84
|
+
clearInterval(this.refreshInterval);
|
|
85
|
+
}
|
|
86
|
+
if (this.token) {
|
|
87
|
+
const tokenLifetime = this.token.expiresAt - (this.token.expiresAt - 4 * 60 * 1000); // 4 minutes in milliseconds
|
|
88
|
+
const refreshTime = Math.round(tokenLifetime * REFRESH_THRESHOLD);
|
|
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);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
setToken(tokenResponse) {
|
|
100
|
+
const expiresAt = this.getExpirationFromToken(tokenResponse.accessToken);
|
|
101
|
+
this.token = {
|
|
102
|
+
accessToken: tokenResponse.accessToken,
|
|
103
|
+
expiresAt,
|
|
104
|
+
};
|
|
105
|
+
const expiresIn = Math.round((expiresAt - Date.now()) / 1000);
|
|
106
|
+
console.log('New access token obtained:', {
|
|
107
|
+
expiresIn: `${expiresIn} seconds`,
|
|
108
|
+
expiresAt: new Date(expiresAt).toISOString(),
|
|
109
|
+
});
|
|
110
|
+
if (typeof window !== 'undefined') {
|
|
111
|
+
localStorage.setItem(TOKEN_STORAGE_KEY, JSON.stringify(this.token));
|
|
112
|
+
}
|
|
113
|
+
this.startRefreshInterval();
|
|
114
|
+
}
|
|
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
|
+
clearToken() {
|
|
123
|
+
this.token = null;
|
|
124
|
+
if (this.refreshInterval) {
|
|
125
|
+
clearInterval(this.refreshInterval);
|
|
126
|
+
this.refreshInterval = null;
|
|
127
|
+
}
|
|
128
|
+
if (typeof window !== 'undefined') {
|
|
129
|
+
localStorage.removeItem(TOKEN_STORAGE_KEY);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ApiClient = void 0;
|
|
4
|
+
const auth_1 = require("./auth");
|
|
5
|
+
class ApiClient {
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.config = config;
|
|
8
|
+
this.auth = new auth_1.FlexbeAuth(config);
|
|
9
|
+
}
|
|
10
|
+
replaceSiteId(url) {
|
|
11
|
+
if (!this.config.siteId) {
|
|
12
|
+
return url;
|
|
13
|
+
}
|
|
14
|
+
return url.replace(/:siteId:/g, this.config.siteId);
|
|
15
|
+
}
|
|
16
|
+
buildUrl(path, params) {
|
|
17
|
+
const processedPath = this.replaceSiteId(path);
|
|
18
|
+
const searchParams = new URLSearchParams();
|
|
19
|
+
if (params) {
|
|
20
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
21
|
+
if (value !== undefined && value !== null) {
|
|
22
|
+
searchParams.append(key, String(value));
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return `${processedPath}${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
|
|
27
|
+
}
|
|
28
|
+
async request(config) {
|
|
29
|
+
try {
|
|
30
|
+
await this.auth.ensureInitialized();
|
|
31
|
+
const controller = new AbortController();
|
|
32
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
33
|
+
const url = this.buildUrl(config.url, config.params);
|
|
34
|
+
const headers = {
|
|
35
|
+
...(await this.auth.getAuthHeaders()),
|
|
36
|
+
...config.headers,
|
|
37
|
+
};
|
|
38
|
+
const response = await fetch(this.config.baseUrl + url, {
|
|
39
|
+
...config,
|
|
40
|
+
headers,
|
|
41
|
+
signal: controller.signal,
|
|
42
|
+
});
|
|
43
|
+
clearTimeout(timeoutId);
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
const defaultError = { message: response.statusText };
|
|
46
|
+
const errorData = await response.json().catch(() => defaultError);
|
|
47
|
+
const error = {
|
|
48
|
+
message: errorData.message || response.statusText,
|
|
49
|
+
code: errorData.code,
|
|
50
|
+
status: response.status,
|
|
51
|
+
details: errorData.details,
|
|
52
|
+
};
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
const data = await response.json();
|
|
56
|
+
return {
|
|
57
|
+
data,
|
|
58
|
+
status: response.status,
|
|
59
|
+
statusText: response.statusText,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
64
|
+
const timeoutError = {
|
|
65
|
+
message: 'Request timeout',
|
|
66
|
+
status: 408,
|
|
67
|
+
};
|
|
68
|
+
throw timeoutError;
|
|
69
|
+
}
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
get(url, config) {
|
|
74
|
+
return this.request({ ...config, method: 'GET', url });
|
|
75
|
+
}
|
|
76
|
+
post(url, data, config) {
|
|
77
|
+
return this.request({ ...config, method: 'POST', url, body: JSON.stringify(data) });
|
|
78
|
+
}
|
|
79
|
+
put(url, data, config) {
|
|
80
|
+
return this.request({ ...config, method: 'PUT', url, body: JSON.stringify(data) });
|
|
81
|
+
}
|
|
82
|
+
delete(url, config) {
|
|
83
|
+
return this.request({ ...config, method: 'DELETE', url });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
exports.ApiClient = ApiClient;
|