@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/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
|