@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.
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FlexbeAuth = void 0;
4
+ const token_manager_1 = require("./token-manager");
5
+ class FlexbeAuth {
6
+ constructor(config) {
7
+ this.initialized = false;
8
+ this.initializing = false;
9
+ this.config = config;
10
+ this.tokenManager = token_manager_1.TokenManager.getInstance();
11
+ if (this.config.authType === 'bearer') {
12
+ // Check if we have a valid token in storage
13
+ const existingToken = this.tokenManager.getToken();
14
+ if (existingToken) {
15
+ this.initialized = true;
16
+ }
17
+ // Don't start initialization here, let ensureInitialized handle it
18
+ }
19
+ else {
20
+ this.initialized = true;
21
+ }
22
+ }
23
+ async initializeBearerAuth() {
24
+ if (this.initializing) {
25
+ // Wait for the ongoing initialization to complete
26
+ while (this.initializing) {
27
+ await new Promise(resolve => setTimeout(resolve, 100));
28
+ }
29
+ return;
30
+ }
31
+ try {
32
+ this.initializing = true;
33
+ const controller = new AbortController();
34
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
35
+ const response = await fetch('/oauth/token', {
36
+ method: 'POST',
37
+ headers: {
38
+ 'Content-Type': 'application/json',
39
+ },
40
+ body: JSON.stringify({
41
+ grant_type: 'client_credentials',
42
+ }),
43
+ credentials: 'include',
44
+ signal: controller.signal,
45
+ });
46
+ clearTimeout(timeoutId);
47
+ if (!response.ok) {
48
+ const defaultError = { message: response.statusText };
49
+ const errorData = await response.json().catch(() => defaultError);
50
+ const error = {
51
+ message: errorData.message || response.statusText,
52
+ code: errorData.code,
53
+ status: response.status,
54
+ details: errorData.details,
55
+ };
56
+ throw error;
57
+ }
58
+ const data = await response.json();
59
+ this.tokenManager.setToken(data);
60
+ this.initialized = true;
61
+ }
62
+ catch (error) {
63
+ console.error('Failed to initialize bearer authentication:', error);
64
+ this.initialized = false; // Reset initialized state on error
65
+ throw error;
66
+ }
67
+ finally {
68
+ this.initializing = false;
69
+ }
70
+ }
71
+ async ensureInitialized() {
72
+ if (this.config.authType !== 'bearer' || this.initialized) {
73
+ return;
74
+ }
75
+ await this.initializeBearerAuth();
76
+ }
77
+ async getAuthHeaders() {
78
+ await this.ensureInitialized();
79
+ const headers = {
80
+ 'Content-Type': 'application/json',
81
+ };
82
+ if (this.config.authType === 'apiKey') {
83
+ headers['x-api-key'] = this.config.apiKey;
84
+ }
85
+ else if (this.config.authType === 'bearer') {
86
+ const token = this.tokenManager.getToken();
87
+ if (!token) {
88
+ // If no token is available, try to initialize again
89
+ this.initialized = false;
90
+ await this.ensureInitialized();
91
+ const newToken = this.tokenManager.getToken();
92
+ if (!newToken) {
93
+ throw new Error('No valid bearer token available');
94
+ }
95
+ headers['Authorization'] = `Bearer ${newToken}`;
96
+ }
97
+ else {
98
+ headers['Authorization'] = `Bearer ${token}`;
99
+ }
100
+ }
101
+ return headers;
102
+ }
103
+ isInitialized() {
104
+ return this.initialized;
105
+ }
106
+ }
107
+ exports.FlexbeAuth = FlexbeAuth;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FlexbeClient = void 0;
4
4
  const pages_1 = require("./pages");
5
+ const api_client_1 = require("./api-client");
5
6
  class FlexbeClient {
6
7
  constructor(config) {
7
8
  const getEnvVar = (key) => {
@@ -15,96 +16,13 @@ class FlexbeClient {
15
16
  timeout: config?.timeout || 30000,
16
17
  apiKey: config?.apiKey || getEnvVar('FLEXBE_API_KEY') || '',
17
18
  siteId: config?.siteId || getEnvVar('FLEXBE_SITE_ID'),
19
+ authType: config?.authType || 'apiKey',
18
20
  };
19
- if (!this.config.apiKey) {
20
- throw new Error('API key is required. Please provide it either through config or FLEXBE_API_KEY environment variable.');
21
+ if (this.config.authType === 'apiKey' && !this.config.apiKey) {
22
+ throw new Error('API key is required when using apiKey authentication. Please provide it either through config or FLEXBE_API_KEY environment variable.');
21
23
  }
22
- this.pages = new pages_1.Pages(this);
23
- }
24
- buildUrl(path, params) {
25
- const searchParams = new URLSearchParams();
26
- if (params) {
27
- Object.entries(params).forEach(([key, value]) => {
28
- if (value !== undefined && value !== null) {
29
- searchParams.append(key, String(value));
30
- }
31
- });
32
- }
33
- return `${path}${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
34
- }
35
- async request(config) {
36
- try {
37
- const controller = new AbortController();
38
- const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
39
- const url = this.buildUrl(config.url, config.params);
40
- const response = await fetch(this.config.baseUrl + url, {
41
- ...config,
42
- headers: {
43
- 'Authorization': `Bearer ${this.config.apiKey}`,
44
- 'Content-Type': 'application/json',
45
- ...config.headers,
46
- },
47
- signal: controller.signal,
48
- });
49
- clearTimeout(timeoutId);
50
- if (!response.ok) {
51
- const defaultError = { message: response.statusText };
52
- const errorData = await response.json().catch(() => defaultError);
53
- const error = {
54
- message: errorData.message || response.statusText,
55
- code: errorData.code,
56
- status: response.status,
57
- details: errorData.details,
58
- };
59
- throw error;
60
- }
61
- const data = await response.json();
62
- return {
63
- data,
64
- status: response.status,
65
- statusText: response.statusText,
66
- };
67
- }
68
- catch (error) {
69
- if (error instanceof Error && error.name === 'AbortError') {
70
- const timeoutError = {
71
- message: 'Request timeout',
72
- status: 408,
73
- };
74
- throw timeoutError;
75
- }
76
- throw error;
77
- }
78
- }
79
- get(url, config) {
80
- return this.request({ ...config, method: 'GET', url });
81
- }
82
- post(url, data, config) {
83
- return this.request({ ...config, method: 'POST', url, body: JSON.stringify(data) });
84
- }
85
- put(url, data, config) {
86
- return this.request({ ...config, method: 'PUT', url, body: JSON.stringify(data) });
87
- }
88
- delete(url, config) {
89
- return this.request({ ...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);
24
+ this.api = new api_client_1.ApiClient(this.config);
25
+ this.pages = new pages_1.Pages(this.api);
108
26
  }
109
27
  }
110
28
  exports.FlexbeClient = FlexbeClient;
@@ -2,21 +2,21 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Pages = void 0;
4
4
  class Pages {
5
- constructor(client) {
6
- this.client = client;
5
+ constructor(api) {
6
+ this.api = api;
7
7
  }
8
8
  /**
9
9
  * Get list of pages for a site
10
10
  */
11
11
  async getPages(params) {
12
- const response = await this.client.sitesGet('/pages', { params });
12
+ const response = await this.api.get('/sites/:siteId:/pages', { params });
13
13
  return response.data;
14
14
  }
15
15
  /**
16
16
  * Get a single page by ID
17
17
  */
18
18
  async getPage(pageId) {
19
- const response = await this.client.sitesGet(`/pages/${pageId}`);
19
+ const response = await this.api.get(`/sites/:siteId:/pages/${pageId}`);
20
20
  return response.data;
21
21
  }
22
22
  }
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TokenManager = void 0;
4
+ const TOKEN_STORAGE_KEY = 'flexbe_jwt_token';
5
+ const REFRESH_THRESHOLD = 0.8; // Refresh when 80% of token lifetime has passed
6
+ class TokenManager {
7
+ constructor() {
8
+ this.token = null;
9
+ this.refreshInterval = null;
10
+ this.initializeFromStorage();
11
+ this.setupStorageListener();
12
+ }
13
+ static getInstance() {
14
+ if (!TokenManager.instance) {
15
+ TokenManager.instance = new TokenManager();
16
+ }
17
+ return TokenManager.instance;
18
+ }
19
+ initializeFromStorage() {
20
+ if (typeof window !== 'undefined') {
21
+ const storedToken = localStorage.getItem(TOKEN_STORAGE_KEY);
22
+ if (storedToken) {
23
+ try {
24
+ this.token = JSON.parse(storedToken);
25
+ if (this.token.expiresAt > Date.now()) {
26
+ console.log('Reusing stored token:', {
27
+ expiresIn: `${Math.round((this.token.expiresAt - Date.now()) / 1000)} seconds`,
28
+ expiresAt: new Date(this.token.expiresAt).toISOString(),
29
+ });
30
+ this.startRefreshInterval();
31
+ }
32
+ else {
33
+ this.clearToken();
34
+ }
35
+ }
36
+ catch (error) {
37
+ console.error('Failed to parse stored token:', error);
38
+ this.clearToken();
39
+ }
40
+ }
41
+ }
42
+ }
43
+ setupStorageListener() {
44
+ if (typeof window !== 'undefined') {
45
+ window.addEventListener('storage', (event) => {
46
+ if (event.key === TOKEN_STORAGE_KEY) {
47
+ if (event.newValue) {
48
+ try {
49
+ const newToken = JSON.parse(event.newValue);
50
+ if (newToken.expiresAt > Date.now()) {
51
+ this.token = newToken;
52
+ console.log('Token updated from storage:', {
53
+ expiresIn: `${Math.round((newToken.expiresAt - Date.now()) / 1000)} seconds`,
54
+ expiresAt: new Date(newToken.expiresAt).toISOString(),
55
+ });
56
+ this.startRefreshInterval();
57
+ }
58
+ else {
59
+ this.clearToken();
60
+ }
61
+ }
62
+ catch (error) {
63
+ console.error('Failed to parse token from storage event:', error);
64
+ this.clearToken();
65
+ }
66
+ }
67
+ else {
68
+ this.clearToken();
69
+ }
70
+ }
71
+ });
72
+ }
73
+ }
74
+ getExpirationFromToken(token) {
75
+ try {
76
+ const [, payload] = token.split('.');
77
+ const decodedPayload = JSON.parse(atob(payload));
78
+ return decodedPayload.exp * 1000; // Convert to milliseconds
79
+ }
80
+ catch (error) {
81
+ console.error('Failed to parse token expiration:', error);
82
+ return Date.now() + (4 * 60 * 1000); // Default to 4 minutes if parsing fails
83
+ }
84
+ }
85
+ startRefreshInterval() {
86
+ if (this.refreshInterval) {
87
+ clearInterval(this.refreshInterval);
88
+ }
89
+ if (this.token) {
90
+ const tokenLifetime = this.token.expiresAt - (this.token.expiresAt - 4 * 60 * 1000); // 4 minutes in milliseconds
91
+ const refreshTime = Math.round(tokenLifetime * REFRESH_THRESHOLD);
92
+ console.log('Setting up token refresh:', {
93
+ tokenLifetime: `${Math.round(tokenLifetime / 1000)} seconds`,
94
+ refreshIn: `${Math.round(refreshTime / 1000)} seconds`,
95
+ refreshAt: new Date(Date.now() + refreshTime).toISOString(),
96
+ });
97
+ this.refreshInterval = window.setInterval(() => {
98
+ this.clearToken();
99
+ }, refreshTime);
100
+ }
101
+ }
102
+ setToken(tokenResponse) {
103
+ const expiresAt = this.getExpirationFromToken(tokenResponse.accessToken);
104
+ this.token = {
105
+ accessToken: tokenResponse.accessToken,
106
+ expiresAt,
107
+ };
108
+ const expiresIn = Math.round((expiresAt - Date.now()) / 1000);
109
+ console.log('New access token obtained:', {
110
+ expiresIn: `${expiresIn} seconds`,
111
+ expiresAt: new Date(expiresAt).toISOString(),
112
+ });
113
+ if (typeof window !== 'undefined') {
114
+ localStorage.setItem(TOKEN_STORAGE_KEY, JSON.stringify(this.token));
115
+ }
116
+ this.startRefreshInterval();
117
+ }
118
+ getToken() {
119
+ if (this.token && this.token.expiresAt > Date.now()) {
120
+ return this.token.accessToken;
121
+ }
122
+ this.clearToken();
123
+ return null;
124
+ }
125
+ clearToken() {
126
+ this.token = null;
127
+ if (this.refreshInterval) {
128
+ clearInterval(this.refreshInterval);
129
+ this.refreshInterval = null;
130
+ }
131
+ if (typeof window !== 'undefined') {
132
+ localStorage.removeItem(TOKEN_STORAGE_KEY);
133
+ }
134
+ }
135
+ }
136
+ exports.TokenManager = TokenManager;
@@ -0,0 +1,82 @@
1
+ import { FlexbeAuth } from './auth';
2
+ export class ApiClient {
3
+ constructor(config) {
4
+ this.config = config;
5
+ this.auth = new FlexbeAuth(config);
6
+ }
7
+ replaceSiteId(url) {
8
+ if (!this.config.siteId) {
9
+ return url;
10
+ }
11
+ return url.replace(/:siteId:/g, this.config.siteId);
12
+ }
13
+ buildUrl(path, params) {
14
+ const processedPath = this.replaceSiteId(path);
15
+ const searchParams = new URLSearchParams();
16
+ if (params) {
17
+ Object.entries(params).forEach(([key, value]) => {
18
+ if (value !== undefined && value !== null) {
19
+ searchParams.append(key, String(value));
20
+ }
21
+ });
22
+ }
23
+ return `${processedPath}${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
24
+ }
25
+ async request(config) {
26
+ try {
27
+ await this.auth.ensureInitialized();
28
+ const controller = new AbortController();
29
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
30
+ const url = this.buildUrl(config.url, config.params);
31
+ const headers = {
32
+ ...(await this.auth.getAuthHeaders()),
33
+ ...config.headers,
34
+ };
35
+ const response = await fetch(this.config.baseUrl + url, {
36
+ ...config,
37
+ headers,
38
+ signal: controller.signal,
39
+ });
40
+ clearTimeout(timeoutId);
41
+ if (!response.ok) {
42
+ const defaultError = { message: response.statusText };
43
+ const errorData = await response.json().catch(() => defaultError);
44
+ const error = {
45
+ message: errorData.message || response.statusText,
46
+ code: errorData.code,
47
+ status: response.status,
48
+ details: errorData.details,
49
+ };
50
+ throw error;
51
+ }
52
+ const data = await response.json();
53
+ return {
54
+ data,
55
+ status: response.status,
56
+ statusText: response.statusText,
57
+ };
58
+ }
59
+ catch (error) {
60
+ if (error instanceof Error && error.name === 'AbortError') {
61
+ const timeoutError = {
62
+ message: 'Request timeout',
63
+ status: 408,
64
+ };
65
+ throw timeoutError;
66
+ }
67
+ throw error;
68
+ }
69
+ }
70
+ get(url, config) {
71
+ return this.request({ ...config, method: 'GET', url });
72
+ }
73
+ post(url, data, config) {
74
+ return this.request({ ...config, method: 'POST', url, body: JSON.stringify(data) });
75
+ }
76
+ put(url, data, config) {
77
+ return this.request({ ...config, method: 'PUT', url, body: JSON.stringify(data) });
78
+ }
79
+ delete(url, config) {
80
+ return this.request({ ...config, method: 'DELETE', url });
81
+ }
82
+ }
@@ -0,0 +1,103 @@
1
+ import { TokenManager } from './token-manager';
2
+ export class FlexbeAuth {
3
+ constructor(config) {
4
+ this.initialized = false;
5
+ this.initializing = false;
6
+ this.config = config;
7
+ this.tokenManager = TokenManager.getInstance();
8
+ if (this.config.authType === 'bearer') {
9
+ // Check if we have a valid token in storage
10
+ const existingToken = this.tokenManager.getToken();
11
+ if (existingToken) {
12
+ this.initialized = true;
13
+ }
14
+ // Don't start initialization here, let ensureInitialized handle it
15
+ }
16
+ else {
17
+ this.initialized = true;
18
+ }
19
+ }
20
+ async initializeBearerAuth() {
21
+ if (this.initializing) {
22
+ // Wait for the ongoing initialization to complete
23
+ while (this.initializing) {
24
+ await new Promise(resolve => setTimeout(resolve, 100));
25
+ }
26
+ return;
27
+ }
28
+ try {
29
+ this.initializing = true;
30
+ const controller = new AbortController();
31
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
32
+ const response = await fetch('/oauth/token', {
33
+ method: 'POST',
34
+ headers: {
35
+ 'Content-Type': 'application/json',
36
+ },
37
+ body: JSON.stringify({
38
+ grant_type: 'client_credentials',
39
+ }),
40
+ credentials: 'include',
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
+ this.tokenManager.setToken(data);
57
+ this.initialized = true;
58
+ }
59
+ catch (error) {
60
+ console.error('Failed to initialize bearer authentication:', error);
61
+ this.initialized = false; // Reset initialized state on error
62
+ throw error;
63
+ }
64
+ finally {
65
+ this.initializing = false;
66
+ }
67
+ }
68
+ async ensureInitialized() {
69
+ if (this.config.authType !== 'bearer' || this.initialized) {
70
+ return;
71
+ }
72
+ await this.initializeBearerAuth();
73
+ }
74
+ async getAuthHeaders() {
75
+ await this.ensureInitialized();
76
+ const headers = {
77
+ 'Content-Type': 'application/json',
78
+ };
79
+ if (this.config.authType === 'apiKey') {
80
+ headers['x-api-key'] = this.config.apiKey;
81
+ }
82
+ else if (this.config.authType === 'bearer') {
83
+ const token = this.tokenManager.getToken();
84
+ if (!token) {
85
+ // If no token is available, try to initialize again
86
+ this.initialized = false;
87
+ await this.ensureInitialized();
88
+ const newToken = this.tokenManager.getToken();
89
+ if (!newToken) {
90
+ throw new Error('No valid bearer token available');
91
+ }
92
+ headers['Authorization'] = `Bearer ${newToken}`;
93
+ }
94
+ else {
95
+ headers['Authorization'] = `Bearer ${token}`;
96
+ }
97
+ }
98
+ return headers;
99
+ }
100
+ isInitialized() {
101
+ return this.initialized;
102
+ }
103
+ }
@@ -1,4 +1,5 @@
1
1
  import { Pages } from './pages';
2
+ import { ApiClient } from './api-client';
2
3
  export class FlexbeClient {
3
4
  constructor(config) {
4
5
  const getEnvVar = (key) => {
@@ -12,95 +13,12 @@ export class FlexbeClient {
12
13
  timeout: config?.timeout || 30000,
13
14
  apiKey: config?.apiKey || getEnvVar('FLEXBE_API_KEY') || '',
14
15
  siteId: config?.siteId || getEnvVar('FLEXBE_SITE_ID'),
16
+ authType: config?.authType || 'apiKey',
15
17
  };
16
- if (!this.config.apiKey) {
17
- 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.');
18
20
  }
19
- this.pages = new Pages(this);
20
- }
21
- buildUrl(path, params) {
22
- const searchParams = new URLSearchParams();
23
- if (params) {
24
- Object.entries(params).forEach(([key, value]) => {
25
- if (value !== undefined && value !== null) {
26
- searchParams.append(key, String(value));
27
- }
28
- });
29
- }
30
- return `${path}${searchParams.toString() ? `?${searchParams.toString()}` : ''}`;
31
- }
32
- async request(config) {
33
- try {
34
- const controller = new AbortController();
35
- const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
36
- const url = this.buildUrl(config.url, config.params);
37
- const response = await fetch(this.config.baseUrl + url, {
38
- ...config,
39
- headers: {
40
- 'Authorization': `Bearer ${this.config.apiKey}`,
41
- 'Content-Type': 'application/json',
42
- ...config.headers,
43
- },
44
- signal: controller.signal,
45
- });
46
- clearTimeout(timeoutId);
47
- if (!response.ok) {
48
- const defaultError = { message: response.statusText };
49
- const errorData = await response.json().catch(() => defaultError);
50
- const error = {
51
- message: errorData.message || response.statusText,
52
- code: errorData.code,
53
- status: response.status,
54
- details: errorData.details,
55
- };
56
- throw error;
57
- }
58
- const data = await response.json();
59
- return {
60
- data,
61
- status: response.status,
62
- statusText: response.statusText,
63
- };
64
- }
65
- catch (error) {
66
- if (error instanceof Error && error.name === 'AbortError') {
67
- const timeoutError = {
68
- message: 'Request timeout',
69
- status: 408,
70
- };
71
- throw timeoutError;
72
- }
73
- throw error;
74
- }
75
- }
76
- get(url, config) {
77
- return this.request({ ...config, method: 'GET', url });
78
- }
79
- post(url, data, config) {
80
- return this.request({ ...config, method: 'POST', url, body: JSON.stringify(data) });
81
- }
82
- put(url, data, config) {
83
- return this.request({ ...config, method: 'PUT', url, body: JSON.stringify(data) });
84
- }
85
- delete(url, config) {
86
- return this.request({ ...config, method: 'DELETE', url });
87
- }
88
- getSiteUrl(path) {
89
- if (!this.config.siteId) {
90
- return path;
91
- }
92
- return `/sites/${this.config.siteId}${path}`;
93
- }
94
- sitesGet(path, config) {
95
- return this.get(this.getSiteUrl(path), config);
96
- }
97
- sitesPost(path, data, config) {
98
- return this.post(this.getSiteUrl(path), data, config);
99
- }
100
- sitesPut(path, data, config) {
101
- return this.put(this.getSiteUrl(path), data, config);
102
- }
103
- sitesDelete(path, config) {
104
- return this.delete(this.getSiteUrl(path), config);
21
+ this.api = new ApiClient(this.config);
22
+ this.pages = new Pages(this.api);
105
23
  }
106
24
  }