@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 ADDED
@@ -0,0 +1,498 @@
1
+ # @tiquo/dom-package
2
+
3
+ Tiquo SDK for third-party websites. Integrate authentication, customer profiles, orders, bookings, and enquiries into your website with a simple JavaScript API.
4
+
5
+ ## Features
6
+
7
+ - **Email OTP Authentication** - Secure passwordless login using email verification codes
8
+ - **Session Management** - Automatic session persistence and refresh
9
+ - **Multi-Tab Sync** - Auth state automatically syncs across all browser tabs
10
+ - **Customer Flow Integration** - Embed authenticated customer flows with one line of code
11
+ - **Framework Agnostic** - Works with React, Vue, Svelte, or vanilla JavaScript
12
+ - **TypeScript Support** - Full type definitions included
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @tiquo/dom-package
18
+ # or
19
+ yarn add @tiquo/dom-package
20
+ # or
21
+ pnpm add @tiquo/dom-package
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ### 1. Get Your Public Key
27
+
28
+ Go to your Tiquo dashboard → Settings → Auth DOM to get your public key.
29
+
30
+ ### 2. Initialize the SDK
31
+
32
+ ```typescript
33
+ import { TiquoAuth } from '@tiquo/dom-package';
34
+
35
+ const auth = new TiquoAuth({
36
+ publicKey: 'pk_dom_your_public_key'
37
+ });
38
+ ```
39
+
40
+ ### 3. Authenticate Users
41
+
42
+ ```typescript
43
+ // Step 1: Send OTP to user's email
44
+ await auth.sendOTP('user@example.com');
45
+
46
+ // Step 2: Verify the OTP code
47
+ const result = await auth.verifyOTP('user@example.com', '123456');
48
+
49
+ // Step 3: Get user data
50
+ const session = await auth.getUser();
51
+ console.log(session?.user.email); // user@example.com
52
+ console.log(session?.customer?.firstName); // John
53
+ ```
54
+
55
+ ## API Reference
56
+
57
+ ### Constructor
58
+
59
+ ```typescript
60
+ const auth = new TiquoAuth({
61
+ publicKey: string; // Required: Your public key from Tiquo dashboard
62
+ apiEndpoint?: string; // Optional: API endpoint (defaults to production)
63
+ storagePrefix?: string; // Optional: localStorage key prefix
64
+ debug?: boolean; // Optional: Enable debug logging
65
+ enableTabSync?: boolean; // Optional: Multi-tab session sync (default: true)
66
+ });
67
+ ```
68
+
69
+ ### Methods
70
+
71
+ #### `sendOTP(email: string): Promise<SendOTPResult>`
72
+
73
+ Send a verification code to the user's email.
74
+
75
+ ```typescript
76
+ const result = await auth.sendOTP('user@example.com');
77
+ // { success: true, message: 'OTP sent' }
78
+ ```
79
+
80
+ #### `verifyOTP(email: string, otp: string): Promise<VerifyOTPResult>`
81
+
82
+ Verify the OTP code and authenticate the user.
83
+
84
+ ```typescript
85
+ const result = await auth.verifyOTP('user@example.com', '123456');
86
+ // {
87
+ // success: true,
88
+ // sessionToken: 'xxx',
89
+ // expiresAt: 1234567890,
90
+ // isNewUser: false,
91
+ // hasCustomer: true
92
+ // }
93
+ ```
94
+
95
+ #### `getUser(): Promise<TiquoSession | null>`
96
+
97
+ Get the current authenticated user and customer data.
98
+
99
+ ```typescript
100
+ const session = await auth.getUser();
101
+ if (session) {
102
+ console.log(session.user.id);
103
+ console.log(session.user.email);
104
+ console.log(session.customer?.firstName);
105
+ console.log(session.customer?.customerNumber);
106
+ }
107
+ ```
108
+
109
+ #### `isAuthenticated(): boolean`
110
+
111
+ Check if the user is currently authenticated.
112
+
113
+ ```typescript
114
+ if (auth.isAuthenticated()) {
115
+ // User is logged in
116
+ }
117
+ ```
118
+
119
+ #### `updateProfile(updates): Promise<ProfileUpdateResult>`
120
+
121
+ Update the authenticated customer's profile. Only allows updating the logged-in customer's own data.
122
+
123
+ ```typescript
124
+ const result = await auth.updateProfile({
125
+ firstName: 'John',
126
+ lastName: 'Doe',
127
+ displayName: 'Johnny',
128
+ phone: '+1234567890',
129
+ });
130
+
131
+ console.log(result.customer.firstName); // 'John'
132
+ ```
133
+
134
+ **Available fields:**
135
+ - `firstName` - Customer's first name
136
+ - `lastName` - Customer's last name
137
+ - `displayName` - Display name (nickname)
138
+ - `phone` - Primary phone number
139
+ - `profilePhoto` - URL to profile photo
140
+
141
+ #### `logout(): Promise<void>`
142
+
143
+ Log out the current user.
144
+
145
+ ```typescript
146
+ await auth.logout();
147
+ ```
148
+
149
+ #### `getOrders(options?): Promise<GetOrdersResult>`
150
+
151
+ Get the authenticated customer's order history. Only returns orders for the logged-in customer.
152
+
153
+ ```typescript
154
+ // Get all orders (default limit 50)
155
+ const { orders, hasMore, nextCursor } = await auth.getOrders();
156
+
157
+ // With pagination
158
+ const page1 = await auth.getOrders({ limit: 10 });
159
+ const page2 = await auth.getOrders({ limit: 10, cursor: page1.nextCursor });
160
+
161
+ // Filter by status
162
+ const completedOrders = await auth.getOrders({ status: 'completed' });
163
+ ```
164
+
165
+ **Options:**
166
+ - `limit` - Number of orders to return (max 100, default 50)
167
+ - `cursor` - Order ID to start after (for pagination)
168
+ - `status` - Filter by status: `draft`, `pending`, `confirmed`, `processing`, `completed`, `cancelled`, `refunded`, `open_tab`
169
+
170
+ **Returns:**
171
+ - `orders` - Array of order objects with items, totals, and status
172
+ - `hasMore` - Whether there are more orders to fetch
173
+ - `nextCursor` - Cursor for the next page (if `hasMore` is true)
174
+
175
+ #### `getBookings(options?): Promise<GetBookingsResult>`
176
+
177
+ Get the authenticated customer's booking history. Only returns bookings for the logged-in customer.
178
+
179
+ ```typescript
180
+ // Get all bookings (default limit 50)
181
+ const { bookings, hasMore, nextCursor } = await auth.getBookings();
182
+
183
+ // Get upcoming bookings only (sorted soonest first)
184
+ const upcomingBookings = await auth.getBookings({ upcoming: true });
185
+
186
+ // Filter by status
187
+ const confirmedBookings = await auth.getBookings({ status: 'confirmed' });
188
+
189
+ // With pagination
190
+ const page1 = await auth.getBookings({ limit: 10 });
191
+ const page2 = await auth.getBookings({ limit: 10, cursor: page1.nextCursor });
192
+ ```
193
+
194
+ **Options:**
195
+ - `limit` - Number of bookings to return (max 100, default 50)
196
+ - `cursor` - Booking ID to start after (for pagination)
197
+ - `status` - Filter by status: `draft`, `scheduled`, `confirmed`, `reminder_sent`, `waiting_room`, `waiting_list`, `checked_in`, `active`, `in_progress`, `completed`, `cancelled`, `no_show`, `rescheduled`
198
+ - `upcoming` - If true, only return future bookings (sorted soonest first)
199
+
200
+ **Returns:**
201
+ - `bookings` - Array of booking objects with service details, date/time, and status
202
+ - `hasMore` - Whether there are more bookings to fetch
203
+ - `nextCursor` - Cursor for the next page (if `hasMore` is true)
204
+
205
+ #### `getEnquiries(options?): Promise<GetEnquiriesResult>`
206
+
207
+ Get the authenticated customer's enquiry history. Only returns enquiries for the logged-in customer.
208
+
209
+ ```typescript
210
+ // Get all enquiries (default limit 50)
211
+ const { enquiries, hasMore, nextCursor } = await auth.getEnquiries();
212
+
213
+ // Filter by status
214
+ const openEnquiries = await auth.getEnquiries({ status: 'in_progress' });
215
+
216
+ // With pagination
217
+ const page1 = await auth.getEnquiries({ limit: 10 });
218
+ const page2 = await auth.getEnquiries({ limit: 10, cursor: page1.nextCursor });
219
+ ```
220
+
221
+ **Options:**
222
+ - `limit` - Number of enquiries to return (max 100, default 50)
223
+ - `cursor` - Enquiry ID to start after (for pagination)
224
+ - `status` - Filter by status: `new`, `in_progress`, `waiting_customer`, `waiting_internal`, `won`, `lost`, `closed`, `resolved`, `archived`
225
+
226
+ **Returns:**
227
+ - `enquiries` - Array of enquiry objects with subject, message, status, and priority
228
+ - `hasMore` - Whether there are more enquiries to fetch
229
+ - `nextCursor` - Cursor for the next page (if `hasMore` is true)
230
+
231
+ #### `destroy(): void`
232
+
233
+ Clean up resources when destroying the auth instance. Call this when your component unmounts or when you no longer need the auth instance.
234
+
235
+ ```typescript
236
+ // In React
237
+ useEffect(() => {
238
+ const auth = new TiquoAuth({ publicKey: 'pk_dom_xxx' });
239
+
240
+ return () => {
241
+ auth.destroy(); // Cleanup on unmount
242
+ };
243
+ }, []);
244
+
245
+ // Or when done with auth
246
+ auth.destroy();
247
+ ```
248
+
249
+ #### `onAuthStateChange(callback): () => void`
250
+
251
+ Subscribe to authentication state changes. Returns an unsubscribe function.
252
+
253
+ ```typescript
254
+ const unsubscribe = auth.onAuthStateChange((session) => {
255
+ if (session) {
256
+ console.log('User logged in:', session.user.email);
257
+ } else {
258
+ console.log('User logged out');
259
+ }
260
+ });
261
+
262
+ // Later: unsubscribe()
263
+ ```
264
+
265
+ #### `embedCustomerFlow(flowUrl, container, options?): Promise<HTMLIFrameElement>`
266
+
267
+ Embed a Tiquo customer flow with automatic authentication.
268
+
269
+ ```typescript
270
+ await auth.embedCustomerFlow(
271
+ 'https://book.tiquo.app/your-flow',
272
+ '#container',
273
+ {
274
+ width: '100%',
275
+ height: '600px',
276
+ onLoad: () => console.log('Flow loaded'),
277
+ onError: (err) => console.error('Flow error:', err)
278
+ }
279
+ );
280
+ ```
281
+
282
+ #### `getIframeToken(customerFlowId?): Promise<IframeTokenResult>`
283
+
284
+ Generate a short-lived token for manual iframe authentication.
285
+
286
+ ```typescript
287
+ const { token, expiresAt } = await auth.getIframeToken();
288
+ // Use token in iframe URL: ?_auth_token=xxx
289
+ ```
290
+
291
+ ## React Integration
292
+
293
+ ```tsx
294
+ import { TiquoAuth } from '@tiquo/dom-package';
295
+ import { useState, useEffect } from 'react';
296
+
297
+ const auth = new TiquoAuth({ publicKey: 'pk_dom_xxx' });
298
+
299
+ function App() {
300
+ const [session, setSession] = useState(null);
301
+ const [loading, setLoading] = useState(true);
302
+
303
+ useEffect(() => {
304
+ // Subscribe to auth changes
305
+ const unsubscribe = auth.onAuthStateChange((s) => {
306
+ setSession(s);
307
+ setLoading(false);
308
+ });
309
+
310
+ return unsubscribe;
311
+ }, []);
312
+
313
+ if (loading) return <div>Loading...</div>;
314
+
315
+ if (!session) {
316
+ return <LoginForm />;
317
+ }
318
+
319
+ return (
320
+ <div>
321
+ <p>Welcome, {session.user.email}!</p>
322
+ <button onClick={() => auth.logout()}>Logout</button>
323
+ </div>
324
+ );
325
+ }
326
+
327
+ function LoginForm() {
328
+ const [email, setEmail] = useState('');
329
+ const [otp, setOtp] = useState('');
330
+ const [step, setStep] = useState<'email' | 'otp'>('email');
331
+ const [error, setError] = useState('');
332
+
333
+ const handleSendOTP = async (e) => {
334
+ e.preventDefault();
335
+ try {
336
+ await auth.sendOTP(email);
337
+ setStep('otp');
338
+ } catch (err) {
339
+ setError(err.message);
340
+ }
341
+ };
342
+
343
+ const handleVerifyOTP = async (e) => {
344
+ e.preventDefault();
345
+ try {
346
+ await auth.verifyOTP(email, otp);
347
+ // Auth state change will update the UI
348
+ } catch (err) {
349
+ setError(err.message);
350
+ }
351
+ };
352
+
353
+ if (step === 'email') {
354
+ return (
355
+ <form onSubmit={handleSendOTP}>
356
+ <input
357
+ type="email"
358
+ value={email}
359
+ onChange={(e) => setEmail(e.target.value)}
360
+ placeholder="Email"
361
+ required
362
+ />
363
+ <button type="submit">Send Code</button>
364
+ {error && <p>{error}</p>}
365
+ </form>
366
+ );
367
+ }
368
+
369
+ return (
370
+ <form onSubmit={handleVerifyOTP}>
371
+ <p>Enter the code sent to {email}</p>
372
+ <input
373
+ type="text"
374
+ value={otp}
375
+ onChange={(e) => setOtp(e.target.value)}
376
+ placeholder="000000"
377
+ maxLength={6}
378
+ required
379
+ />
380
+ <button type="submit">Verify</button>
381
+ {error && <p>{error}</p>}
382
+ </form>
383
+ );
384
+ }
385
+ ```
386
+
387
+ ## Vue Integration
388
+
389
+ ```vue
390
+ <script setup>
391
+ import { TiquoAuth } from '@tiquo/dom-package';
392
+ import { ref, onMounted, onUnmounted } from 'vue';
393
+
394
+ const auth = new TiquoAuth({ publicKey: 'pk_dom_xxx' });
395
+
396
+ const session = ref(null);
397
+ const loading = ref(true);
398
+ let unsubscribe;
399
+
400
+ onMounted(() => {
401
+ unsubscribe = auth.onAuthStateChange((s) => {
402
+ session.value = s;
403
+ loading.value = false;
404
+ });
405
+ });
406
+
407
+ onUnmounted(() => {
408
+ unsubscribe?.();
409
+ auth.destroy(); // Clean up resources including tab sync
410
+ });
411
+
412
+ async function logout() {
413
+ await auth.logout();
414
+ }
415
+ </script>
416
+
417
+ <template>
418
+ <div v-if="loading">Loading...</div>
419
+ <div v-else-if="session">
420
+ <p>Welcome, {{ session.user.email }}!</p>
421
+ <button @click="logout">Logout</button>
422
+ </div>
423
+ <LoginForm v-else />
424
+ </template>
425
+ ```
426
+
427
+ ## Multi-Tab Session Sync
428
+
429
+ The SDK automatically synchronizes authentication state across all browser tabs using the [BroadcastChannel API](https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel). This means:
430
+
431
+ - **Login in one tab → All tabs are authenticated**
432
+ - **Logout in one tab → All tabs are logged out**
433
+ - **Profile updates sync across tabs**
434
+
435
+ This feature is enabled by default and works automatically. No additional code is required.
436
+
437
+ ### Disabling Tab Sync
438
+
439
+ If you need to disable multi-tab sync (e.g., for isolated sessions):
440
+
441
+ ```typescript
442
+ const auth = new TiquoAuth({
443
+ publicKey: 'pk_dom_xxx',
444
+ enableTabSync: false, // Disable multi-tab sync
445
+ });
446
+ ```
447
+
448
+ ### Browser Support
449
+
450
+ Multi-tab sync requires `BroadcastChannel` support. It's available in all modern browsers:
451
+ - Chrome 54+
452
+ - Firefox 38+
453
+ - Safari 15.4+
454
+ - Edge 79+
455
+
456
+ For older browsers, the feature gracefully degrades - auth still works normally, just without cross-tab sync.
457
+
458
+ ## Error Handling
459
+
460
+ All SDK methods can throw `TiquoAuthError` with helpful error codes:
461
+
462
+ ```typescript
463
+ import { TiquoAuth, TiquoAuthError } from '@tiquo/dom-package';
464
+
465
+ try {
466
+ await auth.verifyOTP(email, otp);
467
+ } catch (error) {
468
+ if (error instanceof TiquoAuthError) {
469
+ switch (error.code) {
470
+ case 'OTP_VERIFY_FAILED':
471
+ console.log('Invalid or expired code');
472
+ break;
473
+ case 'NOT_AUTHENTICATED':
474
+ console.log('Please log in first');
475
+ break;
476
+ default:
477
+ console.log('Error:', error.message);
478
+ }
479
+ }
480
+ }
481
+ ```
482
+
483
+ ## Security Considerations
484
+
485
+ - The public key is safe to expose in client-side code
486
+ - Sessions are stored in localStorage with automatic refresh
487
+ - OTP codes expire after 10 minutes
488
+ - Failed OTP attempts are rate limited (max 5 attempts per code)
489
+
490
+ ## Support
491
+
492
+ - [Documentation](https://docs.tiquo.com/dom-package)
493
+ - [GitHub Issues](https://github.com/tiquo/dom-package/issues)
494
+ - [Support Email](mailto:support@tiquo.com)
495
+
496
+ ## License
497
+
498
+ MIT