@flexbe/sdk 0.2.32 → 0.2.33

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.
Files changed (48) hide show
  1. package/README.md +6 -4
  2. package/dist/browser/client/api-client.js +19 -9
  3. package/dist/browser/client/client.js +1 -1
  4. package/dist/browser/client/meta-api.js +3 -3
  5. package/dist/browser/client/pages.js +43 -9
  6. package/dist/browser/client/token-manager.js +33 -24
  7. package/dist/cjs/client/api-client.js +8 -8
  8. package/dist/cjs/client/client.js +1 -1
  9. package/dist/cjs/client/meta-api.js +3 -3
  10. package/dist/cjs/client/pages.js +44 -13
  11. package/dist/cjs/client/token-manager.js +33 -24
  12. package/dist/esm/client/api-client.js +9 -9
  13. package/dist/esm/client/client.js +1 -1
  14. package/dist/esm/client/meta-api.js +3 -3
  15. package/dist/esm/client/pages.js +44 -13
  16. package/dist/esm/client/token-manager.js +33 -24
  17. package/dist/types/client/api-client.d.ts +1 -1
  18. package/dist/types/client/client.d.ts +2 -2
  19. package/dist/types/client/meta-api.d.ts +2 -2
  20. package/dist/types/client/pages.d.ts +30 -7
  21. package/dist/types/client/site-api.d.ts +1 -1
  22. package/dist/types/client/stat.d.ts +2 -2
  23. package/dist/types/client/token-manager.d.ts +1 -0
  24. package/dist/types/types/pages.d.ts +14 -2
  25. package/package.json +61 -64
  26. package/dist/browser/client/auth.js +0 -74
  27. package/dist/cjs/client/auth.js +0 -63
  28. package/dist/client/flexbe-client.d.ts +0 -13
  29. package/dist/client/flexbe-client.js +0 -62
  30. package/dist/esm/client/auth.js +0 -59
  31. package/dist/index.d.ts +0 -2
  32. package/dist/index.js +0 -18
  33. package/dist/src/client/flexbe-client.d.ts +0 -20
  34. package/dist/src/client/flexbe-client.js +0 -86
  35. package/dist/src/client/pages-client.d.ts +0 -14
  36. package/dist/src/client/pages-client.js +0 -23
  37. package/dist/src/index.d.ts +0 -4
  38. package/dist/src/index.js +0 -20
  39. package/dist/src/types/index.d.ts +0 -23
  40. package/dist/src/types/index.js +0 -2
  41. package/dist/src/types/pages.d.ts +0 -41
  42. package/dist/src/types/pages.js +0 -19
  43. package/dist/test/client/flexbe-client.test.d.ts +0 -1
  44. package/dist/test/client/flexbe-client.test.js +0 -38
  45. package/dist/test/client/pages-client.test.d.ts +0 -1
  46. package/dist/test/client/pages-client.test.js +0 -82
  47. package/dist/types/client/auth.d.ts +0 -11
  48. package/dist/types/index.js +0 -2
package/README.md CHANGED
@@ -19,7 +19,7 @@ const client = new FlexbeClient({
19
19
  baseUrl: 'https://api.flexbe.com', // optional
20
20
  timeout: 30000, // optional, defaults to 30 seconds
21
21
  });
22
- const siteApi = client.getSiteApi(SITE_ID)
22
+ const siteApi = client.getSiteApi(SITE_ID);
23
23
 
24
24
  // Using the Pages API for a specific site
25
25
  try {
@@ -28,14 +28,15 @@ try {
28
28
  limit: 10,
29
29
  offset: 0,
30
30
  type: 'page',
31
- status: 'published'
31
+ status: 'published',
32
32
  });
33
33
  console.log(pages.pages);
34
34
 
35
35
  // Get a single page from site
36
36
  const page = await siteApi.pages.getPage(123);
37
37
  console.log(page);
38
- } catch (error) {
38
+ }
39
+ catch (error) {
39
40
  console.error(error.message);
40
41
  }
41
42
 
@@ -58,6 +59,7 @@ try {
58
59
  ## Environment Variables
59
60
 
60
61
  The SDK supports the following environment variables:
62
+
61
63
  - `FLEXBE_API_KEY`: Your API key (required for API Key authentication)
62
64
  - `FLEXBE_API_URL`: Base URL (defaults to 'https://api.flexbe.com')
63
65
 
@@ -79,4 +81,4 @@ npm run lint
79
81
 
80
82
  ## License
81
83
 
82
- MIT
84
+ MIT
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { NotFoundException, ForbiddenException, BadRequestException, UnauthorizedException, ServerException, TimeoutException, FlexbeAuthType } from '../types';
10
+ import { BadRequestException, FlexbeAuthType, ForbiddenException, NotFoundException, ServerException, TimeoutException, UnauthorizedException } from '../types';
11
11
  import { TokenManager } from './token-manager';
12
12
  export class ApiClient {
13
13
  constructor(config) {
@@ -29,7 +29,7 @@ export class ApiClient {
29
29
  if (!token) {
30
30
  throw new Error('No valid bearer token available');
31
31
  }
32
- headers['Authorization'] = `Bearer ${token}`;
32
+ headers.Authorization = `Bearer ${token}`;
33
33
  }
34
34
  return headers;
35
35
  });
@@ -59,7 +59,7 @@ export class ApiClient {
59
59
  const defaultError = {
60
60
  message: response.statusText,
61
61
  error: response.statusText,
62
- statusCode: response.status
62
+ statusCode: response.status,
63
63
  };
64
64
  const errorData = yield response.json().catch(() => defaultError);
65
65
  switch (errorData.statusCode) {
@@ -81,7 +81,7 @@ export class ApiClient {
81
81
  message: errorData.message,
82
82
  error: errorData.error,
83
83
  statusCode: errorData.statusCode,
84
- errors: errorData.errors
84
+ errors: errorData.errors,
85
85
  };
86
86
  }
87
87
  }
@@ -112,18 +112,28 @@ export class ApiClient {
112
112
  });
113
113
  }
114
114
  get(url, config) {
115
- return this.request(Object.assign(Object.assign({}, config), { url, method: 'GET' }));
115
+ return __awaiter(this, void 0, void 0, function* () {
116
+ return this.request(Object.assign(Object.assign({}, config), { url, method: 'GET' }));
117
+ });
116
118
  }
117
119
  post(url, data, config) {
118
- return this.request(Object.assign(Object.assign({}, config), { url, method: 'POST', body: JSON.stringify(data) }));
120
+ return __awaiter(this, void 0, void 0, function* () {
121
+ return this.request(Object.assign(Object.assign({}, config), { url, method: 'POST', body: JSON.stringify(data) }));
122
+ });
119
123
  }
120
124
  put(url, data, config) {
121
- return this.request(Object.assign(Object.assign({}, config), { url, method: 'PUT', body: JSON.stringify(data) }));
125
+ return __awaiter(this, void 0, void 0, function* () {
126
+ return this.request(Object.assign(Object.assign({}, config), { url, method: 'PUT', body: JSON.stringify(data) }));
127
+ });
122
128
  }
123
129
  patch(url, data, config) {
124
- return this.request(Object.assign(Object.assign({}, config), { url, method: 'PATCH', body: JSON.stringify(data) }));
130
+ return __awaiter(this, void 0, void 0, function* () {
131
+ return this.request(Object.assign(Object.assign({}, config), { url, method: 'PATCH', body: JSON.stringify(data) }));
132
+ });
125
133
  }
126
134
  delete(url, config) {
127
- return this.request(Object.assign(Object.assign({}, config), { url, method: 'DELETE' }));
135
+ return __awaiter(this, void 0, void 0, function* () {
136
+ return this.request(Object.assign(Object.assign({}, config), { url, method: 'DELETE' }));
137
+ });
128
138
  }
129
139
  }
@@ -9,8 +9,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { FlexbeAuthType } from '../types';
11
11
  import { ApiClient } from './api-client';
12
- import { SiteApi } from './site-api';
13
12
  import { MetaApi } from './meta-api';
13
+ import { SiteApi } from './site-api';
14
14
  import { TokenManager } from './token-manager';
15
15
  export class FlexbeClient {
16
16
  constructor(config) {
@@ -18,7 +18,7 @@ export class MetaApi {
18
18
  getSiteLanguages() {
19
19
  return __awaiter(this, void 0, void 0, function* () {
20
20
  const response = yield this.api.get('/meta/site-languages', {
21
- headers: { 'Authorization': '' }
21
+ headers: { Authorization: '' },
22
22
  });
23
23
  return response.data;
24
24
  });
@@ -30,7 +30,7 @@ export class MetaApi {
30
30
  getUserLanguages() {
31
31
  return __awaiter(this, void 0, void 0, function* () {
32
32
  const response = yield this.api.get('/meta/user-languages', {
33
- headers: { 'Authorization': '' }
33
+ headers: { Authorization: '' },
34
34
  });
35
35
  return response.data;
36
36
  });
@@ -42,7 +42,7 @@ export class MetaApi {
42
42
  getSiteCurrencies() {
43
43
  return __awaiter(this, void 0, void 0, function* () {
44
44
  const response = yield this.api.get('/meta/site-currencies', {
45
- headers: { 'Authorization': '' }
45
+ headers: { Authorization: '' },
46
46
  });
47
47
  return response.data;
48
48
  });
@@ -30,7 +30,8 @@ export class Pages {
30
30
  */
31
31
  getPages(params) {
32
32
  return __awaiter(this, void 0, void 0, function* () {
33
- const processedParams = params ? Object.assign(Object.assign({}, params), { type: Array.isArray(params.type) ? params.type.join(',') : params.type, status: Array.isArray(params.status) ? params.status.join(',') : params.status }) : undefined;
33
+ const processedParams = params
34
+ ? Object.assign(Object.assign({}, params), { type: Array.isArray(params.type) ? params.type.join(',') : params.type, status: Array.isArray(params.status) ? params.status.join(',') : params.status }) : undefined;
34
35
  const response = yield this.api.get(`/sites/${this.siteId}/pages`, { params: processedParams });
35
36
  return response.data;
36
37
  });
@@ -144,6 +145,7 @@ export class Pages {
144
145
  * @param pageId - ID of the page to update
145
146
  * @param data - Update parameters including:
146
147
  * - status: New status for the page
148
+ * - versionId: ID of the version to set as current
147
149
  * - name: New name for the page (max 150 characters)
148
150
  * - uri: New URI for the page (max 255 characters, automatically normalized with leading and trailing slashes)
149
151
  * - language: New language for the page
@@ -157,10 +159,9 @@ export class Pages {
157
159
  * - ogTitle: Open Graph title for social sharing (max 200 characters)
158
160
  * - ogDescription: Open Graph description for social sharing (max 1000 characters)
159
161
  * - noindex: Whether to prevent search engine indexing
160
- * - grid: Grid configuration for the page
161
162
  * @throws {UnauthorizedException} When the API key is invalid or expired
162
- * @throws {NotFoundException} When the page is not found
163
- * @throws {ForbiddenException} When the page does not belong to the site
163
+ * @throws {NotFoundException} When the page or version is not found
164
+ * @throws {ForbiddenException} When the page does not belong to the site or version belongs to a different page
164
165
  * @throws {BadRequestException} When the update parameters are invalid
165
166
  * @throws {ServerException} When the server encounters an error
166
167
  * @throws {TimeoutException} When the request times out
@@ -233,7 +234,7 @@ export class Pages {
233
234
  bulkDeletePages(ids) {
234
235
  return __awaiter(this, void 0, void 0, function* () {
235
236
  const response = yield this.api.delete(`/sites/${this.siteId}/pages`, {
236
- body: JSON.stringify({ ids })
237
+ body: JSON.stringify({ ids }),
237
238
  });
238
239
  return response.data;
239
240
  });
@@ -272,6 +273,39 @@ export class Pages {
272
273
  return response.data;
273
274
  });
274
275
  }
276
+ /**
277
+ * Get list of page history items
278
+ * @param pageId - ID of the page to get history for
279
+ * @returns List of page history items
280
+ * @throws {UnauthorizedException} When the API key is invalid or expired
281
+ * @throws {NotFoundException} When the page is not found
282
+ * @throws {ForbiddenException} When the page does not belong to the site
283
+ * @throws {ServerException} When the server encounters an error
284
+ * @throws {TimeoutException} When the request times out
285
+ */
286
+ getPageHistory(pageId) {
287
+ return __awaiter(this, void 0, void 0, function* () {
288
+ const response = yield this.api.get(`/sites/${this.siteId}/pages/${pageId}/history`);
289
+ return response.data;
290
+ });
291
+ }
292
+ /**
293
+ * Get a specific page history item
294
+ * @param pageId - ID of the page
295
+ * @param versionId - ID of the history item to get
296
+ * @returns The requested page history item with data
297
+ * @throws {UnauthorizedException} When the API key is invalid or expired
298
+ * @throws {NotFoundException} When the page or history item is not found
299
+ * @throws {ForbiddenException} When the page does not belong to the site
300
+ * @throws {ServerException} When the server encounters an error
301
+ * @throws {TimeoutException} When the request times out
302
+ */
303
+ getPageHistoryItem(pageId, versionId) {
304
+ return __awaiter(this, void 0, void 0, function* () {
305
+ const response = yield this.api.get(`/sites/${this.siteId}/pages/${pageId}/history/${versionId}`);
306
+ return response.data;
307
+ });
308
+ }
275
309
  /**
276
310
  * Get list of page versions
277
311
  * @param pageId - ID of the page to get versions for
@@ -282,9 +316,9 @@ export class Pages {
282
316
  * @throws {ServerException} When the server encounters an error
283
317
  * @throws {TimeoutException} When the request times out
284
318
  */
285
- getPageHistory(pageId) {
319
+ getPageVersions(pageId) {
286
320
  return __awaiter(this, void 0, void 0, function* () {
287
- const response = yield this.api.get(`/sites/${this.siteId}/pages/${pageId}/history`);
321
+ const response = yield this.api.get(`/sites/${this.siteId}/pages/${pageId}/versions`);
288
322
  return response.data;
289
323
  });
290
324
  }
@@ -299,9 +333,9 @@ export class Pages {
299
333
  * @throws {ServerException} When the server encounters an error
300
334
  * @throws {TimeoutException} When the request times out
301
335
  */
302
- getPageHistoryItem(pageId, versionId) {
336
+ getPageVersion(pageId, versionId) {
303
337
  return __awaiter(this, void 0, void 0, function* () {
304
- const response = yield this.api.get(`/sites/${this.siteId}/pages/${pageId}/history/${versionId}`);
338
+ const response = yield this.api.get(`/sites/${this.siteId}/pages/${pageId}/versions/${versionId}`);
305
339
  return response.data;
306
340
  });
307
341
  }
@@ -13,6 +13,7 @@ const TOKEN_REFRESH_THRESHOLD = 5 * 60 * 1000; // update token 5 minutes before
13
13
  export class TokenManager {
14
14
  constructor() {
15
15
  this.tokenPromise = null;
16
+ this.isRevoking = false;
16
17
  }
17
18
  static getInstance() {
18
19
  if (!TokenManager.instance) {
@@ -29,6 +30,9 @@ export class TokenManager {
29
30
  getToken() {
30
31
  return __awaiter(this, void 0, void 0, function* () {
31
32
  var _a;
33
+ if (this.isRevoking) {
34
+ return null;
35
+ }
32
36
  const token = this.getStoredToken();
33
37
  if (token && token.expiresAt > Date.now()) {
34
38
  // TODO check if token expire less that 1 minute
@@ -45,10 +49,14 @@ export class TokenManager {
45
49
  }
46
50
  revokeToken() {
47
51
  return __awaiter(this, void 0, void 0, function* () {
52
+ this.isRevoking = true;
48
53
  const token = this.getStoredToken();
49
- this.clearToken();
50
- if (!token)
54
+ if (!token) {
55
+ this.isRevoking = false;
51
56
  return;
57
+ }
58
+ // Optimistic token cleanup
59
+ this.clearToken();
52
60
  try {
53
61
  const controller = new AbortController();
54
62
  const timeoutId = setTimeout(() => controller.abort(), 30000);
@@ -56,7 +64,7 @@ export class TokenManager {
56
64
  method: 'POST',
57
65
  headers: {
58
66
  'Content-Type': 'application/json',
59
- 'Authorization': `Bearer ${token.accessToken}`
67
+ Authorization: `Bearer ${token.accessToken}`,
60
68
  },
61
69
  body: JSON.stringify({ token: token.accessToken }),
62
70
  credentials: 'include',
@@ -67,11 +75,17 @@ export class TokenManager {
67
75
  catch (error) {
68
76
  console.error('Failed to revoke token:', error);
69
77
  }
78
+ finally {
79
+ // Finally cleanup the token
80
+ this.clearToken();
81
+ this.isRevoking = false;
82
+ }
70
83
  });
71
84
  }
72
85
  getStoredToken() {
73
- if (typeof window === 'undefined')
86
+ if (typeof window === 'undefined') {
74
87
  return null;
88
+ }
75
89
  const storedToken = localStorage.getItem(TOKEN_STORAGE_KEY);
76
90
  if (!storedToken) {
77
91
  return null;
@@ -104,28 +118,23 @@ export class TokenManager {
104
118
  return __awaiter(this, void 0, void 0, function* () {
105
119
  const controller = new AbortController();
106
120
  const timeoutId = setTimeout(() => controller.abort(), 30000);
107
- try {
108
- const response = yield fetch('/oauth/token', {
109
- method: 'POST',
110
- headers: { 'Content-Type': 'application/json' },
111
- body: JSON.stringify({ grant_type: 'client_credentials' }),
112
- credentials: 'include',
113
- signal: controller.signal,
114
- });
115
- clearTimeout(timeoutId);
116
- if (!response.ok) {
117
- const errorData = yield response.json().catch(() => ({ message: response.statusText }));
118
- if (response.status === 401) {
119
- throw new UnauthorizedException(errorData.message || response.statusText);
120
- }
121
- throw new Error(errorData.message || response.statusText);
121
+ const response = yield fetch('/oauth/token', {
122
+ method: 'POST',
123
+ headers: { 'Content-Type': 'application/json' },
124
+ body: JSON.stringify({ grant_type: 'client_credentials' }),
125
+ credentials: 'include',
126
+ signal: controller.signal,
127
+ });
128
+ clearTimeout(timeoutId);
129
+ if (!response.ok) {
130
+ const errorData = yield response.json().catch(() => ({ message: response.statusText }));
131
+ if (response.status === 401) {
132
+ throw new UnauthorizedException(errorData.message || response.statusText);
122
133
  }
123
- const data = yield response.json();
124
- this.setToken(data);
125
- }
126
- catch (error) {
127
- throw error;
134
+ throw new Error(errorData.message || response.statusText);
128
135
  }
136
+ const data = yield response.json();
137
+ this.setToken(data);
129
138
  });
130
139
  }
131
140
  setToken(tokenResponse) {
@@ -22,7 +22,7 @@ class ApiClient {
22
22
  if (!token) {
23
23
  throw new Error('No valid bearer token available');
24
24
  }
25
- headers['Authorization'] = `Bearer ${token}`;
25
+ headers.Authorization = `Bearer ${token}`;
26
26
  }
27
27
  return headers;
28
28
  }
@@ -57,7 +57,7 @@ class ApiClient {
57
57
  const defaultError = {
58
58
  message: response.statusText,
59
59
  error: response.statusText,
60
- statusCode: response.status
60
+ statusCode: response.status,
61
61
  };
62
62
  const errorData = await response.json().catch(() => defaultError);
63
63
  switch (errorData.statusCode) {
@@ -79,7 +79,7 @@ class ApiClient {
79
79
  message: errorData.message,
80
80
  error: errorData.error,
81
81
  statusCode: errorData.statusCode,
82
- errors: errorData.errors
82
+ errors: errorData.errors,
83
83
  };
84
84
  }
85
85
  }
@@ -108,19 +108,19 @@ class ApiClient {
108
108
  throw error;
109
109
  }
110
110
  }
111
- get(url, config) {
111
+ async get(url, config) {
112
112
  return this.request({ ...config, url, method: 'GET' });
113
113
  }
114
- post(url, data, config) {
114
+ async post(url, data, config) {
115
115
  return this.request({ ...config, url, method: 'POST', body: JSON.stringify(data) });
116
116
  }
117
- put(url, data, config) {
117
+ async put(url, data, config) {
118
118
  return this.request({ ...config, url, method: 'PUT', body: JSON.stringify(data) });
119
119
  }
120
- patch(url, data, config) {
120
+ async patch(url, data, config) {
121
121
  return this.request({ ...config, url, method: 'PATCH', body: JSON.stringify(data) });
122
122
  }
123
- delete(url, config) {
123
+ async delete(url, config) {
124
124
  return this.request({ ...config, url, method: 'DELETE' });
125
125
  }
126
126
  }
@@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FlexbeClient = void 0;
4
4
  const types_1 = require("../types");
5
5
  const api_client_1 = require("./api-client");
6
- const site_api_1 = require("./site-api");
7
6
  const meta_api_1 = require("./meta-api");
7
+ const site_api_1 = require("./site-api");
8
8
  const token_manager_1 = require("./token-manager");
9
9
  class FlexbeClient {
10
10
  constructor(config) {
@@ -11,7 +11,7 @@ class MetaApi {
11
11
  */
12
12
  async getSiteLanguages() {
13
13
  const response = await this.api.get('/meta/site-languages', {
14
- headers: { 'Authorization': '' }
14
+ headers: { Authorization: '' },
15
15
  });
16
16
  return response.data;
17
17
  }
@@ -21,7 +21,7 @@ class MetaApi {
21
21
  */
22
22
  async getUserLanguages() {
23
23
  const response = await this.api.get('/meta/user-languages', {
24
- headers: { 'Authorization': '' }
24
+ headers: { Authorization: '' },
25
25
  });
26
26
  return response.data;
27
27
  }
@@ -31,7 +31,7 @@ class MetaApi {
31
31
  */
32
32
  async getSiteCurrencies() {
33
33
  const response = await this.api.get('/meta/site-currencies', {
34
- headers: { 'Authorization': '' }
34
+ headers: { Authorization: '' },
35
35
  });
36
36
  return response.data;
37
37
  }
@@ -23,11 +23,13 @@ class Pages {
23
23
  * @throws {TimeoutException} When the request times out
24
24
  */
25
25
  async getPages(params) {
26
- const processedParams = params ? {
27
- ...params,
28
- type: Array.isArray(params.type) ? params.type.join(',') : params.type,
29
- status: Array.isArray(params.status) ? params.status.join(',') : params.status
30
- } : undefined;
26
+ const processedParams = params
27
+ ? {
28
+ ...params,
29
+ type: Array.isArray(params.type) ? params.type.join(',') : params.type,
30
+ status: Array.isArray(params.status) ? params.status.join(',') : params.status,
31
+ }
32
+ : undefined;
31
33
  const response = await this.api.get(`/sites/${this.siteId}/pages`, { params: processedParams });
32
34
  return response.data;
33
35
  }
@@ -126,6 +128,7 @@ class Pages {
126
128
  * @param pageId - ID of the page to update
127
129
  * @param data - Update parameters including:
128
130
  * - status: New status for the page
131
+ * - versionId: ID of the version to set as current
129
132
  * - name: New name for the page (max 150 characters)
130
133
  * - uri: New URI for the page (max 255 characters, automatically normalized with leading and trailing slashes)
131
134
  * - language: New language for the page
@@ -139,10 +142,9 @@ class Pages {
139
142
  * - ogTitle: Open Graph title for social sharing (max 200 characters)
140
143
  * - ogDescription: Open Graph description for social sharing (max 1000 characters)
141
144
  * - noindex: Whether to prevent search engine indexing
142
- * - grid: Grid configuration for the page
143
145
  * @throws {UnauthorizedException} When the API key is invalid or expired
144
- * @throws {NotFoundException} When the page is not found
145
- * @throws {ForbiddenException} When the page does not belong to the site
146
+ * @throws {NotFoundException} When the page or version is not found
147
+ * @throws {ForbiddenException} When the page does not belong to the site or version belongs to a different page
146
148
  * @throws {BadRequestException} When the update parameters are invalid
147
149
  * @throws {ServerException} When the server encounters an error
148
150
  * @throws {TimeoutException} When the request times out
@@ -208,7 +210,7 @@ class Pages {
208
210
  */
209
211
  async bulkDeletePages(ids) {
210
212
  const response = await this.api.delete(`/sites/${this.siteId}/pages`, {
211
- body: JSON.stringify({ ids })
213
+ body: JSON.stringify({ ids }),
212
214
  });
213
215
  return response.data;
214
216
  }
@@ -242,6 +244,35 @@ class Pages {
242
244
  const response = await this.api.put(`/sites/${this.siteId}/pages/${pageId}/content`, content);
243
245
  return response.data;
244
246
  }
247
+ /**
248
+ * Get list of page history items
249
+ * @param pageId - ID of the page to get history for
250
+ * @returns List of page history items
251
+ * @throws {UnauthorizedException} When the API key is invalid or expired
252
+ * @throws {NotFoundException} When the page is not found
253
+ * @throws {ForbiddenException} When the page does not belong to the site
254
+ * @throws {ServerException} When the server encounters an error
255
+ * @throws {TimeoutException} When the request times out
256
+ */
257
+ async getPageHistory(pageId) {
258
+ const response = await this.api.get(`/sites/${this.siteId}/pages/${pageId}/history`);
259
+ return response.data;
260
+ }
261
+ /**
262
+ * Get a specific page history item
263
+ * @param pageId - ID of the page
264
+ * @param versionId - ID of the history item to get
265
+ * @returns The requested page history item with data
266
+ * @throws {UnauthorizedException} When the API key is invalid or expired
267
+ * @throws {NotFoundException} When the page or history item is not found
268
+ * @throws {ForbiddenException} When the page does not belong to the site
269
+ * @throws {ServerException} When the server encounters an error
270
+ * @throws {TimeoutException} When the request times out
271
+ */
272
+ async getPageHistoryItem(pageId, versionId) {
273
+ const response = await this.api.get(`/sites/${this.siteId}/pages/${pageId}/history/${versionId}`);
274
+ return response.data;
275
+ }
245
276
  /**
246
277
  * Get list of page versions
247
278
  * @param pageId - ID of the page to get versions for
@@ -252,8 +283,8 @@ class Pages {
252
283
  * @throws {ServerException} When the server encounters an error
253
284
  * @throws {TimeoutException} When the request times out
254
285
  */
255
- async getPageHistory(pageId) {
256
- const response = await this.api.get(`/sites/${this.siteId}/pages/${pageId}/history`);
286
+ async getPageVersions(pageId) {
287
+ const response = await this.api.get(`/sites/${this.siteId}/pages/${pageId}/versions`);
257
288
  return response.data;
258
289
  }
259
290
  /**
@@ -267,8 +298,8 @@ class Pages {
267
298
  * @throws {ServerException} When the server encounters an error
268
299
  * @throws {TimeoutException} When the request times out
269
300
  */
270
- async getPageHistoryItem(pageId, versionId) {
271
- const response = await this.api.get(`/sites/${this.siteId}/pages/${pageId}/history/${versionId}`);
301
+ async getPageVersion(pageId, versionId) {
302
+ const response = await this.api.get(`/sites/${this.siteId}/pages/${pageId}/versions/${versionId}`);
272
303
  return response.data;
273
304
  }
274
305
  }
@@ -7,6 +7,7 @@ const TOKEN_REFRESH_THRESHOLD = 5 * 60 * 1000; // update token 5 minutes before
7
7
  class TokenManager {
8
8
  constructor() {
9
9
  this.tokenPromise = null;
10
+ this.isRevoking = false;
10
11
  }
11
12
  static getInstance() {
12
13
  if (!TokenManager.instance) {
@@ -21,6 +22,9 @@ class TokenManager {
21
22
  return TokenManager.instance;
22
23
  }
23
24
  async getToken() {
25
+ if (this.isRevoking) {
26
+ return null;
27
+ }
24
28
  const token = this.getStoredToken();
25
29
  if (token && token.expiresAt > Date.now()) {
26
30
  // TODO check if token expire less that 1 minute
@@ -35,10 +39,14 @@ class TokenManager {
35
39
  return retrievedToken?.accessToken ?? null;
36
40
  }
37
41
  async revokeToken() {
42
+ this.isRevoking = true;
38
43
  const token = this.getStoredToken();
39
- this.clearToken();
40
- if (!token)
44
+ if (!token) {
45
+ this.isRevoking = false;
41
46
  return;
47
+ }
48
+ // Optimistic token cleanup
49
+ this.clearToken();
42
50
  try {
43
51
  const controller = new AbortController();
44
52
  const timeoutId = setTimeout(() => controller.abort(), 30000);
@@ -46,7 +54,7 @@ class TokenManager {
46
54
  method: 'POST',
47
55
  headers: {
48
56
  'Content-Type': 'application/json',
49
- 'Authorization': `Bearer ${token.accessToken}`
57
+ Authorization: `Bearer ${token.accessToken}`,
50
58
  },
51
59
  body: JSON.stringify({ token: token.accessToken }),
52
60
  credentials: 'include',
@@ -57,10 +65,16 @@ class TokenManager {
57
65
  catch (error) {
58
66
  console.error('Failed to revoke token:', error);
59
67
  }
68
+ finally {
69
+ // Finally cleanup the token
70
+ this.clearToken();
71
+ this.isRevoking = false;
72
+ }
60
73
  }
61
74
  getStoredToken() {
62
- if (typeof window === 'undefined')
75
+ if (typeof window === 'undefined') {
63
76
  return null;
77
+ }
64
78
  const storedToken = localStorage.getItem(TOKEN_STORAGE_KEY);
65
79
  if (!storedToken) {
66
80
  return null;
@@ -90,28 +104,23 @@ class TokenManager {
90
104
  async doRetrieveToken() {
91
105
  const controller = new AbortController();
92
106
  const timeoutId = setTimeout(() => controller.abort(), 30000);
93
- try {
94
- const response = await fetch('/oauth/token', {
95
- method: 'POST',
96
- headers: { 'Content-Type': 'application/json' },
97
- body: JSON.stringify({ grant_type: 'client_credentials' }),
98
- credentials: 'include',
99
- signal: controller.signal,
100
- });
101
- clearTimeout(timeoutId);
102
- if (!response.ok) {
103
- const errorData = await response.json().catch(() => ({ message: response.statusText }));
104
- if (response.status === 401) {
105
- throw new types_1.UnauthorizedException(errorData.message || response.statusText);
106
- }
107
- throw new Error(errorData.message || response.statusText);
107
+ const response = await fetch('/oauth/token', {
108
+ method: 'POST',
109
+ headers: { 'Content-Type': 'application/json' },
110
+ body: JSON.stringify({ grant_type: 'client_credentials' }),
111
+ credentials: 'include',
112
+ signal: controller.signal,
113
+ });
114
+ clearTimeout(timeoutId);
115
+ if (!response.ok) {
116
+ const errorData = await response.json().catch(() => ({ message: response.statusText }));
117
+ if (response.status === 401) {
118
+ throw new types_1.UnauthorizedException(errorData.message || response.statusText);
108
119
  }
109
- const data = await response.json();
110
- this.setToken(data);
111
- }
112
- catch (error) {
113
- throw error;
120
+ throw new Error(errorData.message || response.statusText);
114
121
  }
122
+ const data = await response.json();
123
+ this.setToken(data);
115
124
  }
116
125
  setToken(tokenResponse) {
117
126
  const expiresAt = this.getExpirationFromToken(tokenResponse.accessToken);