@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 CHANGED
@@ -3,33 +3,13 @@
3
3
  [![npm version](https://img.shields.io/npm/v/sophi-web-sdk.svg)](https://www.npmjs.com/package/sophi-web-sdk)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- A framework-agnostic, TypeScript-first SDK for embedding the Sophi AI widget into any web application. Works seamlessly with vanilla JavaScript, React, Vue, Svelte, Angular, Next.js, and more.
6
+ TypeScript-first SDK for embedding the Sophi widget in web applications.
7
7
 
8
- ## Features
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
- - API URLs themselves aren't secrets - they're just endpoints
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
- Think of API URLs like a street address - it's public information. The security is the lock on the door (your API authentication).
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
- ### Vanilla JavaScript
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", // From parent app
64
- clientId: "your-client-id", // From parent app
65
- userId: "user-12345", // Current user ID
28
+ apiKey: "your-api-key",
29
+ clientId: "your-client-id",
30
+ userId: "user-12345",
66
31
  container: "#sophi-widget",
67
- environment: "production", // API URL determined automatically
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("Products to add:", data.products);
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
- **Authentication Flow:**
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
- See [AUTHENTICATION.md](./AUTHENTICATION.md) for detailed authentication documentation.
277
-
278
- #### `sendUserData(data: UserData): void`
279
-
280
- Send user data to the widget via postMessage.
281
-
282
- ```typescript
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
- Check out the `examples/` directory for complete working examples:
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 © Sophi
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=g;exports.API_ENDPOINTS=I;exports.ApiService=c;exports.DEFAULT_CONFIG=f;exports.ERROR_MESSAGES=a;exports.IncomingMessageTypes=m;exports.OutgoingMessageTypes=d;exports.SophiWidget=h;//# sourceMappingURL=index.cjs.map
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