@tiquo/dom-package 1.0.0 → 1.0.1
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 +76 -6
- package/dist/index.d.mts +64 -29
- package/dist/index.d.ts +64 -29
- package/dist/index.js +274 -103
- package/dist/index.mjs +274 -103
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -7,12 +7,32 @@ var TiquoAuthError = class extends Error {
|
|
|
7
7
|
this.name = "TiquoAuthError";
|
|
8
8
|
}
|
|
9
9
|
};
|
|
10
|
+
function decodeJWT(token) {
|
|
11
|
+
try {
|
|
12
|
+
const parts = token.split(".");
|
|
13
|
+
if (parts.length !== 3) return null;
|
|
14
|
+
const payload = parts[1];
|
|
15
|
+
const decoded = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
|
|
16
|
+
return JSON.parse(decoded);
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function isTokenExpired(token, bufferSeconds = 300) {
|
|
22
|
+
const payload = decodeJWT(token);
|
|
23
|
+
if (!payload) return true;
|
|
24
|
+
const expiresAt = payload.exp * 1e3;
|
|
25
|
+
const bufferMs = bufferSeconds * 1e3;
|
|
26
|
+
return Date.now() > expiresAt - bufferMs;
|
|
27
|
+
}
|
|
10
28
|
var TiquoAuth = class {
|
|
11
29
|
constructor(config) {
|
|
12
|
-
this.
|
|
30
|
+
this.accessToken = null;
|
|
31
|
+
this.refreshToken = null;
|
|
13
32
|
this.session = null;
|
|
14
33
|
this.listeners = /* @__PURE__ */ new Set();
|
|
15
34
|
this.refreshTimer = null;
|
|
35
|
+
this.isRefreshing = false;
|
|
16
36
|
// Multi-tab sync
|
|
17
37
|
this.broadcastChannel = null;
|
|
18
38
|
this.isProcessingTabSync = false;
|
|
@@ -30,14 +50,17 @@ var TiquoAuth = class {
|
|
|
30
50
|
apiEndpoint: config.apiEndpoint || "https://edge.tiquo.app",
|
|
31
51
|
storagePrefix: config.storagePrefix || "tiquo_auth_",
|
|
32
52
|
debug: config.debug || false,
|
|
33
|
-
enableTabSync: config.enableTabSync !== false
|
|
53
|
+
enableTabSync: config.enableTabSync !== false,
|
|
34
54
|
// Default true
|
|
55
|
+
accessToken: config.accessToken,
|
|
56
|
+
refreshToken: config.refreshToken
|
|
35
57
|
};
|
|
36
58
|
this.tabId = this.generateTabId();
|
|
37
59
|
if (this.config.enableTabSync) {
|
|
38
60
|
this.initTabSync();
|
|
39
61
|
}
|
|
40
|
-
this.
|
|
62
|
+
this.checkForInjectedTokens();
|
|
63
|
+
this.restoreTokens();
|
|
41
64
|
}
|
|
42
65
|
// ============================================
|
|
43
66
|
// PUBLIC METHODS
|
|
@@ -82,13 +105,16 @@ var TiquoAuth = class {
|
|
|
82
105
|
throw new TiquoAuthError(error.error || "Invalid OTP", "OTP_VERIFY_FAILED", response.status);
|
|
83
106
|
}
|
|
84
107
|
const result = await response.json();
|
|
85
|
-
this.
|
|
86
|
-
this.
|
|
108
|
+
this.accessToken = result.accessToken;
|
|
109
|
+
this.refreshToken = result.refreshToken;
|
|
110
|
+
this.saveTokens();
|
|
87
111
|
await this.refreshSession();
|
|
88
112
|
this.broadcastTabSync("LOGIN");
|
|
89
113
|
return {
|
|
90
114
|
success: true,
|
|
91
|
-
|
|
115
|
+
accessToken: result.accessToken,
|
|
116
|
+
refreshToken: result.refreshToken,
|
|
117
|
+
expiresIn: result.expiresIn,
|
|
92
118
|
expiresAt: result.expiresAt,
|
|
93
119
|
isNewUser: result.isNewUser,
|
|
94
120
|
hasCustomer: result.hasCustomer
|
|
@@ -98,10 +124,11 @@ var TiquoAuth = class {
|
|
|
98
124
|
* Get the current authenticated user and customer data
|
|
99
125
|
*/
|
|
100
126
|
async getUser() {
|
|
101
|
-
if (!this.
|
|
127
|
+
if (!this.accessToken) {
|
|
102
128
|
return null;
|
|
103
129
|
}
|
|
104
|
-
|
|
130
|
+
await this.refreshTokenIfNeeded();
|
|
131
|
+
if (this.session) {
|
|
105
132
|
return this.session;
|
|
106
133
|
}
|
|
107
134
|
return this.refreshSession();
|
|
@@ -110,60 +137,57 @@ var TiquoAuth = class {
|
|
|
110
137
|
* Check if user is currently authenticated
|
|
111
138
|
*/
|
|
112
139
|
isAuthenticated() {
|
|
113
|
-
return !!this.
|
|
140
|
+
return !!this.accessToken && !isTokenExpired(this.accessToken, 0);
|
|
114
141
|
}
|
|
115
142
|
/**
|
|
116
143
|
* Update the authenticated customer's profile
|
|
117
144
|
* Only allows updating the logged-in customer's own data
|
|
118
145
|
*/
|
|
119
146
|
async updateProfile(updates) {
|
|
120
|
-
|
|
121
|
-
throw new TiquoAuthError("Not authenticated", "NOT_AUTHENTICATED");
|
|
122
|
-
}
|
|
147
|
+
await this.ensureValidToken();
|
|
123
148
|
this.log("Updating customer profile:", updates);
|
|
124
|
-
const response = await this.request("/api/
|
|
149
|
+
const response = await this.request("/api/client/v1/profile", {
|
|
125
150
|
method: "PATCH",
|
|
126
|
-
body: JSON.stringify(
|
|
127
|
-
publicKey: this.config.publicKey,
|
|
128
|
-
sessionToken: this.sessionToken,
|
|
129
|
-
updates
|
|
130
|
-
})
|
|
151
|
+
body: JSON.stringify(updates)
|
|
131
152
|
});
|
|
132
153
|
if (!response.ok) {
|
|
133
154
|
const error = await response.json().catch(() => ({ error: "Failed to update profile" }));
|
|
134
155
|
throw new TiquoAuthError(error.error || "Failed to update profile", "PROFILE_UPDATE_FAILED", response.status);
|
|
135
156
|
}
|
|
136
157
|
const result = await response.json();
|
|
137
|
-
if (this.session && result.customer) {
|
|
158
|
+
if (this.session && result.data?.customer) {
|
|
138
159
|
this.session = {
|
|
139
160
|
...this.session,
|
|
140
161
|
customer: {
|
|
141
|
-
id: result.customer.id,
|
|
142
|
-
firstName: result.customer.firstName,
|
|
143
|
-
lastName: result.customer.lastName,
|
|
144
|
-
displayName: result.customer.displayName,
|
|
145
|
-
customerNumber: result.customer.customerNumber,
|
|
146
|
-
email: result.customer.email,
|
|
147
|
-
phone: result.customer.phone
|
|
162
|
+
id: result.data.customer.id,
|
|
163
|
+
firstName: result.data.customer.firstName,
|
|
164
|
+
lastName: result.data.customer.lastName,
|
|
165
|
+
displayName: result.data.customer.displayName,
|
|
166
|
+
customerNumber: result.data.customer.customerNumber,
|
|
167
|
+
email: result.data.customer.email,
|
|
168
|
+
phone: result.data.customer.phone
|
|
148
169
|
}
|
|
149
170
|
};
|
|
150
171
|
this.notifyListeners();
|
|
151
172
|
this.broadcastTabSync("SESSION_UPDATE");
|
|
152
173
|
}
|
|
153
|
-
return
|
|
174
|
+
return {
|
|
175
|
+
success: true,
|
|
176
|
+
customer: result.data?.customer
|
|
177
|
+
};
|
|
154
178
|
}
|
|
155
179
|
/**
|
|
156
180
|
* Log out the current user
|
|
157
181
|
*/
|
|
158
182
|
async logout() {
|
|
159
183
|
this.log("Logging out");
|
|
160
|
-
if (this.
|
|
184
|
+
if (this.refreshToken) {
|
|
161
185
|
try {
|
|
162
186
|
await this.request("/api/auth-dom/logout", {
|
|
163
187
|
method: "POST",
|
|
164
188
|
body: JSON.stringify({
|
|
165
189
|
publicKey: this.config.publicKey,
|
|
166
|
-
|
|
190
|
+
refreshToken: this.refreshToken
|
|
167
191
|
})
|
|
168
192
|
});
|
|
169
193
|
} catch (error) {
|
|
@@ -171,7 +195,7 @@ var TiquoAuth = class {
|
|
|
171
195
|
}
|
|
172
196
|
}
|
|
173
197
|
this.broadcastTabSync("LOGOUT");
|
|
174
|
-
this.
|
|
198
|
+
this.clearTokens();
|
|
175
199
|
}
|
|
176
200
|
/**
|
|
177
201
|
* Subscribe to authentication state changes
|
|
@@ -188,12 +212,9 @@ var TiquoAuth = class {
|
|
|
188
212
|
* Only returns orders for the logged-in customer
|
|
189
213
|
*/
|
|
190
214
|
async getOrders(options) {
|
|
191
|
-
|
|
192
|
-
throw new TiquoAuthError("Not authenticated", "NOT_AUTHENTICATED");
|
|
193
|
-
}
|
|
215
|
+
await this.ensureValidToken();
|
|
194
216
|
this.log("Fetching customer orders:", options);
|
|
195
|
-
const url = new URL(`${this.config.apiEndpoint}/api/
|
|
196
|
-
url.searchParams.set("publicKey", this.config.publicKey);
|
|
217
|
+
const url = new URL(`${this.config.apiEndpoint}/api/client/v1/orders`);
|
|
197
218
|
if (options?.limit) {
|
|
198
219
|
url.searchParams.set("limit", options.limit.toString());
|
|
199
220
|
}
|
|
@@ -206,7 +227,7 @@ var TiquoAuth = class {
|
|
|
206
227
|
const response = await fetch(url.toString(), {
|
|
207
228
|
method: "GET",
|
|
208
229
|
headers: {
|
|
209
|
-
"Authorization": `Bearer ${this.
|
|
230
|
+
"Authorization": `Bearer ${this.accessToken}`
|
|
210
231
|
},
|
|
211
232
|
credentials: "include"
|
|
212
233
|
});
|
|
@@ -214,19 +235,17 @@ var TiquoAuth = class {
|
|
|
214
235
|
const error = await response.json().catch(() => ({ error: "Failed to get orders" }));
|
|
215
236
|
throw new TiquoAuthError(error.error || "Failed to get orders", "GET_ORDERS_FAILED", response.status);
|
|
216
237
|
}
|
|
217
|
-
|
|
238
|
+
const result = await response.json();
|
|
239
|
+
return result.data || { orders: [], hasMore: false };
|
|
218
240
|
}
|
|
219
241
|
/**
|
|
220
242
|
* Get the authenticated customer's booking history
|
|
221
243
|
* Only returns bookings for the logged-in customer
|
|
222
244
|
*/
|
|
223
245
|
async getBookings(options) {
|
|
224
|
-
|
|
225
|
-
throw new TiquoAuthError("Not authenticated", "NOT_AUTHENTICATED");
|
|
226
|
-
}
|
|
246
|
+
await this.ensureValidToken();
|
|
227
247
|
this.log("Fetching customer bookings:", options);
|
|
228
|
-
const url = new URL(`${this.config.apiEndpoint}/api/
|
|
229
|
-
url.searchParams.set("publicKey", this.config.publicKey);
|
|
248
|
+
const url = new URL(`${this.config.apiEndpoint}/api/client/v1/bookings`);
|
|
230
249
|
if (options?.limit) {
|
|
231
250
|
url.searchParams.set("limit", options.limit.toString());
|
|
232
251
|
}
|
|
@@ -242,7 +261,7 @@ var TiquoAuth = class {
|
|
|
242
261
|
const response = await fetch(url.toString(), {
|
|
243
262
|
method: "GET",
|
|
244
263
|
headers: {
|
|
245
|
-
"Authorization": `Bearer ${this.
|
|
264
|
+
"Authorization": `Bearer ${this.accessToken}`
|
|
246
265
|
},
|
|
247
266
|
credentials: "include"
|
|
248
267
|
});
|
|
@@ -250,19 +269,17 @@ var TiquoAuth = class {
|
|
|
250
269
|
const error = await response.json().catch(() => ({ error: "Failed to get bookings" }));
|
|
251
270
|
throw new TiquoAuthError(error.error || "Failed to get bookings", "GET_BOOKINGS_FAILED", response.status);
|
|
252
271
|
}
|
|
253
|
-
|
|
272
|
+
const result = await response.json();
|
|
273
|
+
return result.data || { bookings: [], hasMore: false };
|
|
254
274
|
}
|
|
255
275
|
/**
|
|
256
276
|
* Get the authenticated customer's enquiry history
|
|
257
277
|
* Only returns enquiries for the logged-in customer
|
|
258
278
|
*/
|
|
259
279
|
async getEnquiries(options) {
|
|
260
|
-
|
|
261
|
-
throw new TiquoAuthError("Not authenticated", "NOT_AUTHENTICATED");
|
|
262
|
-
}
|
|
280
|
+
await this.ensureValidToken();
|
|
263
281
|
this.log("Fetching customer enquiries:", options);
|
|
264
|
-
const url = new URL(`${this.config.apiEndpoint}/api/
|
|
265
|
-
url.searchParams.set("publicKey", this.config.publicKey);
|
|
282
|
+
const url = new URL(`${this.config.apiEndpoint}/api/client/v1/enquiries`);
|
|
266
283
|
if (options?.limit) {
|
|
267
284
|
url.searchParams.set("limit", options.limit.toString());
|
|
268
285
|
}
|
|
@@ -275,7 +292,7 @@ var TiquoAuth = class {
|
|
|
275
292
|
const response = await fetch(url.toString(), {
|
|
276
293
|
method: "GET",
|
|
277
294
|
headers: {
|
|
278
|
-
"Authorization": `Bearer ${this.
|
|
295
|
+
"Authorization": `Bearer ${this.accessToken}`
|
|
279
296
|
},
|
|
280
297
|
credentials: "include"
|
|
281
298
|
});
|
|
@@ -283,21 +300,21 @@ var TiquoAuth = class {
|
|
|
283
300
|
const error = await response.json().catch(() => ({ error: "Failed to get enquiries" }));
|
|
284
301
|
throw new TiquoAuthError(error.error || "Failed to get enquiries", "GET_ENQUIRIES_FAILED", response.status);
|
|
285
302
|
}
|
|
286
|
-
|
|
303
|
+
const result = await response.json();
|
|
304
|
+
return result.data || { enquiries: [], hasMore: false };
|
|
287
305
|
}
|
|
288
306
|
/**
|
|
289
307
|
* Generate a short-lived token for customer flow iframe authentication
|
|
290
308
|
*/
|
|
291
309
|
async getIframeToken(customerFlowId) {
|
|
292
|
-
|
|
293
|
-
throw new TiquoAuthError("Not authenticated", "NOT_AUTHENTICATED");
|
|
294
|
-
}
|
|
310
|
+
await this.ensureValidToken();
|
|
295
311
|
this.log("Generating iframe token");
|
|
296
312
|
const response = await this.request("/api/auth-dom/iframe-token", {
|
|
297
313
|
method: "POST",
|
|
298
314
|
body: JSON.stringify({
|
|
299
315
|
publicKey: this.config.publicKey,
|
|
300
|
-
sessionToken: this.
|
|
316
|
+
sessionToken: this.accessToken,
|
|
317
|
+
// Use access token for backward compatibility
|
|
301
318
|
customerFlowId
|
|
302
319
|
})
|
|
303
320
|
});
|
|
@@ -320,7 +337,7 @@ var TiquoAuth = class {
|
|
|
320
337
|
throw new TiquoAuthError("Container element not found", "CONTAINER_NOT_FOUND");
|
|
321
338
|
}
|
|
322
339
|
let authToken;
|
|
323
|
-
if (this.
|
|
340
|
+
if (this.accessToken) {
|
|
324
341
|
try {
|
|
325
342
|
const { token } = await this.getIframeToken();
|
|
326
343
|
authToken = token;
|
|
@@ -350,22 +367,71 @@ var TiquoAuth = class {
|
|
|
350
367
|
return iframe;
|
|
351
368
|
}
|
|
352
369
|
/**
|
|
353
|
-
* Get the current
|
|
370
|
+
* Get the current access token (for advanced use cases)
|
|
371
|
+
*/
|
|
372
|
+
getAccessToken() {
|
|
373
|
+
return this.accessToken;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Get the current refresh token (for advanced use cases)
|
|
354
377
|
*/
|
|
355
|
-
|
|
356
|
-
return this.
|
|
378
|
+
getRefreshToken() {
|
|
379
|
+
return this.refreshToken;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Initialize with external tokens (for WebView integration)
|
|
383
|
+
*/
|
|
384
|
+
initWithTokens(accessToken, refreshToken) {
|
|
385
|
+
this.log("Initializing with external tokens");
|
|
386
|
+
this.accessToken = accessToken;
|
|
387
|
+
this.refreshToken = refreshToken || null;
|
|
388
|
+
this.saveTokens();
|
|
389
|
+
this.refreshSession();
|
|
390
|
+
this.broadcastTabSync("LOGIN");
|
|
357
391
|
}
|
|
358
392
|
// ============================================
|
|
359
393
|
// PRIVATE METHODS
|
|
360
394
|
// ============================================
|
|
395
|
+
/**
|
|
396
|
+
* Check for tokens injected by native apps (WebView integration)
|
|
397
|
+
*/
|
|
398
|
+
checkForInjectedTokens() {
|
|
399
|
+
if (typeof window === "undefined") return;
|
|
400
|
+
if (this.config.accessToken) {
|
|
401
|
+
this.log("Found token in config");
|
|
402
|
+
this.accessToken = this.config.accessToken;
|
|
403
|
+
this.refreshToken = this.config.refreshToken || null;
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (window.__TIQUO_INIT_TOKEN__?.accessToken) {
|
|
407
|
+
this.log("Found token in window.__TIQUO_INIT_TOKEN__");
|
|
408
|
+
this.accessToken = window.__TIQUO_INIT_TOKEN__.accessToken;
|
|
409
|
+
this.refreshToken = window.__TIQUO_INIT_TOKEN__.refreshToken || null;
|
|
410
|
+
delete window.__TIQUO_INIT_TOKEN__;
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (window.location.hash) {
|
|
414
|
+
const params = new URLSearchParams(window.location.hash.slice(1));
|
|
415
|
+
const accessToken = params.get("access_token");
|
|
416
|
+
if (accessToken) {
|
|
417
|
+
this.log("Found token in URL fragment");
|
|
418
|
+
this.accessToken = accessToken;
|
|
419
|
+
this.refreshToken = params.get("refresh_token");
|
|
420
|
+
if (window.history?.replaceState) {
|
|
421
|
+
window.history.replaceState(null, "", window.location.pathname + window.location.search);
|
|
422
|
+
}
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
361
427
|
async request(path, options) {
|
|
362
428
|
const url = `${this.config.apiEndpoint}${path}`;
|
|
363
429
|
const headers = {
|
|
364
430
|
"Content-Type": "application/json",
|
|
365
431
|
...options.headers
|
|
366
432
|
};
|
|
367
|
-
if (this.
|
|
368
|
-
headers["Authorization"] = `Bearer ${this.
|
|
433
|
+
if (this.accessToken) {
|
|
434
|
+
headers["Authorization"] = `Bearer ${this.accessToken}`;
|
|
369
435
|
}
|
|
370
436
|
return fetch(url, {
|
|
371
437
|
...options,
|
|
@@ -373,73 +439,168 @@ var TiquoAuth = class {
|
|
|
373
439
|
credentials: "include"
|
|
374
440
|
});
|
|
375
441
|
}
|
|
442
|
+
/**
|
|
443
|
+
* Ensure we have a valid access token, refreshing if necessary
|
|
444
|
+
*/
|
|
445
|
+
async ensureValidToken() {
|
|
446
|
+
if (!this.accessToken) {
|
|
447
|
+
throw new TiquoAuthError("Not authenticated", "NOT_AUTHENTICATED");
|
|
448
|
+
}
|
|
449
|
+
await this.refreshTokenIfNeeded();
|
|
450
|
+
if (!this.accessToken) {
|
|
451
|
+
throw new TiquoAuthError("Not authenticated", "NOT_AUTHENTICATED");
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Refresh the access token if it's expired or about to expire
|
|
456
|
+
*/
|
|
457
|
+
async refreshTokenIfNeeded() {
|
|
458
|
+
if (!this.accessToken || !this.refreshToken) {
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
if (!isTokenExpired(this.accessToken, 300)) {
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
return this.performTokenRefresh();
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Perform the actual token refresh
|
|
468
|
+
*/
|
|
469
|
+
async performTokenRefresh() {
|
|
470
|
+
if (this.isRefreshing) {
|
|
471
|
+
return new Promise((resolve) => {
|
|
472
|
+
const checkInterval = setInterval(() => {
|
|
473
|
+
if (!this.isRefreshing) {
|
|
474
|
+
clearInterval(checkInterval);
|
|
475
|
+
resolve(!!this.accessToken);
|
|
476
|
+
}
|
|
477
|
+
}, 100);
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
if (!this.refreshToken) {
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
this.isRefreshing = true;
|
|
484
|
+
this.log("Refreshing access token");
|
|
485
|
+
try {
|
|
486
|
+
const response = await fetch(`${this.config.apiEndpoint}/api/client/v1/refresh`, {
|
|
487
|
+
method: "POST",
|
|
488
|
+
headers: {
|
|
489
|
+
"Content-Type": "application/json"
|
|
490
|
+
},
|
|
491
|
+
body: JSON.stringify({
|
|
492
|
+
refresh_token: this.refreshToken
|
|
493
|
+
}),
|
|
494
|
+
credentials: "include"
|
|
495
|
+
});
|
|
496
|
+
if (!response.ok) {
|
|
497
|
+
this.log("Token refresh failed, clearing session");
|
|
498
|
+
this.clearTokens();
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
const result = await response.json();
|
|
502
|
+
if (result.success && result.data) {
|
|
503
|
+
this.accessToken = result.data.access_token;
|
|
504
|
+
this.refreshToken = result.data.refresh_token;
|
|
505
|
+
this.saveTokens();
|
|
506
|
+
this.scheduleRefresh();
|
|
507
|
+
this.broadcastTabSync("TOKEN_REFRESH");
|
|
508
|
+
this.log("Token refreshed successfully");
|
|
509
|
+
return true;
|
|
510
|
+
}
|
|
511
|
+
return false;
|
|
512
|
+
} catch (error) {
|
|
513
|
+
this.log("Token refresh error:", error);
|
|
514
|
+
return false;
|
|
515
|
+
} finally {
|
|
516
|
+
this.isRefreshing = false;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
376
519
|
async refreshSession() {
|
|
377
|
-
if (!this.
|
|
520
|
+
if (!this.accessToken) {
|
|
378
521
|
return null;
|
|
379
522
|
}
|
|
380
523
|
try {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
const response = await fetch(url.toString(), {
|
|
524
|
+
await this.refreshTokenIfNeeded();
|
|
525
|
+
const response = await fetch(`${this.config.apiEndpoint}/api/client/v1/profile`, {
|
|
384
526
|
method: "GET",
|
|
385
527
|
headers: {
|
|
386
|
-
"Authorization": `Bearer ${this.
|
|
528
|
+
"Authorization": `Bearer ${this.accessToken}`
|
|
387
529
|
},
|
|
388
530
|
credentials: "include"
|
|
389
531
|
});
|
|
390
532
|
if (!response.ok) {
|
|
391
533
|
this.log("Session invalid, clearing");
|
|
392
|
-
this.
|
|
534
|
+
this.clearTokens();
|
|
393
535
|
return null;
|
|
394
536
|
}
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
537
|
+
const result = await response.json();
|
|
538
|
+
if (result.success && result.data) {
|
|
539
|
+
const payload = decodeJWT(this.accessToken);
|
|
540
|
+
const expiresAt = payload?.exp ? payload.exp * 1e3 : Date.now() + 36e5;
|
|
541
|
+
this.session = {
|
|
542
|
+
user: result.data.user,
|
|
543
|
+
customer: result.data.customer,
|
|
544
|
+
expiresAt
|
|
545
|
+
};
|
|
546
|
+
this.notifyListeners();
|
|
547
|
+
this.scheduleRefresh();
|
|
548
|
+
return this.session;
|
|
549
|
+
}
|
|
550
|
+
return null;
|
|
404
551
|
} catch (error) {
|
|
405
552
|
this.log("Session refresh error:", error);
|
|
406
553
|
return null;
|
|
407
554
|
}
|
|
408
555
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
556
|
+
saveTokens() {
|
|
557
|
+
try {
|
|
558
|
+
if (this.accessToken) {
|
|
412
559
|
localStorage.setItem(
|
|
413
|
-
`${this.config.storagePrefix}
|
|
414
|
-
this.
|
|
560
|
+
`${this.config.storagePrefix}access_token`,
|
|
561
|
+
this.accessToken
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
if (this.refreshToken) {
|
|
565
|
+
localStorage.setItem(
|
|
566
|
+
`${this.config.storagePrefix}refresh_token`,
|
|
567
|
+
this.refreshToken
|
|
415
568
|
);
|
|
416
|
-
} catch (error) {
|
|
417
|
-
this.log("Failed to save session to storage:", error);
|
|
418
569
|
}
|
|
570
|
+
} catch (error) {
|
|
571
|
+
this.log("Failed to save tokens to storage:", error);
|
|
419
572
|
}
|
|
420
573
|
}
|
|
421
|
-
|
|
574
|
+
restoreTokens() {
|
|
575
|
+
if (this.accessToken) {
|
|
576
|
+
this.refreshSession();
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
422
579
|
try {
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
|
|
580
|
+
const accessToken = localStorage.getItem(`${this.config.storagePrefix}access_token`);
|
|
581
|
+
const refreshToken = localStorage.getItem(`${this.config.storagePrefix}refresh_token`);
|
|
582
|
+
if (accessToken) {
|
|
583
|
+
this.accessToken = accessToken;
|
|
584
|
+
this.refreshToken = refreshToken;
|
|
426
585
|
this.refreshSession();
|
|
427
586
|
}
|
|
428
587
|
} catch (error) {
|
|
429
|
-
this.log("Failed to restore
|
|
588
|
+
this.log("Failed to restore tokens from storage:", error);
|
|
430
589
|
}
|
|
431
590
|
}
|
|
432
|
-
|
|
433
|
-
this.
|
|
591
|
+
clearTokens() {
|
|
592
|
+
this.accessToken = null;
|
|
593
|
+
this.refreshToken = null;
|
|
434
594
|
this.session = null;
|
|
435
595
|
if (this.refreshTimer) {
|
|
436
596
|
clearTimeout(this.refreshTimer);
|
|
437
597
|
this.refreshTimer = null;
|
|
438
598
|
}
|
|
439
599
|
try {
|
|
440
|
-
localStorage.removeItem(`${this.config.storagePrefix}
|
|
600
|
+
localStorage.removeItem(`${this.config.storagePrefix}access_token`);
|
|
601
|
+
localStorage.removeItem(`${this.config.storagePrefix}refresh_token`);
|
|
441
602
|
} catch (error) {
|
|
442
|
-
this.log("Failed to clear
|
|
603
|
+
this.log("Failed to clear tokens from storage:", error);
|
|
443
604
|
}
|
|
444
605
|
this.notifyListeners();
|
|
445
606
|
}
|
|
@@ -456,11 +617,14 @@ var TiquoAuth = class {
|
|
|
456
617
|
if (this.refreshTimer) {
|
|
457
618
|
clearTimeout(this.refreshTimer);
|
|
458
619
|
}
|
|
459
|
-
if (!this.
|
|
460
|
-
const
|
|
620
|
+
if (!this.accessToken) return;
|
|
621
|
+
const payload = decodeJWT(this.accessToken);
|
|
622
|
+
if (!payload) return;
|
|
623
|
+
const expiresAt = payload.exp * 1e3;
|
|
624
|
+
const refreshIn = expiresAt - Date.now() - 5 * 60 * 1e3;
|
|
461
625
|
if (refreshIn > 0) {
|
|
462
626
|
this.refreshTimer = setTimeout(() => {
|
|
463
|
-
this.
|
|
627
|
+
this.performTokenRefresh();
|
|
464
628
|
}, refreshIn);
|
|
465
629
|
}
|
|
466
630
|
}
|
|
@@ -497,7 +661,8 @@ var TiquoAuth = class {
|
|
|
497
661
|
type,
|
|
498
662
|
tabId: this.tabId,
|
|
499
663
|
timestamp: Date.now(),
|
|
500
|
-
|
|
664
|
+
accessToken: type === "LOGOUT" ? null : this.accessToken,
|
|
665
|
+
refreshToken: type === "LOGOUT" ? null : this.refreshToken,
|
|
501
666
|
session: type === "LOGOUT" ? null : this.session
|
|
502
667
|
};
|
|
503
668
|
try {
|
|
@@ -515,24 +680,30 @@ var TiquoAuth = class {
|
|
|
515
680
|
try {
|
|
516
681
|
switch (message.type) {
|
|
517
682
|
case "LOGIN":
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
this.
|
|
521
|
-
this.
|
|
683
|
+
case "TOKEN_REFRESH":
|
|
684
|
+
if (message.accessToken) {
|
|
685
|
+
this.accessToken = message.accessToken;
|
|
686
|
+
this.refreshToken = message.refreshToken || null;
|
|
687
|
+
if (message.session) {
|
|
688
|
+
this.session = message.session;
|
|
689
|
+
}
|
|
690
|
+
this.saveTokens();
|
|
522
691
|
this.scheduleRefresh();
|
|
523
692
|
this.notifyListeners();
|
|
524
|
-
this.log("
|
|
693
|
+
this.log("Tokens synced from another tab");
|
|
525
694
|
}
|
|
526
695
|
break;
|
|
527
696
|
case "LOGOUT":
|
|
528
|
-
this.
|
|
697
|
+
this.accessToken = null;
|
|
698
|
+
this.refreshToken = null;
|
|
529
699
|
this.session = null;
|
|
530
700
|
if (this.refreshTimer) {
|
|
531
701
|
clearTimeout(this.refreshTimer);
|
|
532
702
|
this.refreshTimer = null;
|
|
533
703
|
}
|
|
534
704
|
try {
|
|
535
|
-
localStorage.removeItem(`${this.config.storagePrefix}
|
|
705
|
+
localStorage.removeItem(`${this.config.storagePrefix}access_token`);
|
|
706
|
+
localStorage.removeItem(`${this.config.storagePrefix}refresh_token`);
|
|
536
707
|
} catch {
|
|
537
708
|
}
|
|
538
709
|
this.notifyListeners();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiquo/dom-package",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Tiquo SDK for third-party websites - authentication, customer profiles, orders, bookings, and enquiries",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "restricted"
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"type": "git",
|
|
46
46
|
"url": "https://github.com/tiquo/dom-package.git"
|
|
47
47
|
},
|
|
48
|
-
"homepage": "https://docs.tiquo.
|
|
48
|
+
"homepage": "https://docs.tiquo.co/dom-package",
|
|
49
49
|
"bugs": {
|
|
50
50
|
"url": "https://github.com/tiquo/dom-package/issues"
|
|
51
51
|
},
|