@usesophi/sophi-web-sdk 0.1.0-beta.7 → 0.1.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 +21 -444
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -2
- package/dist/index.d.ts +27 -2
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +3 -0
- package/dist/index.umd.js.map +1 -0
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -3,33 +3,13 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/sophi-web-sdk)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
TypeScript-first SDK for embedding the Sophi widget in web applications.
|
|
7
7
|
|
|
8
|
-
##
|
|
9
|
-
|
|
10
|
-
- 🚀 **Framework Agnostic** - Works with any JavaScript framework or vanilla JS
|
|
11
|
-
- 📦 **Zero Dependencies** - Lightweight with no runtime dependencies
|
|
12
|
-
- 🔒 **Secure** - Built-in authentication and origin validation
|
|
13
|
-
- 🔑 **Automatic Session Management** - Handles authentication automatically
|
|
14
|
-
- 💪 **TypeScript First** - Full type definitions included
|
|
15
|
-
- 🎯 **Event-Driven** - Clean event-based API for loose coupling
|
|
16
|
-
- 📱 **Modern** - ESM and CommonJS support with tree-shaking
|
|
17
|
-
- 🎨 **Flexible** - Client controls all UX (toasts, errors, styling)
|
|
18
|
-
- 📁 **Well-Structured** - Modular architecture for easy maintenance
|
|
19
|
-
|
|
20
|
-
## Security Note
|
|
21
|
-
|
|
22
|
-
**Q: Will users see my API URLs?**
|
|
23
|
-
**A: Yes, and that's okay!**
|
|
24
|
-
|
|
25
|
-
API URLs in client-side code are always visible to users (they can see them in browser DevTools network tab). This is **normal and safe** because:
|
|
8
|
+
## Documentation
|
|
26
9
|
|
|
27
|
-
|
|
28
|
-
- ✅ **Real security comes from your `apiKey` and `clientId` validation on the server**
|
|
29
|
-
- ✅ Your API should authenticate and authorize every request
|
|
30
|
-
- ✅ Never put actual secrets (API keys, tokens) in client code - they come from your backend
|
|
10
|
+
For setup, integration guides, API details, and framework examples, see:
|
|
31
11
|
|
|
32
|
-
|
|
12
|
+
[https://docs.usesophi.com/frontend-integration/](https://docs.usesophi.com/frontend-integration/)
|
|
33
13
|
|
|
34
14
|
## Installation
|
|
35
15
|
|
|
@@ -37,444 +17,41 @@ Think of API URLs like a street address - it's public information. The security
|
|
|
37
17
|
npm install sophi-web-sdk
|
|
38
18
|
```
|
|
39
19
|
|
|
40
|
-
Or with yarn:
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
yarn add sophi-web-sdk
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
Or with pnpm:
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
pnpm add sophi-web-sdk
|
|
50
|
-
```
|
|
51
|
-
|
|
52
20
|
## Quick Start
|
|
53
21
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
```javascript
|
|
22
|
+
```typescript
|
|
57
23
|
import { SophiWidget } from "sophi-web-sdk";
|
|
58
24
|
|
|
59
25
|
const widget = new SophiWidget();
|
|
60
26
|
|
|
61
|
-
// Initialize with authentication (async)
|
|
62
27
|
await widget.init({
|
|
63
|
-
apiKey: "your-api-key",
|
|
64
|
-
clientId: "your-client-id",
|
|
65
|
-
userId: "user-12345",
|
|
28
|
+
apiKey: "your-api-key",
|
|
29
|
+
clientId: "your-client-id",
|
|
30
|
+
userId: "user-12345",
|
|
66
31
|
container: "#sophi-widget",
|
|
67
|
-
environment: "production",
|
|
68
|
-
metadata: {
|
|
69
|
-
// Optional metadata
|
|
70
|
-
theme: "dark",
|
|
71
|
-
plan: "premium",
|
|
72
|
-
},
|
|
73
|
-
onReady: () => console.log("Widget ready!"),
|
|
32
|
+
environment: "production",
|
|
74
33
|
});
|
|
75
34
|
|
|
76
|
-
// Listen for events
|
|
77
35
|
widget.on("add_to_cart", (data) => {
|
|
78
|
-
console.log(
|
|
79
|
-
// Handle cart logic here
|
|
80
|
-
});
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
> **Note**: The `init()` method is now async and automatically handles authentication with the Sophi API before loading the widget. The API URL is determined automatically based on the `environment` setting.
|
|
84
|
-
|
|
85
|
-
### React
|
|
86
|
-
|
|
87
|
-
```tsx
|
|
88
|
-
import { useEffect, useRef } from "react";
|
|
89
|
-
import { SophiWidget } from "sophi-web-sdk";
|
|
90
|
-
|
|
91
|
-
function MyComponent() {
|
|
92
|
-
const widgetRef = useRef<SophiWidget | null>(null);
|
|
93
|
-
|
|
94
|
-
useEffect(() => {
|
|
95
|
-
const widget = new SophiWidget();
|
|
96
|
-
widgetRef.current = widget;
|
|
97
|
-
|
|
98
|
-
// Async initialization
|
|
99
|
-
const initWidget = async () => {
|
|
100
|
-
try {
|
|
101
|
-
await widget.init({
|
|
102
|
-
apiKey: "your-api-key",
|
|
103
|
-
clientId: "your-client-id",
|
|
104
|
-
userId: "user-12345",
|
|
105
|
-
container: "#sophi-widget",
|
|
106
|
-
environment: "production",
|
|
107
|
-
metadata: { theme: "dark" },
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
widget.on("add_to_cart", (data) => {
|
|
111
|
-
// Handle add to cart
|
|
112
|
-
});
|
|
113
|
-
} catch (error) {
|
|
114
|
-
console.error("Failed to initialize widget:", error);
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
initWidget();
|
|
119
|
-
|
|
120
|
-
return () => widget.destroy();
|
|
121
|
-
}, []);
|
|
122
|
-
|
|
123
|
-
return <div id="sophi-widget" />;
|
|
124
|
-
}
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
See `examples/react-example/` for a complete React example with custom hooks.
|
|
128
|
-
|
|
129
|
-
### Vue 3
|
|
130
|
-
|
|
131
|
-
```vue
|
|
132
|
-
<template>
|
|
133
|
-
<div id="sophi-widget"></div>
|
|
134
|
-
</template>
|
|
135
|
-
|
|
136
|
-
<script setup>
|
|
137
|
-
import { onMounted, onUnmounted } from "vue";
|
|
138
|
-
import { SophiWidget } from "sophi-web-sdk";
|
|
139
|
-
|
|
140
|
-
let widget = null;
|
|
141
|
-
|
|
142
|
-
onMounted(() => {
|
|
143
|
-
widget = new SophiWidget();
|
|
144
|
-
|
|
145
|
-
widget.init({
|
|
146
|
-
apiKey: "your-api-key",
|
|
147
|
-
container: "#sophi-widget",
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
widget.on("add_to_cart", (data) => {
|
|
151
|
-
// Handle add to cart
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
onUnmounted(() => {
|
|
156
|
-
if (widget) {
|
|
157
|
-
widget.destroy();
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
</script>
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
### Svelte
|
|
164
|
-
|
|
165
|
-
```svelte
|
|
166
|
-
<script>
|
|
167
|
-
import { onMount, onDestroy } from 'svelte';
|
|
168
|
-
import { SophiWidget } from 'sophi-web-sdk';
|
|
169
|
-
|
|
170
|
-
let widget;
|
|
171
|
-
|
|
172
|
-
onMount(() => {
|
|
173
|
-
widget = new SophiWidget();
|
|
174
|
-
|
|
175
|
-
widget.init({
|
|
176
|
-
apiKey: 'your-api-key',
|
|
177
|
-
container: '#sophi-widget',
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
widget.on('add_to_cart', (data) => {
|
|
181
|
-
// Handle add to cart
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
onDestroy(() => {
|
|
186
|
-
if (widget) {
|
|
187
|
-
widget.destroy();
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
</script>
|
|
191
|
-
|
|
192
|
-
<div id="sophi-widget"></div>
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
### Angular
|
|
196
|
-
|
|
197
|
-
```typescript
|
|
198
|
-
import { Component, OnInit, OnDestroy } from "@angular/core";
|
|
199
|
-
import { SophiWidget } from "sophi-web-sdk";
|
|
200
|
-
|
|
201
|
-
@Component({
|
|
202
|
-
selector: "app-widget",
|
|
203
|
-
template: '<div id="sophi-widget"></div>',
|
|
204
|
-
})
|
|
205
|
-
export class WidgetComponent implements OnInit, OnDestroy {
|
|
206
|
-
private widget: SophiWidget | null = null;
|
|
207
|
-
|
|
208
|
-
ngOnInit() {
|
|
209
|
-
this.widget = new SophiWidget();
|
|
210
|
-
|
|
211
|
-
this.widget.init({
|
|
212
|
-
apiKey: "your-api-key",
|
|
213
|
-
container: "#sophi-widget",
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
this.widget.on("add_to_cart", (data) => {
|
|
217
|
-
// Handle add to cart
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
ngOnDestroy() {
|
|
222
|
-
if (this.widget) {
|
|
223
|
-
this.widget.destroy();
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
## API Reference
|
|
230
|
-
|
|
231
|
-
### `SophiWidget`
|
|
232
|
-
|
|
233
|
-
The main class for interacting with the Sophi widget.
|
|
234
|
-
|
|
235
|
-
#### `init(config: SophiConfig): Promise<void>`
|
|
236
|
-
|
|
237
|
-
Initialize the widget with configuration. This method is now **async** and handles authentication automatically.
|
|
238
|
-
|
|
239
|
-
```typescript
|
|
240
|
-
await widget.init({
|
|
241
|
-
// Required fields
|
|
242
|
-
apiKey: "your-api-key", // Your API key (x-api-key header)
|
|
243
|
-
clientId: "your-client-id", // Client ID (x-sophi-client-id header)
|
|
244
|
-
userId: "user-12345", // External user ID from your app
|
|
245
|
-
container: "#sophi-widget", // CSS selector or HTMLElement
|
|
246
|
-
|
|
247
|
-
// Optional fields
|
|
248
|
-
environment: "production", // 'test' | 'staging' | 'production' (determines API URL)
|
|
249
|
-
metadata: {
|
|
250
|
-
// Custom metadata for the session
|
|
251
|
-
theme: "dark",
|
|
252
|
-
plan: "premium",
|
|
253
|
-
},
|
|
254
|
-
width: "100%", // Widget width (default: '100%')
|
|
255
|
-
height: "600px", // Widget height (default: '600px')
|
|
256
|
-
onReady: () => {}, // Callback when widget is ready
|
|
257
|
-
onError: (error) => {}, // Callback for errors
|
|
36
|
+
console.log(data.products);
|
|
258
37
|
});
|
|
259
38
|
```
|
|
260
39
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
1. SDK validates configuration
|
|
264
|
-
2. SDK determines API URL based on `environment` setting
|
|
265
|
-
3. SDK calls authentication API with `apiKey`, `clientId`, and `userId`
|
|
266
|
-
4. API returns session data (accessToken, sessionId, etc.)
|
|
267
|
-
5. SDK creates iframe and sends session data via postMessage
|
|
268
|
-
6. Widget is ready to use
|
|
269
|
-
|
|
270
|
-
**Environment to API URL Mapping:**
|
|
271
|
-
|
|
272
|
-
- `test`: Test API URL (for development/testing)
|
|
273
|
-
- `staging`: Staging API URL
|
|
274
|
-
- `production`: Production API URL (default)
|
|
40
|
+
## Basic API
|
|
275
41
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
widget.sendUserData({
|
|
284
|
-
userId: "user-123",
|
|
285
|
-
email: "user@example.com",
|
|
286
|
-
name: "John Doe",
|
|
287
|
-
// Add any custom fields
|
|
288
|
-
customField: "value",
|
|
289
|
-
});
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
#### `on<K extends EventName>(event: K, handler: EventHandler<SophiEventMap[K]>): void`
|
|
293
|
-
|
|
294
|
-
Register an event listener.
|
|
295
|
-
|
|
296
|
-
```typescript
|
|
297
|
-
// Listen for add_to_cart events
|
|
298
|
-
widget.on("add_to_cart", (data) => {
|
|
299
|
-
console.log("Products:", data.products);
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
// Listen for ready events
|
|
303
|
-
widget.on("ready", () => {
|
|
304
|
-
console.log("Widget is ready");
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
// Listen for error events
|
|
308
|
-
widget.on("error", (error) => {
|
|
309
|
-
console.error("Widget error:", error);
|
|
310
|
-
});
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
#### `off<K extends EventName>(event: K, handler: EventHandler<SophiEventMap[K]>): void`
|
|
314
|
-
|
|
315
|
-
Unregister an event listener.
|
|
316
|
-
|
|
317
|
-
```typescript
|
|
318
|
-
const handler = (data) => console.log(data);
|
|
319
|
-
widget.on("add_to_cart", handler);
|
|
320
|
-
widget.off("add_to_cart", handler);
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
#### `show(): void`
|
|
324
|
-
|
|
325
|
-
Show the widget (sets display to 'block').
|
|
326
|
-
|
|
327
|
-
```typescript
|
|
328
|
-
widget.show();
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
#### `hide(): void`
|
|
332
|
-
|
|
333
|
-
Hide the widget (sets display to 'none').
|
|
334
|
-
|
|
335
|
-
```typescript
|
|
336
|
-
widget.hide();
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
#### `destroy(): void`
|
|
340
|
-
|
|
341
|
-
Destroy the widget and cleanup all resources.
|
|
342
|
-
|
|
343
|
-
```typescript
|
|
344
|
-
widget.destroy();
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
#### `isReady(): boolean`
|
|
348
|
-
|
|
349
|
-
Check if the widget is initialized and ready.
|
|
350
|
-
|
|
351
|
-
```typescript
|
|
352
|
-
if (widget.isReady()) {
|
|
353
|
-
widget.sendUserData({ userId: "123" });
|
|
354
|
-
}
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
## TypeScript Support
|
|
358
|
-
|
|
359
|
-
The SDK is written in TypeScript and includes full type definitions.
|
|
360
|
-
|
|
361
|
-
```typescript
|
|
362
|
-
import { SophiWidget, SophiConfig, AddToCartEvent, UserData } from "sophi-web-sdk";
|
|
363
|
-
|
|
364
|
-
const config: SophiConfig = {
|
|
365
|
-
apiKey: "your-api-key",
|
|
366
|
-
container: "#widget",
|
|
367
|
-
};
|
|
368
|
-
|
|
369
|
-
const widget = new SophiWidget();
|
|
370
|
-
widget.init(config);
|
|
371
|
-
|
|
372
|
-
widget.on("add_to_cart", (data: AddToCartEvent) => {
|
|
373
|
-
data.products.forEach((product) => {
|
|
374
|
-
console.log(product.productId, product.variantId);
|
|
375
|
-
});
|
|
376
|
-
});
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
## Events
|
|
380
|
-
|
|
381
|
-
### `add_to_cart`
|
|
382
|
-
|
|
383
|
-
Fired when the widget requests to add products to the cart.
|
|
384
|
-
|
|
385
|
-
```typescript
|
|
386
|
-
interface AddToCartEvent {
|
|
387
|
-
products: Array<{
|
|
388
|
-
productId: string;
|
|
389
|
-
variantId?: string;
|
|
390
|
-
}>;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
widget.on("add_to_cart", (data: AddToCartEvent) => {
|
|
394
|
-
// Handle cart logic
|
|
395
|
-
// Show toast notifications
|
|
396
|
-
// Update cart UI
|
|
397
|
-
});
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
### `ready`
|
|
401
|
-
|
|
402
|
-
Fired when the widget iframe is ready.
|
|
403
|
-
|
|
404
|
-
```typescript
|
|
405
|
-
widget.on("ready", () => {
|
|
406
|
-
console.log("Widget is ready");
|
|
407
|
-
});
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
### `error`
|
|
411
|
-
|
|
412
|
-
Fired when an error occurs.
|
|
413
|
-
|
|
414
|
-
```typescript
|
|
415
|
-
widget.on("error", (error: Error) => {
|
|
416
|
-
console.error("Error:", error.message);
|
|
417
|
-
});
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
## Security
|
|
421
|
-
|
|
422
|
-
The SDK includes comprehensive security features:
|
|
423
|
-
|
|
424
|
-
- **Authentication**: Automatic session creation with JWT tokens
|
|
425
|
-
- **Origin Validation**: All postMessage events are validated against the iframe origin
|
|
426
|
-
- **Message Validation**: Incoming messages are validated for correct structure
|
|
427
|
-
- **Type Safety**: TypeScript ensures type-safe message handling
|
|
428
|
-
- **Secure Headers**: API key and client ID sent via secure headers
|
|
429
|
-
- **Token Expiration**: Session tokens include expiration timestamps
|
|
430
|
-
|
|
431
|
-
See [AUTHENTICATION.md](./AUTHENTICATION.md) for security best practices.
|
|
432
|
-
|
|
433
|
-
## Documentation
|
|
434
|
-
|
|
435
|
-
- **[AUTHENTICATION.md](./AUTHENTICATION.md)** - Detailed authentication flow and API documentation
|
|
436
|
-
- **[FILE_STRUCTURE.md](./FILE_STRUCTURE.md)** - Project structure and architecture
|
|
437
|
-
- **[GETTING_STARTED.md](./GETTING_STARTED.md)** - Complete getting started guide
|
|
438
|
-
- **[QUICK_START_TESTING.md](./QUICK_START_TESTING.md)** - Quick testing guide
|
|
439
|
-
- **[TESTING.md](./TESTING.md)** - Comprehensive testing documentation
|
|
42
|
+
- `init(config)` - Initializes the widget
|
|
43
|
+
- `on(event, handler)` - Subscribes to widget events
|
|
44
|
+
- `off(event, handler)` - Unsubscribes from widget events
|
|
45
|
+
- `sendUserData(data)` - Sends user data updates
|
|
46
|
+
- `show()` / `hide()` - Controls widget visibility
|
|
47
|
+
- `isReady()` - Returns widget readiness status
|
|
48
|
+
- `destroy()` - Cleans up widget resources
|
|
440
49
|
|
|
441
50
|
## Examples
|
|
442
51
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
- **`examples/vanilla-html/`** - Pure HTML/JS example with no build step
|
|
446
|
-
- **`examples/react-example/`** - React example with custom hooks
|
|
447
|
-
|
|
448
|
-
To run the examples:
|
|
449
|
-
|
|
450
|
-
```bash
|
|
451
|
-
# Vanilla HTML example
|
|
452
|
-
cd examples/vanilla-html
|
|
453
|
-
# Open index.html in your browser
|
|
454
|
-
|
|
455
|
-
# React example
|
|
456
|
-
cd examples/react-example
|
|
457
|
-
npm install
|
|
458
|
-
npm run dev
|
|
459
|
-
```
|
|
460
|
-
|
|
461
|
-
Both examples include the new authentication flow with all required fields.
|
|
462
|
-
|
|
463
|
-
## Browser Support
|
|
464
|
-
|
|
465
|
-
- Chrome (latest)
|
|
466
|
-
- Firefox (latest)
|
|
467
|
-
- Safari (latest)
|
|
468
|
-
- Edge (latest)
|
|
469
|
-
|
|
470
|
-
## Contributing
|
|
471
|
-
|
|
472
|
-
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
52
|
+
- `examples/vanilla-html/`
|
|
53
|
+
- `examples/react-example/`
|
|
473
54
|
|
|
474
55
|
## License
|
|
475
56
|
|
|
476
|
-
MIT
|
|
477
|
-
|
|
478
|
-
## Support
|
|
479
|
-
|
|
480
|
-
For issues and questions, please open an issue on GitHub or contact support@sophi.ai.
|
|
57
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
'use strict';var m={ADD_TO_CART:"add_to_cart",READY:"ready",ERROR:"error",SEND_TO_CHECKOUT:"send_to_checkout"},d={USER_DATA:"user_data",CONFIG:"config",UPDATE_USER_CART:"update_user_cart",DESTROY:"destroy",SESSION_DATA:"session_data",CLIENT_ID_UPDATED:"client_id_updated"};var s=class s{constructor(e,t){this.namespace=s.sanitizeNamespace(e),this.maxAgeSeconds=t?.maxAgeSeconds??s.DEFAULT_MAX_AGE_SECONDS,this.keys={...s.DEFAULT_KEYS,...t?.keys??{}};}static buildNamespace(e,t){return s.sanitizeNamespace(`sophi-widget-${e}-${t}`)}static canUseCookies(){return typeof document<"u"&&typeof document.cookie=="string"}set(e,t){s.canUseCookies()&&(document.cookie=`${this.formatName(e)}=${encodeURIComponent(t)}; path=/; max-age=${this.maxAgeSeconds}; samesite=lax`);}get(e){return s.canUseCookies()?s.readRawCookie(this.formatName(e)):null}delete(e){s.canUseCookies()&&(document.cookie=`${this.formatName(e)}=; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT; samesite=lax`);}clearAll(){Object.keys(this.keys).forEach(e=>{this.delete(e);});}formatName(e){return `${this.namespace}-${this.keys[e]}`}static readRawCookie(e){let t=document.cookie.split(";").map(r=>r.trim());for(let r of t){if(!r)continue;let[i,...n]=r.split("=");if(i===e)return decodeURIComponent(n.join("="))}return null}static sanitizeNamespace(e){let t=e.replace(/[^a-zA-Z0-9_-]/g,"");return t.length?t:"sophi-widget"}};s.DEFAULT_MAX_AGE_SECONDS=3600*24*30,s.DEFAULT_KEYS={clientId:"client-id",externalUserId:"external-user-id",sessionData:"session-data",metadataHash:"metadata-hash"};var o=s;var c=class{constructor(e,t,r){this.clientId=null;this.userId=void 0;this.storedUserId=null;this.baseUrl=e,this.apiKey=t,this.userId=r,this.cookieManager=new o(o.buildNamespace(e,t)),this.restoreSessionFromCookies();}async createSession(e){let t=`${this.baseUrl}/api/v1/auth/client/session`;try{let r={"Content-Type":"application/json",Accept:"application/json","x-api-key":this.apiKey};if(this.clientId){r["x-sophi-client-id"]=this.clientId;let p=this.storedUserId||null,l=this.userId||null;p!==l&&l&&(await this.mergeUser({userId:l}),this.storedUserId=l);}let i=await fetch(t,{method:"POST",headers:r,body:JSON.stringify({externalUserId:this.userId,metadata:e.metadata})});if(!i.ok){let p=await i.text();throw new Error(`Failed to create session: ${i.status} ${i.statusText}. ${p}`)}let n=await i.json();if(!n.success)throw new Error(`Session creation failed: ${n.message||"Unknown error"}`);if(!n.data)throw new Error("Session data is missing from response");return this.persistClientInfo(n.data.clientId,this.userId),n.data}catch(r){throw r instanceof Error?r:new Error(`Unexpected error during session creation: ${String(r)}`)}}validateSessionData(e){return !!(e.accessToken&&e.sessionId&&e.clientId&&e.companyCode&&e.expiresAt)}async mergeUser(e){let t=`${this.baseUrl}/api/v1/auth/client/info`;try{if(!this.clientId)throw new Error("Client ID is not set. Please create a session first.");let r=await fetch(t,{method:"PATCH",headers:{"Content-Type":"application/json",Accept:"application/json","x-api-key":this.apiKey,"x-sophi-client-id":this.clientId},body:JSON.stringify({externalUserId:e.userId})});if(!r.ok){let n=await r.text();throw new Error(`Failed to merge user: ${r.status} ${r.statusText}. ${n}`)}let i=await r.json();if(!i.success)throw new Error(`User merge failed: ${i.message||"Unknown error"}`);if(!i.data)throw new Error("Merge data is missing from response");return i.data}catch(r){throw r instanceof Error?r:new Error(`Unexpected error during user merge: ${String(r)}`)}}persistClientInfo(e,t){this.clientId=e,this.userId=t,this.storedUserId=t||null,o.canUseCookies()&&(this.cookieManager.set("clientId",e),this.cookieManager.set("externalUserId",t||""));}restoreSessionFromCookies(){if(!o.canUseCookies())return;let e=this.cookieManager.get("externalUserId"),t=this.cookieManager.get("clientId");if(this.storedUserId=e||null,!t)return;let r=e||null,i=this.userId||null;if(r===i){this.clientId=t,!this.userId&&e&&(this.userId=e);return}if(r&&!i){this.clearStoredClientInfo();return}if(r&&i&&r!==i){this.clearStoredClientInfo();return}if(!r&&i){this.clientId=t;return}}clearStoredClientInfo(){this.clientId=null,this.storedUserId=null,o.canUseCookies()&&this.cookieManager.clearAll();}};var f={WIDTH:"100%",HEIGHT:"600px"},g={test:"http://10.0.2.127:80",production:"https://api.usesophi.com"},I={CREATE_SESSION:"/api/v1/auth/client/session"},a={MISSING_API_KEY:"API key is required and must be a string",MISSING_CLIENT_ID:"Client ID is required and must be a string",MISSING_USER_ID:"User ID is required and must be a string",MISSING_CONTAINER:"Container is required",MISSING_IFRAME_URL:"Iframe URL is required and must be a string",INVALID_IFRAME_URL:"Invalid iframe URL",INVALID_ENVIRONMENT:"Invalid environment. Must be 'dev', 'test', 'staging', or 'production'",CONTAINER_NOT_FOUND:"Container element not found",NOT_INITIALIZED:"Widget not initialized. Call init() first.",ALREADY_INITIALIZED:"Sophi Widget is already initialized. Call destroy() first to reinitialize.",SESSION_CREATION_FAILED:"Failed to create authentication session",INVALID_SESSION_DATA:"Invalid session data received from API",YOU_NEED_TO_INIT_THE_WIDGET_FIRST:"You need to initialize the widget first. Call init() before calling this method."};var h=class{constructor(){this.config=null;this.iframe=null;this.container=null;this.eventListeners=new Map;this.messageHandler=null;this.isInitialized=false;this.iframeOrigin=null;this.sessionData=null;this.apiService=null;this.apiBaseUrl=null;}async init(e){if(this.isInitialized){console.warn(a.ALREADY_INITIALIZED);return}try{this.validateConfig(e),this.config=this.formatConfig(e),this.apiBaseUrl=g[e.environment||"production"],this.apiService=new c(this.apiBaseUrl,e.apiKey,e.userId),await this.createAuthSession(),this.isInitialized=!0;try{let t=new URL(this.getIframeUrl());this.iframeOrigin=t.origin;}catch{let r=new Error(`${a.INVALID_IFRAME_URL}: ${this.getIframeUrl()}`);throw this.emitError(r),r}if(this.container=this.resolveContainer(e.container),!this.container){let t=new Error(a.CONTAINER_NOT_FOUND);throw this.emitError(t),t}this.createIframe(),this.setupMessageListener(),this.iframe?.addEventListener("load",()=>{setTimeout(()=>{this.sendSessionData();},100);}),e.onReady&&e.onReady();}catch(t){this.isInitialized=false,this.config=null,this.apiService=null,this.sessionData=null;let r=t instanceof Error?t:new Error(String(t));throw this.emitError(r),r}}sendUserData(e){if(!this.isInitialized||!this.iframe){console.warn("Widget not initialized. Call init() first.");return}let t={type:d.USER_DATA,data:e};this.postMessage(t);}updateUserCart(e){if(!this.isInitialized||!this.iframe){console.warn("Widget not initialized. Call init() first.");return}let t={type:d.UPDATE_USER_CART,data:e};this.postMessage(t);}onDestroy(){if(!this.isInitialized||!this.iframe){console.warn("Widget not initialized. Call init() first.");return}let e={type:d.DESTROY};this.postMessage(e);}on(e,t){this.eventListeners.has(e)||this.eventListeners.set(e,new Set),this.eventListeners.get(e).add(t);}off(e,t){let r=this.eventListeners.get(e);r&&r.delete(t);}show(){this.iframe&&(this.iframe.style.display="block");}hide(){this.iframe&&(this.iframe.style.display="none");}async destroy(){this.onDestroy(),await new Promise(e=>setTimeout(e,300)),this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.iframe&&this.iframe.parentNode&&(this.iframe.parentNode.removeChild(this.iframe),this.iframe=null),this.eventListeners.clear(),this.config=null,this.container=null,this.isInitialized=false,this.iframeOrigin=null,this.sessionData=null,this.apiService=null;}isReady(){return this.isInitialized&&this.iframe!==null}validateConfig(e){if(!e.apiKey||typeof e.apiKey!="string")throw new Error(a.MISSING_API_KEY);if(e.userId&&typeof e.userId!="string")throw new Error(a.MISSING_USER_ID);if(!e.container)throw new Error(a.MISSING_CONTAINER)}formatConfig(e){return {...e,userId:e.userId||void 0}}async createAuthSession(){if(!this.config||!this.apiService)throw new Error(a.NOT_INITIALIZED);try{if(this.sessionData=await this.apiService.createSession({externalUserId:this.config.userId,metadata:this.config.metadata}),!this.apiService.validateSessionData(this.sessionData))throw new Error(a.INVALID_SESSION_DATA)}catch(e){throw e instanceof Error?e:new Error(a.SESSION_CREATION_FAILED)}}sendSessionData(){if(!this.sessionData){console.warn("Session data not available");return}let e={type:d.SESSION_DATA,data:{...this.sessionData,userName:this.config?.userName}};this.postMessage(e);}resolveContainer(e){return typeof e=="string"?document.querySelector(e):e}createIframe(){if(!this.container||!this.config)return;let e=document.createElement("iframe");e.src=this.buildIframeUrl(),e.style.width=this.config.width||"100%",e.style.height=this.config.height||"600px",e.style.border="none",e.title="Sophi AI Widget",e.allow="microphone; camera",this.container.appendChild(e),this.iframe=e;}buildIframeUrl(){return this.config?new URL(this.getIframeUrl()).toString():""}getIframeUrl(){return this.config?this.config.environment==="production"?"https://app.usesophi.com":"http://localhost:5173":""}setupMessageListener(){this.messageHandler=e=>{if(this.iframeOrigin&&e.origin!==this.iframeOrigin){console.warn(`Received message from untrusted origin: ${e.origin}`);return}try{let t=typeof e.data=="string"?JSON.parse(e.data):e.data;this.handleIncomingMessage(t);}catch(t){console.error("Error processing message from iframe:",t);}},window.addEventListener("message",this.messageHandler);}handleIncomingMessage(e){switch(e.type){case "add_to_cart":this.isValidAddToCartMessage(e.data)?this.emit("add_to_cart",e.data):(console.error("Invalid add_to_cart message structure:",e.data),this.emitError(new Error("Invalid add_to_cart message format")));break;case "send_to_checkout":this.emit("send_to_checkout",void 0);break;case "ready":this.emit("ready",void 0);break;case "error":let t=e.data instanceof Error?e.data:new Error(String(e.data));this.emitError(t);break;default:console.warn("Unknown message type:",e);}}isValidAddToCartMessage(e){if(!e||typeof e!="object")return false;let t=e;return Array.isArray(t.products)?t.products.every(r=>{if(!r||typeof r!="object")return false;let i=r;return !(typeof i.productId!="string"||i.variantId!==void 0&&typeof i.variantId!="string")}):false}postMessage(e){if(!this.iframe||!this.iframe.contentWindow||!this.iframeOrigin){console.warn("Cannot send message: iframe not ready");return}try{this.iframe.contentWindow.postMessage(e,this.iframeOrigin);}catch(t){console.error("Error sending message to iframe:",t),this.emitError(t instanceof Error?t:new Error(String(t)));}}emit(e,t){let r=this.eventListeners.get(e);r&&r.forEach(i=>{try{i(t);}catch(n){console.error(`Error in ${e} event handler:`,n);}});}emitError(e){this.emit("error",e),this.config?.onError&&this.config.onError(e);}};
|
|
2
|
-
exports.API_BASE_URLS=
|
|
1
|
+
'use strict';var f={ADD_TO_CART:"add_to_cart",READY:"ready",ERROR:"error",SEND_TO_CHECKOUT:"send_to_checkout"},o={USER_DATA:"user_data",CONFIG:"config",UPDATE_USER_CART:"update_user_cart",DESTROY:"destroy",SESSION_DATA:"session_data",CLIENT_ID_UPDATED:"client_id_updated",LAYOUT_CONFIG:"layout_config",TOGGLE_HISTORY:"toggle_history"};var s=class s{constructor(e,t){this.namespace=s.sanitizeNamespace(e),this.maxAgeSeconds=t?.maxAgeSeconds??s.DEFAULT_MAX_AGE_SECONDS,this.keys={...s.DEFAULT_KEYS,...t?.keys??{}};}static buildNamespace(e,t){return s.sanitizeNamespace(`sophi-widget-${e}-${t}`)}static canUseCookies(){return typeof document<"u"&&typeof document.cookie=="string"}set(e,t){s.canUseCookies()&&(document.cookie=`${this.formatName(e)}=${encodeURIComponent(t)}; path=/; max-age=${this.maxAgeSeconds}; samesite=lax`);}get(e){return s.canUseCookies()?s.readRawCookie(this.formatName(e)):null}delete(e){s.canUseCookies()&&(document.cookie=`${this.formatName(e)}=; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT; samesite=lax`);}clearAll(){Object.keys(this.keys).forEach(e=>{this.delete(e);});}formatName(e){return `${this.namespace}-${this.keys[e]}`}static readRawCookie(e){let t=document.cookie.split(";").map(i=>i.trim());for(let i of t){if(!i)continue;let[r,...n]=i.split("=");if(r===e)return decodeURIComponent(n.join("="))}return null}static sanitizeNamespace(e){let t=e.replace(/[^a-zA-Z0-9_-]/g,"");return t.length?t:"sophi-widget"}};s.DEFAULT_MAX_AGE_SECONDS=3600*24*30,s.DEFAULT_KEYS={clientId:"client-id",externalUserId:"external-user-id",sessionData:"session-data",metadataHash:"metadata-hash"};var d=s;var c=class{constructor(e,t,i){this.clientId=null;this.userId=void 0;this.storedUserId=null;this.baseUrl=e,this.apiKey=t,this.userId=i,this.cookieManager=new d(d.buildNamespace(e,t)),this.restoreSessionFromCookies();}async createSession(e){let t=`${this.baseUrl}/api/v1/auth/client/session`;try{let i={"Content-Type":"application/json",Accept:"application/json","x-api-key":this.apiKey};if(this.clientId){i["x-sophi-client-id"]=this.clientId;let g=this.storedUserId||null,l=this.userId||null;g!==l&&l&&(await this.mergeUser({userId:l}),this.storedUserId=l);}let r=await fetch(t,{method:"POST",headers:i,body:JSON.stringify({externalUserId:this.userId,metadata:e.metadata})});if(!r.ok){let g=await r.text();throw new Error(`Failed to create session: ${r.status} ${r.statusText}. ${g}`)}let n=await r.json();if(!n.success)throw new Error(`Session creation failed: ${n.message||"Unknown error"}`);if(!n.data)throw new Error("Session data is missing from response");return this.persistClientInfo(n.data.clientId,this.userId),n.data}catch(i){throw i instanceof Error?i:new Error(`Unexpected error during session creation: ${String(i)}`)}}validateSessionData(e){return !!(e.accessToken&&e.sessionId&&e.clientId&&e.companyCode&&e.expiresAt)}async mergeUser(e){let t=`${this.baseUrl}/api/v1/auth/client/info`;try{if(!this.clientId)throw new Error("Client ID is not set. Please create a session first.");let i=await fetch(t,{method:"PATCH",headers:{"Content-Type":"application/json",Accept:"application/json","x-api-key":this.apiKey,"x-sophi-client-id":this.clientId},body:JSON.stringify({externalUserId:e.userId})});if(!i.ok){let n=await i.text();throw new Error(`Failed to merge user: ${i.status} ${i.statusText}. ${n}`)}let r=await i.json();if(!r.success)throw new Error(`User merge failed: ${r.message||"Unknown error"}`);if(!r.data)throw new Error("Merge data is missing from response");return r.data}catch(i){throw i instanceof Error?i:new Error(`Unexpected error during user merge: ${String(i)}`)}}persistClientInfo(e,t){this.clientId=e,this.userId=t,this.storedUserId=t||null,d.canUseCookies()&&(this.cookieManager.set("clientId",e),this.cookieManager.set("externalUserId",t||""));}restoreSessionFromCookies(){if(!d.canUseCookies())return;let e=this.cookieManager.get("externalUserId"),t=this.cookieManager.get("clientId");if(this.storedUserId=e||null,!t)return;let i=e||null,r=this.userId||null;if(i===r){this.clientId=t,!this.userId&&e&&(this.userId=e);return}if(i&&!r){this.clearStoredClientInfo();return}if(i&&r&&i!==r){this.clearStoredClientInfo();return}if(!i&&r){this.clientId=t;return}}clearStoredClientInfo(){this.clientId=null,this.storedUserId=null,d.canUseCookies()&&this.cookieManager.clearAll();}};var m={WIDTH:"100%",HEIGHT:"600px"},p={test:"http://10.0.2.127:80",production:"https://api.usesophi.com"},I={CREATE_SESSION:"/api/v1/auth/client/session"},a={MISSING_API_KEY:"API key is required and must be a string",MISSING_CLIENT_ID:"Client ID is required and must be a string",MISSING_USER_ID:"User ID is required and must be a string",MISSING_CONTAINER:"Container is required",MISSING_IFRAME_URL:"Iframe URL is required and must be a string",INVALID_IFRAME_URL:"Invalid iframe URL",INVALID_ENVIRONMENT:"Invalid environment. Must be 'dev', 'test', 'staging', or 'production'",CONTAINER_NOT_FOUND:"Container element not found",NOT_INITIALIZED:"Widget not initialized. Call init() first.",ALREADY_INITIALIZED:"Sophi Widget is already initialized. Call destroy() first to reinitialize.",SESSION_CREATION_FAILED:"Failed to create authentication session",INVALID_SESSION_DATA:"Invalid session data received from API",YOU_NEED_TO_INIT_THE_WIDGET_FIRST:"You need to initialize the widget first. Call init() before calling this method."};var h=class{constructor(){this.config=null;this.iframe=null;this.container=null;this.eventListeners=new Map;this.messageHandler=null;this.isInitialized=false;this.iframeOrigin=null;this.sessionData=null;this.apiService=null;this.apiBaseUrl=null;}async init(e){if(this.isInitialized){console.warn(a.ALREADY_INITIALIZED);return}try{this.validateConfig(e),this.config=this.formatConfig(e),this.apiBaseUrl=p[e.environment||"production"],this.apiService=new c(this.apiBaseUrl,e.apiKey,e.userId),await this.createAuthSession(),this.isInitialized=!0;try{let t=new URL(this.getIframeUrl());this.iframeOrigin=t.origin;}catch{let i=new Error(`${a.INVALID_IFRAME_URL}: ${this.getIframeUrl()}`);throw this.emitError(i),i}if(this.container=this.resolveContainer(e.container),!this.container){let t=new Error(a.CONTAINER_NOT_FOUND);throw this.emitError(t),t}this.createIframe(),this.setupMessageListener(),this.iframe?.addEventListener("load",()=>{setTimeout(()=>{this.sendSessionData(),this.config?.layout&&this.sendLayoutConfig();},100);}),e.onReady&&e.onReady();}catch(t){this.isInitialized=false,this.config=null,this.apiService=null,this.sessionData=null;let i=t instanceof Error?t:new Error(String(t));throw this.emitError(i),i}}sendUserData(e){if(!this.isInitialized||!this.iframe){console.warn("Widget not initialized. Call init() first.");return}let t={type:o.USER_DATA,data:e};this.postMessage(t);}sendToggleHistory(){if(!this.isInitialized||!this.iframe){console.warn("Widget not initialized. Call init() first.");return}let e={type:o.TOGGLE_HISTORY};this.postMessage(e);}updateUserCart(e){if(!this.isInitialized||!this.iframe){console.warn("Widget not initialized. Call init() first.");return}let t={type:o.UPDATE_USER_CART,data:e};this.postMessage(t);}onDestroy(){if(!this.isInitialized||!this.iframe){console.warn("Widget not initialized. Call init() first.");return}let e={type:o.DESTROY};this.postMessage(e);}on(e,t){this.eventListeners.has(e)||this.eventListeners.set(e,new Set),this.eventListeners.get(e).add(t);}off(e,t){let i=this.eventListeners.get(e);i&&i.delete(t);}show(){this.iframe&&(this.iframe.style.display="block");}hide(){this.iframe&&(this.iframe.style.display="none");}async destroy(){this.onDestroy(),await new Promise(e=>setTimeout(e,300)),this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.iframe&&this.iframe.parentNode&&(this.iframe.parentNode.removeChild(this.iframe),this.iframe=null),this.eventListeners.clear(),this.config=null,this.container=null,this.isInitialized=false,this.iframeOrigin=null,this.sessionData=null,this.apiService=null;}isReady(){return this.isInitialized&&this.iframe!==null}validateConfig(e){if(!e.apiKey||typeof e.apiKey!="string")throw new Error(a.MISSING_API_KEY);if(e.userId&&typeof e.userId!="string")throw new Error(a.MISSING_USER_ID);if(!e.container)throw new Error(a.MISSING_CONTAINER)}formatConfig(e){return {...e,userId:e.userId||void 0}}async createAuthSession(){if(!this.config||!this.apiService)throw new Error(a.NOT_INITIALIZED);try{if(this.sessionData=await this.apiService.createSession({externalUserId:this.config.userId,metadata:this.config.metadata}),!this.apiService.validateSessionData(this.sessionData))throw new Error(a.INVALID_SESSION_DATA)}catch(e){throw e instanceof Error?e:new Error(a.SESSION_CREATION_FAILED)}}sendSessionData(){if(!this.sessionData){console.warn("Session data not available");return}let e={type:o.SESSION_DATA,data:{...this.sessionData,userName:this.config?.userName}};this.postMessage(e);}sendLayoutConfig(){if(!this.config?.layout)return;let e={type:o.LAYOUT_CONFIG,data:this.config.layout};this.postMessage(e);}resolveContainer(e){return typeof e=="string"?document.querySelector(e):e}createIframe(){if(!this.container||!this.config)return;let e=document.createElement("iframe");e.src=this.buildIframeUrl(),e.style.width=this.config.width||"100%",e.style.height=this.config.height||"600px",e.style.border="none",e.title="Sophi AI Widget",e.allow="microphone; camera",this.container.appendChild(e),this.iframe=e;}buildIframeUrl(){return this.config?new URL(this.getIframeUrl()).toString():""}getIframeUrl(){return this.config?this.config.environment==="production"?"https://app.usesophi.com":"http://localhost:5173":""}setupMessageListener(){this.messageHandler=e=>{if(this.iframeOrigin&&e.origin!==this.iframeOrigin){console.warn(`Received message from untrusted origin: ${e.origin}`);return}try{let t=typeof e.data=="string"?JSON.parse(e.data):e.data;this.handleIncomingMessage(t);}catch(t){console.error("Error processing message from iframe:",t);}},window.addEventListener("message",this.messageHandler);}handleIncomingMessage(e){switch(e.type){case "add_to_cart":this.isValidAddToCartMessage(e.data)?this.emit("add_to_cart",e.data):(console.error("Invalid add_to_cart message structure:",e.data),this.emitError(new Error("Invalid add_to_cart message format")));break;case "send_to_checkout":this.emit("send_to_checkout",void 0);break;case "ready":this.emit("ready",void 0);break;case "error":let t=e.data instanceof Error?e.data:new Error(String(e.data));this.emitError(t);break;default:console.warn("Unknown message type:",e);}}isValidAddToCartMessage(e){if(!e||typeof e!="object")return false;let t=e;return Array.isArray(t.products)?t.products.every(i=>{if(!i||typeof i!="object")return false;let r=i;return !(typeof r.productId!="string"||r.variantId!==void 0&&typeof r.variantId!="string")}):false}postMessage(e){if(!this.iframe||!this.iframe.contentWindow||!this.iframeOrigin){console.warn("Cannot send message: iframe not ready");return}try{this.iframe.contentWindow.postMessage(e,this.iframeOrigin);}catch(t){console.error("Error sending message to iframe:",t),this.emitError(t instanceof Error?t:new Error(String(t)));}}emit(e,t){let i=this.eventListeners.get(e);i&&i.forEach(r=>{try{r(t);}catch(n){console.error(`Error in ${e} event handler:`,n);}});}emitError(e){this.emit("error",e),this.config?.onError&&this.config.onError(e);}};
|
|
2
|
+
exports.API_BASE_URLS=p;exports.API_ENDPOINTS=I;exports.ApiService=c;exports.DEFAULT_CONFIG=m;exports.ERROR_MESSAGES=a;exports.IncomingMessageTypes=f;exports.OutgoingMessageTypes=o;exports.SophiWidget=h;//# sourceMappingURL=index.cjs.map
|
|
3
3
|
//# sourceMappingURL=index.cjs.map
|