@flexbe/sdk 0.2.28 → 0.2.30

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.
@@ -1,16 +1,9 @@
1
+ import { UnauthorizedException } from '../types';
1
2
  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
3
+ const TOKEN_REFRESH_THRESHOLD = 5 * 60 * 1000; // update token 5 minutes before expiration
5
4
  export class TokenManager {
6
5
  constructor() {
7
- this.token = null;
8
- this.refreshInterval = null;
9
- this.refreshTimeout = null;
10
6
  this.tokenPromise = null;
11
- this.debug = false;
12
- this.initializeFromStorage();
13
- this.setupStorageListener();
14
7
  }
15
8
  static getInstance() {
16
9
  if (!TokenManager.instance) {
@@ -18,81 +11,59 @@ export class TokenManager {
18
11
  }
19
12
  return TokenManager.instance;
20
13
  }
21
- initializeFromStorage() {
22
- if (typeof window === 'undefined')
23
- return;
24
- const storedToken = localStorage.getItem(TOKEN_STORAGE_KEY);
25
- if (!storedToken)
14
+ async getToken() {
15
+ const token = this.getStoredToken();
16
+ if (token && token.expiresAt > Date.now()) {
17
+ // TODO check if token expire less that 1 minute
18
+ // if so, retrieve a new token
19
+ if (token.expiresAt - Date.now() < TOKEN_REFRESH_THRESHOLD) {
20
+ void this.retrieveToken();
21
+ }
22
+ return token.accessToken;
23
+ }
24
+ await this.retrieveToken();
25
+ const retrievedToken = this.getStoredToken();
26
+ return retrievedToken?.accessToken ?? null;
27
+ }
28
+ async revokeToken() {
29
+ const token = this.getStoredToken();
30
+ this.clearToken();
31
+ if (!token)
26
32
  return;
27
33
  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
- }
34
+ const controller = new AbortController();
35
+ const timeoutId = setTimeout(() => controller.abort(), 30000);
36
+ await fetch('/oauth/revoke', {
37
+ method: 'POST',
38
+ headers: {
39
+ 'Content-Type': 'application/json',
40
+ 'Authorization': `Bearer ${token.accessToken}`
41
+ },
42
+ body: JSON.stringify({ token: token.accessToken }),
43
+ credentials: 'include',
44
+ signal: controller.signal,
45
+ });
46
+ clearTimeout(timeoutId);
36
47
  }
37
48
  catch (error) {
38
- console.error('Failed to parse stored token:', error);
39
- this.clearToken();
49
+ console.error('Failed to revoke token:', error);
40
50
  }
41
51
  }
42
- setupStorageListener() {
52
+ getStoredToken() {
43
53
  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) {
54
+ return null;
55
+ const storedToken = localStorage.getItem(TOKEN_STORAGE_KEY);
56
+ if (!storedToken) {
57
+ return null;
58
+ }
79
59
  try {
80
- const [, payload] = token.split('.');
81
- const decodedPayload = JSON.parse(atob(payload));
82
- return decodedPayload.exp * 1000; // Convert to milliseconds
60
+ const token = JSON.parse(storedToken);
61
+ return token;
83
62
  }
84
63
  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;
64
+ console.error('getStoredToken: Failed to parse stored token:', error);
65
+ return null;
93
66
  }
94
- await this.retrieveToken();
95
- return this.token?.accessToken ?? null;
96
67
  }
97
68
  async retrieveToken() {
98
69
  if (this.tokenPromise) {
@@ -121,131 +92,43 @@ export class TokenManager {
121
92
  clearTimeout(timeoutId);
122
93
  if (!response.ok) {
123
94
  const errorData = await response.json().catch(() => ({ message: response.statusText }));
95
+ if (response.status === 401) {
96
+ throw new UnauthorizedException(errorData.message || response.statusText);
97
+ }
124
98
  throw new Error(errorData.message || response.statusText);
125
99
  }
126
100
  const data = await response.json();
127
101
  this.setToken(data);
128
102
  }
129
103
  catch (error) {
130
- console.error('Failed to retrieve token:', error);
131
- this.clearToken();
132
- // Schedule a retry after REFRESH_CHECK_INTERVAL
133
- setTimeout(() => {
134
- void this.retrieveToken();
135
- }, REFRESH_CHECK_INTERVAL);
136
104
  throw error;
137
105
  }
138
106
  }
139
- startRefreshInterval() {
140
- this.clearRefreshTimers();
141
- if (!this.token)
142
- return;
143
- const tokenLifetime = this.token.expiresAt - Date.now();
144
- const refreshThreshold = Math.round(tokenLifetime * REFRESH_THRESHOLD);
145
- const timeUntilRefresh = refreshThreshold - (Math.random() * MAX_REFRESH_DELAY);
146
- this.logTokenStatus('Starting refresh interval', {
147
- tokenLifetime: `${Math.round(tokenLifetime / 1000)} seconds`,
148
- refreshThreshold: `${Math.round(refreshThreshold / 1000)} seconds`,
149
- timeUntilRefresh: `${Math.round(timeUntilRefresh / 1000)} seconds`,
150
- });
151
- this.scheduleRefresh(timeUntilRefresh);
152
- this.refreshInterval = window.setInterval(() => {
153
- if (!this.token)
154
- return;
155
- const timeUntilExpiry = this.token.expiresAt - Date.now();
156
- if (timeUntilExpiry <= 0) {
157
- this.logTokenStatus('Token expired');
158
- this.clearToken();
159
- void this.retrieveToken();
160
- return;
161
- }
162
- const refreshThreshold = Math.round(timeUntilExpiry * REFRESH_THRESHOLD);
163
- if (timeUntilExpiry <= refreshThreshold) {
164
- this.logTokenStatus('Refreshing token', {
165
- timeUntilExpiry: `${Math.round(timeUntilExpiry / 1000)} seconds`,
166
- refreshThreshold: `${Math.round(refreshThreshold / 1000)} seconds`,
167
- });
168
- this.scheduleRefresh(refreshThreshold - (Math.random() * MAX_REFRESH_DELAY));
169
- }
170
- }, REFRESH_CHECK_INTERVAL);
171
- }
172
- scheduleRefresh(delay) {
173
- if (this.refreshTimeout) {
174
- window.clearTimeout(this.refreshTimeout);
175
- }
176
- this.refreshTimeout = window.setTimeout(() => {
177
- const token = this.token;
178
- if (token && token.expiresAt - Date.now() <= token.expiresAt * REFRESH_THRESHOLD) {
179
- void this.retrieveToken();
180
- }
181
- }, delay);
182
- }
183
- clearRefreshTimers() {
184
- if (this.refreshInterval) {
185
- clearInterval(this.refreshInterval);
186
- this.refreshInterval = null;
187
- }
188
- if (this.refreshTimeout) {
189
- window.clearTimeout(this.refreshTimeout);
190
- this.refreshTimeout = null;
191
- }
192
- }
193
- logTokenStatus(message, additionalInfo = {}) {
194
- if (!this.debug)
195
- return;
196
- const token = this.token;
197
- if (!token)
198
- return;
199
- console.log(message, {
200
- expiresIn: `${Math.round((token.expiresAt - Date.now()) / 1000)} seconds`,
201
- expiresAt: new Date(token.expiresAt).toISOString(),
202
- ...additionalInfo,
203
- });
204
- }
205
107
  setToken(tokenResponse) {
206
108
  const expiresAt = this.getExpirationFromToken(tokenResponse.accessToken);
207
- this.token = {
109
+ const token = {
208
110
  accessToken: tokenResponse.accessToken,
209
111
  expiresAt,
210
112
  };
211
- this.logTokenStatus('Token set', {
212
- expiresAt: new Date(expiresAt).toISOString(),
213
- });
214
- if (typeof window !== 'undefined') {
215
- localStorage.setItem(TOKEN_STORAGE_KEY, JSON.stringify(this.token));
216
- }
217
- this.startRefreshInterval();
218
- }
219
- clearToken() {
220
- this.token = null;
221
- this.clearRefreshTimers();
222
113
  if (typeof window !== 'undefined') {
223
- localStorage.removeItem(TOKEN_STORAGE_KEY);
114
+ localStorage.setItem(TOKEN_STORAGE_KEY, JSON.stringify(token));
224
115
  }
225
116
  }
226
- async revokeToken() {
227
- const token = this.token;
228
- this.clearToken();
229
- if (!token)
230
- return;
117
+ getExpirationFromToken(token) {
231
118
  try {
232
- const controller = new AbortController();
233
- const timeoutId = setTimeout(() => controller.abort(), 30000);
234
- await fetch('/oauth/revoke', {
235
- method: 'POST',
236
- headers: {
237
- 'Content-Type': 'application/json',
238
- 'Authorization': `Bearer ${token.accessToken}`
239
- },
240
- body: JSON.stringify({ token: token.accessToken }),
241
- credentials: 'include',
242
- signal: controller.signal,
243
- });
244
- clearTimeout(timeoutId);
119
+ const [, payload] = token.split('.');
120
+ const decodedPayload = JSON.parse(atob(payload));
121
+ return decodedPayload.exp * 1000; // Convert to milliseconds
245
122
  }
246
123
  catch (error) {
247
- console.error('Failed to revoke token:', error);
248
- // Even if revocation fails, we still want to clear the local token
124
+ console.error('getExpirationFromToken: Failed to parse token expiration:', error);
125
+ return Date.now() + (4 * 60 * 1000); // Default to 4 minutes if parsing fails
126
+ }
127
+ }
128
+ clearToken() {
129
+ if (typeof window === 'undefined') {
130
+ return;
249
131
  }
132
+ localStorage.removeItem(TOKEN_STORAGE_KEY);
250
133
  }
251
134
  }
@@ -4,44 +4,56 @@ export var FlexbeAuthType;
4
4
  FlexbeAuthType["BEARER"] = "bearer";
5
5
  })(FlexbeAuthType || (FlexbeAuthType = {}));
6
6
  export class NotFoundException extends Error {
7
- constructor(message) {
7
+ constructor(message, error, errors) {
8
8
  super(Array.isArray(message) ? message.join(', ') : message);
9
9
  this.statusCode = 404;
10
10
  this.name = 'NotFoundException';
11
+ this.error = error || 'not_found';
12
+ this.errors = errors;
11
13
  }
12
14
  }
13
15
  export class ForbiddenException extends Error {
14
- constructor(message) {
16
+ constructor(message, error, errors) {
15
17
  super(Array.isArray(message) ? message.join(', ') : message);
16
18
  this.statusCode = 403;
17
19
  this.name = 'ForbiddenException';
20
+ this.error = error || 'forbidden';
21
+ this.errors = errors;
18
22
  }
19
23
  }
20
24
  export class BadRequestException extends Error {
21
- constructor(message) {
25
+ constructor(message, error, errors) {
22
26
  super(Array.isArray(message) ? message.join(', ') : message);
23
27
  this.statusCode = 400;
24
28
  this.name = 'BadRequestException';
29
+ this.error = error || 'bad_request';
30
+ this.errors = errors;
25
31
  }
26
32
  }
27
33
  export class UnauthorizedException extends Error {
28
- constructor(message) {
34
+ constructor(message, error, errors) {
29
35
  super(Array.isArray(message) ? message.join(', ') : message);
30
36
  this.statusCode = 401;
31
37
  this.name = 'UnauthorizedException';
38
+ this.error = error || 'unauthorized';
39
+ this.errors = errors;
32
40
  }
33
41
  }
34
42
  export class ServerException extends Error {
35
- constructor(message, statusCode = 500) {
43
+ constructor(message, error, statusCode = 500, errors) {
36
44
  super(Array.isArray(message) ? message.join(', ') : message);
37
45
  this.name = 'ServerException';
46
+ this.error = error || 'server_error';
38
47
  this.statusCode = statusCode;
48
+ this.errors = errors;
39
49
  }
40
50
  }
41
51
  export class TimeoutException extends Error {
42
- constructor(message) {
52
+ constructor(message, error, errors) {
43
53
  super(Array.isArray(message) ? message.join(', ') : message);
44
54
  this.statusCode = 408;
45
55
  this.name = 'TimeoutException';
56
+ this.error = error || 'timeout';
57
+ this.errors = errors;
46
58
  }
47
59
  }
@@ -1,8 +1,9 @@
1
1
  import { FlexbeConfig, FlexbeResponse } from '../types';
2
2
  export declare class ApiClient {
3
3
  private readonly config;
4
- private readonly auth;
4
+ private readonly tokenManager;
5
5
  constructor(config: FlexbeConfig);
6
+ private getAuthHeaders;
6
7
  private buildUrl;
7
8
  private request;
8
9
  get<T>(url: string, config?: RequestInit & {
@@ -1,24 +1,13 @@
1
- import { TokenResponse } from '../types';
2
1
  export declare class TokenManager {
3
2
  private static instance;
4
- private token;
5
- private refreshInterval;
6
- private refreshTimeout;
7
3
  private tokenPromise;
8
- private debug;
9
- private constructor();
10
4
  static getInstance(): TokenManager;
11
- private initializeFromStorage;
12
- private setupStorageListener;
13
- private getExpirationFromToken;
14
5
  getToken(): Promise<string | null>;
6
+ revokeToken(): Promise<void>;
7
+ private getStoredToken;
15
8
  private retrieveToken;
16
9
  private doRetrieveToken;
17
- private startRefreshInterval;
18
- private scheduleRefresh;
19
- private clearRefreshTimers;
20
- private logTokenStatus;
21
- setToken(tokenResponse: TokenResponse): void;
22
- clearToken(): void;
23
- revokeToken(): Promise<void>;
10
+ private setToken;
11
+ private getExpirationFromToken;
12
+ private clearToken;
24
13
  }
@@ -8,6 +8,9 @@ export interface FlexbeConfig {
8
8
  timeout?: number;
9
9
  siteId?: string;
10
10
  authType?: FlexbeAuthType;
11
+ hooks?: {
12
+ onUnauthorized?: () => void;
13
+ };
11
14
  }
12
15
  export interface FlexbeResponse<T> {
13
16
  data: T;
@@ -18,11 +21,19 @@ export interface FlexbeErrorResponse {
18
21
  message: string | string[];
19
22
  error: string;
20
23
  statusCode: number;
24
+ errors?: FlexbeBulkError[];
21
25
  }
22
26
  export interface FlexbeError {
23
27
  message: string | string[];
24
28
  error: string;
25
29
  statusCode: number;
30
+ errors?: FlexbeBulkError[];
31
+ }
32
+ export interface FlexbeBulkError {
33
+ id: number;
34
+ message: string;
35
+ error: string;
36
+ code: number;
26
37
  }
27
38
  export interface JwtToken {
28
39
  accessToken: string;
@@ -38,26 +49,38 @@ export interface Pagination {
38
49
  }
39
50
  export declare class NotFoundException extends Error {
40
51
  readonly statusCode = 404;
41
- constructor(message: string | string[]);
52
+ readonly errors?: FlexbeBulkError[];
53
+ readonly error: string;
54
+ constructor(message: string | string[], error?: string, errors?: FlexbeBulkError[]);
42
55
  }
43
56
  export declare class ForbiddenException extends Error {
44
57
  readonly statusCode = 403;
45
- constructor(message: string | string[]);
58
+ readonly errors?: FlexbeBulkError[];
59
+ readonly error: string;
60
+ constructor(message: string | string[], error?: string, errors?: FlexbeBulkError[]);
46
61
  }
47
62
  export declare class BadRequestException extends Error {
48
63
  readonly statusCode = 400;
49
- constructor(message: string | string[]);
64
+ readonly errors?: FlexbeBulkError[];
65
+ readonly error: string;
66
+ constructor(message: string | string[], error?: string, errors?: FlexbeBulkError[]);
50
67
  }
51
68
  export declare class UnauthorizedException extends Error {
52
69
  readonly statusCode = 401;
53
- constructor(message: string | string[]);
70
+ readonly errors?: FlexbeBulkError[];
71
+ readonly error: string;
72
+ constructor(message: string | string[], error?: string, errors?: FlexbeBulkError[]);
54
73
  }
55
74
  export declare class ServerException extends Error {
56
75
  readonly statusCode: number;
57
- constructor(message: string | string[], statusCode?: number);
76
+ readonly errors?: FlexbeBulkError[];
77
+ readonly error: string;
78
+ constructor(message: string | string[], error?: string, statusCode?: number, errors?: FlexbeBulkError[]);
58
79
  }
59
80
  export declare class TimeoutException extends Error {
60
81
  readonly statusCode = 408;
61
- constructor(message: string | string[]);
82
+ readonly errors?: FlexbeBulkError[];
83
+ readonly error: string;
84
+ constructor(message: string | string[], error?: string, errors?: FlexbeBulkError[]);
62
85
  }
63
86
  export type SiteApi = import('../client/site-api').SiteApi;
@@ -1,4 +1,4 @@
1
- import { Pagination } from './index';
1
+ import { FlexbeBulkError, Pagination } from './index';
2
2
  export interface GridConfig {
3
3
  color?: string;
4
4
  desktop?: {
@@ -99,14 +99,9 @@ export interface UpdatePageParams {
99
99
  export interface BulkUpdatePageItem extends UpdatePageParams {
100
100
  id: number;
101
101
  }
102
- export interface BulkUpdateError {
103
- id: number;
104
- code: number;
105
- message: string;
106
- }
107
102
  export interface BulkUpdateResponse {
108
103
  updated: Page[];
109
- errors: BulkUpdateError[];
104
+ errors: FlexbeBulkError[];
110
105
  }
111
106
  export interface BulkUpdateFolderItem extends UpdateFolderParams {
112
107
  id: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flexbe/sdk",
3
- "version": "0.2.28",
3
+ "version": "0.2.30",
4
4
  "description": "TypeScript SDK for Flexbe API",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -24,8 +24,7 @@
24
24
  "format": "prettier --write \"src/**/*.ts\"",
25
25
  "prepare": "npm run build",
26
26
  "prepublishOnly": "npm test && npm run lint",
27
- "version": "npm version",
28
- "publish": "npm publish --access public"
27
+ "version": "npm version"
29
28
  },
30
29
  "keywords": [
31
30
  "flexbe",