@tiquo/dom-package 1.0.0
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 +498 -0
- package/dist/index.d.mts +323 -0
- package/dist/index.d.ts +323 -0
- package/dist/index.js +637 -0
- package/dist/index.mjs +610 -0
- package/package.json +60 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
TiquoAuth: () => TiquoAuth,
|
|
24
|
+
TiquoAuthError: () => TiquoAuthError,
|
|
25
|
+
default: () => index_default,
|
|
26
|
+
useTiquoAuth: () => useTiquoAuth
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
var TiquoAuthError = class extends Error {
|
|
30
|
+
constructor(message, code, statusCode) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.code = code;
|
|
33
|
+
this.statusCode = statusCode;
|
|
34
|
+
this.name = "TiquoAuthError";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var TiquoAuth = class {
|
|
38
|
+
constructor(config) {
|
|
39
|
+
this.sessionToken = null;
|
|
40
|
+
this.session = null;
|
|
41
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
42
|
+
this.refreshTimer = null;
|
|
43
|
+
// Multi-tab sync
|
|
44
|
+
this.broadcastChannel = null;
|
|
45
|
+
this.isProcessingTabSync = false;
|
|
46
|
+
if (!config.publicKey) {
|
|
47
|
+
throw new TiquoAuthError("publicKey is required", "MISSING_PUBLIC_KEY");
|
|
48
|
+
}
|
|
49
|
+
if (!config.publicKey.startsWith("pk_dom_")) {
|
|
50
|
+
throw new TiquoAuthError(
|
|
51
|
+
"Invalid public key format. Expected pk_dom_xxx",
|
|
52
|
+
"INVALID_PUBLIC_KEY"
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
this.config = {
|
|
56
|
+
publicKey: config.publicKey,
|
|
57
|
+
apiEndpoint: config.apiEndpoint || "https://edge.tiquo.app",
|
|
58
|
+
storagePrefix: config.storagePrefix || "tiquo_auth_",
|
|
59
|
+
debug: config.debug || false,
|
|
60
|
+
enableTabSync: config.enableTabSync !== false
|
|
61
|
+
// Default true
|
|
62
|
+
};
|
|
63
|
+
this.tabId = this.generateTabId();
|
|
64
|
+
if (this.config.enableTabSync) {
|
|
65
|
+
this.initTabSync();
|
|
66
|
+
}
|
|
67
|
+
this.restoreSession();
|
|
68
|
+
}
|
|
69
|
+
// ============================================
|
|
70
|
+
// PUBLIC METHODS
|
|
71
|
+
// ============================================
|
|
72
|
+
/**
|
|
73
|
+
* Send an OTP verification code to the user's email
|
|
74
|
+
*/
|
|
75
|
+
async sendOTP(email) {
|
|
76
|
+
this.log("Sending OTP to:", email);
|
|
77
|
+
const response = await this.request("/api/auth-dom/otp/send", {
|
|
78
|
+
method: "POST",
|
|
79
|
+
body: JSON.stringify({
|
|
80
|
+
publicKey: this.config.publicKey,
|
|
81
|
+
email
|
|
82
|
+
})
|
|
83
|
+
});
|
|
84
|
+
if (!response.ok) {
|
|
85
|
+
const error = await response.json().catch(() => ({ error: "Failed to send OTP" }));
|
|
86
|
+
throw new TiquoAuthError(error.error || "Failed to send OTP", "OTP_SEND_FAILED", response.status);
|
|
87
|
+
}
|
|
88
|
+
const result = await response.json();
|
|
89
|
+
return {
|
|
90
|
+
success: true,
|
|
91
|
+
message: result.message || "OTP sent"
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Verify an OTP code and authenticate the user
|
|
96
|
+
*/
|
|
97
|
+
async verifyOTP(email, otp) {
|
|
98
|
+
this.log("Verifying OTP for:", email);
|
|
99
|
+
const response = await this.request("/api/auth-dom/otp/verify", {
|
|
100
|
+
method: "POST",
|
|
101
|
+
body: JSON.stringify({
|
|
102
|
+
publicKey: this.config.publicKey,
|
|
103
|
+
email,
|
|
104
|
+
otp
|
|
105
|
+
})
|
|
106
|
+
});
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
const error = await response.json().catch(() => ({ error: "Invalid OTP" }));
|
|
109
|
+
throw new TiquoAuthError(error.error || "Invalid OTP", "OTP_VERIFY_FAILED", response.status);
|
|
110
|
+
}
|
|
111
|
+
const result = await response.json();
|
|
112
|
+
this.sessionToken = result.sessionToken;
|
|
113
|
+
this.saveSession();
|
|
114
|
+
await this.refreshSession();
|
|
115
|
+
this.broadcastTabSync("LOGIN");
|
|
116
|
+
return {
|
|
117
|
+
success: true,
|
|
118
|
+
sessionToken: result.sessionToken,
|
|
119
|
+
expiresAt: result.expiresAt,
|
|
120
|
+
isNewUser: result.isNewUser,
|
|
121
|
+
hasCustomer: result.hasCustomer
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Get the current authenticated user and customer data
|
|
126
|
+
*/
|
|
127
|
+
async getUser() {
|
|
128
|
+
if (!this.sessionToken) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
if (this.session && this.session.expiresAt > Date.now()) {
|
|
132
|
+
return this.session;
|
|
133
|
+
}
|
|
134
|
+
return this.refreshSession();
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Check if user is currently authenticated
|
|
138
|
+
*/
|
|
139
|
+
isAuthenticated() {
|
|
140
|
+
return !!this.sessionToken && (!this.session || this.session.expiresAt > Date.now());
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Update the authenticated customer's profile
|
|
144
|
+
* Only allows updating the logged-in customer's own data
|
|
145
|
+
*/
|
|
146
|
+
async updateProfile(updates) {
|
|
147
|
+
if (!this.sessionToken) {
|
|
148
|
+
throw new TiquoAuthError("Not authenticated", "NOT_AUTHENTICATED");
|
|
149
|
+
}
|
|
150
|
+
this.log("Updating customer profile:", updates);
|
|
151
|
+
const response = await this.request("/api/auth-dom/profile", {
|
|
152
|
+
method: "PATCH",
|
|
153
|
+
body: JSON.stringify({
|
|
154
|
+
publicKey: this.config.publicKey,
|
|
155
|
+
sessionToken: this.sessionToken,
|
|
156
|
+
updates
|
|
157
|
+
})
|
|
158
|
+
});
|
|
159
|
+
if (!response.ok) {
|
|
160
|
+
const error = await response.json().catch(() => ({ error: "Failed to update profile" }));
|
|
161
|
+
throw new TiquoAuthError(error.error || "Failed to update profile", "PROFILE_UPDATE_FAILED", response.status);
|
|
162
|
+
}
|
|
163
|
+
const result = await response.json();
|
|
164
|
+
if (this.session && result.customer) {
|
|
165
|
+
this.session = {
|
|
166
|
+
...this.session,
|
|
167
|
+
customer: {
|
|
168
|
+
id: result.customer.id,
|
|
169
|
+
firstName: result.customer.firstName,
|
|
170
|
+
lastName: result.customer.lastName,
|
|
171
|
+
displayName: result.customer.displayName,
|
|
172
|
+
customerNumber: result.customer.customerNumber,
|
|
173
|
+
email: result.customer.email,
|
|
174
|
+
phone: result.customer.phone
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
this.notifyListeners();
|
|
178
|
+
this.broadcastTabSync("SESSION_UPDATE");
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Log out the current user
|
|
184
|
+
*/
|
|
185
|
+
async logout() {
|
|
186
|
+
this.log("Logging out");
|
|
187
|
+
if (this.sessionToken) {
|
|
188
|
+
try {
|
|
189
|
+
await this.request("/api/auth-dom/logout", {
|
|
190
|
+
method: "POST",
|
|
191
|
+
body: JSON.stringify({
|
|
192
|
+
publicKey: this.config.publicKey,
|
|
193
|
+
sessionToken: this.sessionToken
|
|
194
|
+
})
|
|
195
|
+
});
|
|
196
|
+
} catch (error) {
|
|
197
|
+
this.log("Logout API error:", error);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
this.broadcastTabSync("LOGOUT");
|
|
201
|
+
this.clearSession();
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Subscribe to authentication state changes
|
|
205
|
+
*/
|
|
206
|
+
onAuthStateChange(callback) {
|
|
207
|
+
this.listeners.add(callback);
|
|
208
|
+
callback(this.session);
|
|
209
|
+
return () => {
|
|
210
|
+
this.listeners.delete(callback);
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Get the authenticated customer's order history
|
|
215
|
+
* Only returns orders for the logged-in customer
|
|
216
|
+
*/
|
|
217
|
+
async getOrders(options) {
|
|
218
|
+
if (!this.sessionToken) {
|
|
219
|
+
throw new TiquoAuthError("Not authenticated", "NOT_AUTHENTICATED");
|
|
220
|
+
}
|
|
221
|
+
this.log("Fetching customer orders:", options);
|
|
222
|
+
const url = new URL(`${this.config.apiEndpoint}/api/auth-dom/orders`);
|
|
223
|
+
url.searchParams.set("publicKey", this.config.publicKey);
|
|
224
|
+
if (options?.limit) {
|
|
225
|
+
url.searchParams.set("limit", options.limit.toString());
|
|
226
|
+
}
|
|
227
|
+
if (options?.cursor) {
|
|
228
|
+
url.searchParams.set("cursor", options.cursor);
|
|
229
|
+
}
|
|
230
|
+
if (options?.status) {
|
|
231
|
+
url.searchParams.set("status", options.status);
|
|
232
|
+
}
|
|
233
|
+
const response = await fetch(url.toString(), {
|
|
234
|
+
method: "GET",
|
|
235
|
+
headers: {
|
|
236
|
+
"Authorization": `Bearer ${this.sessionToken}`
|
|
237
|
+
},
|
|
238
|
+
credentials: "include"
|
|
239
|
+
});
|
|
240
|
+
if (!response.ok) {
|
|
241
|
+
const error = await response.json().catch(() => ({ error: "Failed to get orders" }));
|
|
242
|
+
throw new TiquoAuthError(error.error || "Failed to get orders", "GET_ORDERS_FAILED", response.status);
|
|
243
|
+
}
|
|
244
|
+
return response.json();
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Get the authenticated customer's booking history
|
|
248
|
+
* Only returns bookings for the logged-in customer
|
|
249
|
+
*/
|
|
250
|
+
async getBookings(options) {
|
|
251
|
+
if (!this.sessionToken) {
|
|
252
|
+
throw new TiquoAuthError("Not authenticated", "NOT_AUTHENTICATED");
|
|
253
|
+
}
|
|
254
|
+
this.log("Fetching customer bookings:", options);
|
|
255
|
+
const url = new URL(`${this.config.apiEndpoint}/api/auth-dom/bookings`);
|
|
256
|
+
url.searchParams.set("publicKey", this.config.publicKey);
|
|
257
|
+
if (options?.limit) {
|
|
258
|
+
url.searchParams.set("limit", options.limit.toString());
|
|
259
|
+
}
|
|
260
|
+
if (options?.cursor) {
|
|
261
|
+
url.searchParams.set("cursor", options.cursor);
|
|
262
|
+
}
|
|
263
|
+
if (options?.status) {
|
|
264
|
+
url.searchParams.set("status", options.status);
|
|
265
|
+
}
|
|
266
|
+
if (options?.upcoming) {
|
|
267
|
+
url.searchParams.set("upcoming", "true");
|
|
268
|
+
}
|
|
269
|
+
const response = await fetch(url.toString(), {
|
|
270
|
+
method: "GET",
|
|
271
|
+
headers: {
|
|
272
|
+
"Authorization": `Bearer ${this.sessionToken}`
|
|
273
|
+
},
|
|
274
|
+
credentials: "include"
|
|
275
|
+
});
|
|
276
|
+
if (!response.ok) {
|
|
277
|
+
const error = await response.json().catch(() => ({ error: "Failed to get bookings" }));
|
|
278
|
+
throw new TiquoAuthError(error.error || "Failed to get bookings", "GET_BOOKINGS_FAILED", response.status);
|
|
279
|
+
}
|
|
280
|
+
return response.json();
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Get the authenticated customer's enquiry history
|
|
284
|
+
* Only returns enquiries for the logged-in customer
|
|
285
|
+
*/
|
|
286
|
+
async getEnquiries(options) {
|
|
287
|
+
if (!this.sessionToken) {
|
|
288
|
+
throw new TiquoAuthError("Not authenticated", "NOT_AUTHENTICATED");
|
|
289
|
+
}
|
|
290
|
+
this.log("Fetching customer enquiries:", options);
|
|
291
|
+
const url = new URL(`${this.config.apiEndpoint}/api/auth-dom/enquiries`);
|
|
292
|
+
url.searchParams.set("publicKey", this.config.publicKey);
|
|
293
|
+
if (options?.limit) {
|
|
294
|
+
url.searchParams.set("limit", options.limit.toString());
|
|
295
|
+
}
|
|
296
|
+
if (options?.cursor) {
|
|
297
|
+
url.searchParams.set("cursor", options.cursor);
|
|
298
|
+
}
|
|
299
|
+
if (options?.status) {
|
|
300
|
+
url.searchParams.set("status", options.status);
|
|
301
|
+
}
|
|
302
|
+
const response = await fetch(url.toString(), {
|
|
303
|
+
method: "GET",
|
|
304
|
+
headers: {
|
|
305
|
+
"Authorization": `Bearer ${this.sessionToken}`
|
|
306
|
+
},
|
|
307
|
+
credentials: "include"
|
|
308
|
+
});
|
|
309
|
+
if (!response.ok) {
|
|
310
|
+
const error = await response.json().catch(() => ({ error: "Failed to get enquiries" }));
|
|
311
|
+
throw new TiquoAuthError(error.error || "Failed to get enquiries", "GET_ENQUIRIES_FAILED", response.status);
|
|
312
|
+
}
|
|
313
|
+
return response.json();
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Generate a short-lived token for customer flow iframe authentication
|
|
317
|
+
*/
|
|
318
|
+
async getIframeToken(customerFlowId) {
|
|
319
|
+
if (!this.sessionToken) {
|
|
320
|
+
throw new TiquoAuthError("Not authenticated", "NOT_AUTHENTICATED");
|
|
321
|
+
}
|
|
322
|
+
this.log("Generating iframe token");
|
|
323
|
+
const response = await this.request("/api/auth-dom/iframe-token", {
|
|
324
|
+
method: "POST",
|
|
325
|
+
body: JSON.stringify({
|
|
326
|
+
publicKey: this.config.publicKey,
|
|
327
|
+
sessionToken: this.sessionToken,
|
|
328
|
+
customerFlowId
|
|
329
|
+
})
|
|
330
|
+
});
|
|
331
|
+
if (!response.ok) {
|
|
332
|
+
const error = await response.json().catch(() => ({ error: "Failed to generate token" }));
|
|
333
|
+
throw new TiquoAuthError(error.error || "Failed to generate token", "IFRAME_TOKEN_FAILED", response.status);
|
|
334
|
+
}
|
|
335
|
+
return response.json();
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Embed a customer flow with automatic authentication
|
|
339
|
+
*
|
|
340
|
+
* @param flowUrl - The URL of the customer flow (book.tiquo.app/...)
|
|
341
|
+
* @param container - Container element or selector
|
|
342
|
+
* @param options - Optional iframe configuration
|
|
343
|
+
*/
|
|
344
|
+
async embedCustomerFlow(flowUrl, container, options) {
|
|
345
|
+
const containerEl = typeof container === "string" ? document.querySelector(container) : container;
|
|
346
|
+
if (!containerEl) {
|
|
347
|
+
throw new TiquoAuthError("Container element not found", "CONTAINER_NOT_FOUND");
|
|
348
|
+
}
|
|
349
|
+
let authToken;
|
|
350
|
+
if (this.sessionToken) {
|
|
351
|
+
try {
|
|
352
|
+
const { token } = await this.getIframeToken();
|
|
353
|
+
authToken = token;
|
|
354
|
+
} catch (error) {
|
|
355
|
+
this.log("Failed to get iframe token:", error);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const url = new URL(flowUrl);
|
|
359
|
+
if (authToken) {
|
|
360
|
+
url.searchParams.set("_auth_token", authToken);
|
|
361
|
+
}
|
|
362
|
+
const iframe = document.createElement("iframe");
|
|
363
|
+
iframe.src = url.toString();
|
|
364
|
+
iframe.style.width = options?.width || "100%";
|
|
365
|
+
iframe.style.height = options?.height || "600px";
|
|
366
|
+
iframe.style.border = "none";
|
|
367
|
+
iframe.setAttribute("allow", "payment");
|
|
368
|
+
iframe.setAttribute("loading", "lazy");
|
|
369
|
+
if (options?.onLoad) {
|
|
370
|
+
iframe.addEventListener("load", options.onLoad);
|
|
371
|
+
}
|
|
372
|
+
if (options?.onError) {
|
|
373
|
+
iframe.addEventListener("error", () => options.onError(new Error("Failed to load iframe")));
|
|
374
|
+
}
|
|
375
|
+
containerEl.innerHTML = "";
|
|
376
|
+
containerEl.appendChild(iframe);
|
|
377
|
+
return iframe;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Get the current session token (for advanced use cases)
|
|
381
|
+
*/
|
|
382
|
+
getSessionToken() {
|
|
383
|
+
return this.sessionToken;
|
|
384
|
+
}
|
|
385
|
+
// ============================================
|
|
386
|
+
// PRIVATE METHODS
|
|
387
|
+
// ============================================
|
|
388
|
+
async request(path, options) {
|
|
389
|
+
const url = `${this.config.apiEndpoint}${path}`;
|
|
390
|
+
const headers = {
|
|
391
|
+
"Content-Type": "application/json",
|
|
392
|
+
...options.headers
|
|
393
|
+
};
|
|
394
|
+
if (this.sessionToken && !(typeof options.body === "string" && options.body.includes("sessionToken"))) {
|
|
395
|
+
headers["Authorization"] = `Bearer ${this.sessionToken}`;
|
|
396
|
+
}
|
|
397
|
+
return fetch(url, {
|
|
398
|
+
...options,
|
|
399
|
+
headers,
|
|
400
|
+
credentials: "include"
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
async refreshSession() {
|
|
404
|
+
if (!this.sessionToken) {
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
try {
|
|
408
|
+
const url = new URL(`${this.config.apiEndpoint}/api/auth-dom/session`);
|
|
409
|
+
url.searchParams.set("publicKey", this.config.publicKey);
|
|
410
|
+
const response = await fetch(url.toString(), {
|
|
411
|
+
method: "GET",
|
|
412
|
+
headers: {
|
|
413
|
+
"Authorization": `Bearer ${this.sessionToken}`
|
|
414
|
+
},
|
|
415
|
+
credentials: "include"
|
|
416
|
+
});
|
|
417
|
+
if (!response.ok) {
|
|
418
|
+
this.log("Session invalid, clearing");
|
|
419
|
+
this.clearSession();
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
const data = await response.json();
|
|
423
|
+
this.session = {
|
|
424
|
+
user: data.user,
|
|
425
|
+
customer: data.customer,
|
|
426
|
+
expiresAt: data.session.expiresAt
|
|
427
|
+
};
|
|
428
|
+
this.notifyListeners();
|
|
429
|
+
this.scheduleRefresh();
|
|
430
|
+
return this.session;
|
|
431
|
+
} catch (error) {
|
|
432
|
+
this.log("Session refresh error:", error);
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
saveSession() {
|
|
437
|
+
if (this.sessionToken) {
|
|
438
|
+
try {
|
|
439
|
+
localStorage.setItem(
|
|
440
|
+
`${this.config.storagePrefix}session`,
|
|
441
|
+
this.sessionToken
|
|
442
|
+
);
|
|
443
|
+
} catch (error) {
|
|
444
|
+
this.log("Failed to save session to storage:", error);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
restoreSession() {
|
|
449
|
+
try {
|
|
450
|
+
const token = localStorage.getItem(`${this.config.storagePrefix}session`);
|
|
451
|
+
if (token) {
|
|
452
|
+
this.sessionToken = token;
|
|
453
|
+
this.refreshSession();
|
|
454
|
+
}
|
|
455
|
+
} catch (error) {
|
|
456
|
+
this.log("Failed to restore session from storage:", error);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
clearSession() {
|
|
460
|
+
this.sessionToken = null;
|
|
461
|
+
this.session = null;
|
|
462
|
+
if (this.refreshTimer) {
|
|
463
|
+
clearTimeout(this.refreshTimer);
|
|
464
|
+
this.refreshTimer = null;
|
|
465
|
+
}
|
|
466
|
+
try {
|
|
467
|
+
localStorage.removeItem(`${this.config.storagePrefix}session`);
|
|
468
|
+
} catch (error) {
|
|
469
|
+
this.log("Failed to clear session from storage:", error);
|
|
470
|
+
}
|
|
471
|
+
this.notifyListeners();
|
|
472
|
+
}
|
|
473
|
+
notifyListeners() {
|
|
474
|
+
for (const listener of this.listeners) {
|
|
475
|
+
try {
|
|
476
|
+
listener(this.session);
|
|
477
|
+
} catch (error) {
|
|
478
|
+
this.log("Listener error:", error);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
scheduleRefresh() {
|
|
483
|
+
if (this.refreshTimer) {
|
|
484
|
+
clearTimeout(this.refreshTimer);
|
|
485
|
+
}
|
|
486
|
+
if (!this.session) return;
|
|
487
|
+
const refreshIn = this.session.expiresAt - Date.now() - 5 * 60 * 1e3;
|
|
488
|
+
if (refreshIn > 0) {
|
|
489
|
+
this.refreshTimer = setTimeout(() => {
|
|
490
|
+
this.refreshSession();
|
|
491
|
+
}, refreshIn);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
log(...args) {
|
|
495
|
+
if (this.config.debug) {
|
|
496
|
+
console.log("[TiquoAuth]", ...args);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
// ============================================
|
|
500
|
+
// MULTI-TAB SYNC METHODS
|
|
501
|
+
// ============================================
|
|
502
|
+
generateTabId() {
|
|
503
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
504
|
+
}
|
|
505
|
+
initTabSync() {
|
|
506
|
+
if (typeof BroadcastChannel === "undefined") {
|
|
507
|
+
this.log("BroadcastChannel not supported, tab sync disabled");
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
try {
|
|
511
|
+
const channelName = `tiquo_auth_${this.config.publicKey}`;
|
|
512
|
+
this.broadcastChannel = new BroadcastChannel(channelName);
|
|
513
|
+
this.broadcastChannel.onmessage = (event) => {
|
|
514
|
+
this.handleTabSyncMessage(event.data);
|
|
515
|
+
};
|
|
516
|
+
this.log("Tab sync initialized, channel:", channelName);
|
|
517
|
+
} catch (error) {
|
|
518
|
+
this.log("Failed to initialize tab sync:", error);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
broadcastTabSync(type) {
|
|
522
|
+
if (!this.broadcastChannel) return;
|
|
523
|
+
const message = {
|
|
524
|
+
type,
|
|
525
|
+
tabId: this.tabId,
|
|
526
|
+
timestamp: Date.now(),
|
|
527
|
+
sessionToken: type === "LOGOUT" ? null : this.sessionToken,
|
|
528
|
+
session: type === "LOGOUT" ? null : this.session
|
|
529
|
+
};
|
|
530
|
+
try {
|
|
531
|
+
this.broadcastChannel.postMessage(message);
|
|
532
|
+
this.log("Broadcasted tab sync message:", type);
|
|
533
|
+
} catch (error) {
|
|
534
|
+
this.log("Failed to broadcast tab sync:", error);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
handleTabSyncMessage(message) {
|
|
538
|
+
if (message.tabId === this.tabId) return;
|
|
539
|
+
if (this.isProcessingTabSync) return;
|
|
540
|
+
this.isProcessingTabSync = true;
|
|
541
|
+
this.log("Received tab sync message:", message.type, "from tab:", message.tabId);
|
|
542
|
+
try {
|
|
543
|
+
switch (message.type) {
|
|
544
|
+
case "LOGIN":
|
|
545
|
+
if (message.sessionToken && message.session) {
|
|
546
|
+
this.sessionToken = message.sessionToken;
|
|
547
|
+
this.session = message.session;
|
|
548
|
+
this.saveSession();
|
|
549
|
+
this.scheduleRefresh();
|
|
550
|
+
this.notifyListeners();
|
|
551
|
+
this.log("Session synced from another tab (login)");
|
|
552
|
+
}
|
|
553
|
+
break;
|
|
554
|
+
case "LOGOUT":
|
|
555
|
+
this.sessionToken = null;
|
|
556
|
+
this.session = null;
|
|
557
|
+
if (this.refreshTimer) {
|
|
558
|
+
clearTimeout(this.refreshTimer);
|
|
559
|
+
this.refreshTimer = null;
|
|
560
|
+
}
|
|
561
|
+
try {
|
|
562
|
+
localStorage.removeItem(`${this.config.storagePrefix}session`);
|
|
563
|
+
} catch {
|
|
564
|
+
}
|
|
565
|
+
this.notifyListeners();
|
|
566
|
+
this.log("Session cleared from another tab (logout)");
|
|
567
|
+
break;
|
|
568
|
+
case "SESSION_UPDATE":
|
|
569
|
+
if (message.session) {
|
|
570
|
+
this.session = message.session;
|
|
571
|
+
this.notifyListeners();
|
|
572
|
+
this.log("Session synced from another tab (update)");
|
|
573
|
+
}
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
} finally {
|
|
577
|
+
this.isProcessingTabSync = false;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Clean up resources (call when destroying the auth instance)
|
|
582
|
+
*/
|
|
583
|
+
destroy() {
|
|
584
|
+
this.log("Destroying TiquoAuth instance");
|
|
585
|
+
if (this.refreshTimer) {
|
|
586
|
+
clearTimeout(this.refreshTimer);
|
|
587
|
+
this.refreshTimer = null;
|
|
588
|
+
}
|
|
589
|
+
if (this.broadcastChannel) {
|
|
590
|
+
this.broadcastChannel.close();
|
|
591
|
+
this.broadcastChannel = null;
|
|
592
|
+
}
|
|
593
|
+
this.listeners.clear();
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
function useTiquoAuth(auth) {
|
|
597
|
+
let session = null;
|
|
598
|
+
let isLoading = true;
|
|
599
|
+
auth.getUser().then((s) => {
|
|
600
|
+
session = s;
|
|
601
|
+
isLoading = false;
|
|
602
|
+
});
|
|
603
|
+
return {
|
|
604
|
+
get user() {
|
|
605
|
+
return session?.user || null;
|
|
606
|
+
},
|
|
607
|
+
get customer() {
|
|
608
|
+
return session?.customer || null;
|
|
609
|
+
},
|
|
610
|
+
get session() {
|
|
611
|
+
return session;
|
|
612
|
+
},
|
|
613
|
+
get isLoading() {
|
|
614
|
+
return isLoading;
|
|
615
|
+
},
|
|
616
|
+
get isAuthenticated() {
|
|
617
|
+
return auth.isAuthenticated();
|
|
618
|
+
},
|
|
619
|
+
sendOTP: (email) => auth.sendOTP(email),
|
|
620
|
+
verifyOTP: (email, otp) => auth.verifyOTP(email, otp),
|
|
621
|
+
logout: () => auth.logout(),
|
|
622
|
+
updateProfile: (updates) => auth.updateProfile(updates),
|
|
623
|
+
getOrders: (options) => auth.getOrders(options),
|
|
624
|
+
getBookings: (options) => auth.getBookings(options),
|
|
625
|
+
getEnquiries: (options) => auth.getEnquiries(options),
|
|
626
|
+
getIframeToken: (flowId) => auth.getIframeToken(flowId),
|
|
627
|
+
embedCustomerFlow: auth.embedCustomerFlow.bind(auth),
|
|
628
|
+
onAuthStateChange: (cb) => auth.onAuthStateChange(cb)
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
var index_default = TiquoAuth;
|
|
632
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
633
|
+
0 && (module.exports = {
|
|
634
|
+
TiquoAuth,
|
|
635
|
+
TiquoAuthError,
|
|
636
|
+
useTiquoAuth
|
|
637
|
+
});
|