@usesophi/sophi-web-sdk 0.1.0-beta.1

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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sophi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,487 @@
1
+ # Sophi Web SDK
2
+
3
+ [![npm version](https://img.shields.io/npm/v/sophi-web-sdk.svg)](https://www.npmjs.com/package/sophi-web-sdk)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
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.
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:
26
+
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
31
+
32
+ Think of API URLs like a street address - it's public information. The security is the lock on the door (your API authentication).
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ npm install sophi-web-sdk
38
+ ```
39
+
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
+ ## Quick Start
53
+
54
+ ### Vanilla JavaScript
55
+
56
+ ```javascript
57
+ import { SophiWidget } from "sophi-web-sdk";
58
+
59
+ const widget = new SophiWidget();
60
+
61
+ // Initialize with authentication (async)
62
+ 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
66
+ iframeUrl: "https://widget.sophi.ai",
67
+ container: "#sophi-widget",
68
+ environment: "production", // API URL determined automatically
69
+ metadata: {
70
+ // Optional metadata
71
+ theme: "dark",
72
+ plan: "premium",
73
+ },
74
+ onReady: () => console.log("Widget ready!"),
75
+ });
76
+
77
+ // Listen for events
78
+ widget.on("add_to_cart", (data) => {
79
+ console.log("Products to add:", data.products);
80
+ // Handle cart logic here
81
+ });
82
+ ```
83
+
84
+ > **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.
85
+
86
+ ### React
87
+
88
+ ```tsx
89
+ import { useEffect, useRef } from "react";
90
+ import { SophiWidget } from "sophi-web-sdk";
91
+
92
+ function MyComponent() {
93
+ const widgetRef = useRef<SophiWidget | null>(null);
94
+
95
+ useEffect(() => {
96
+ const widget = new SophiWidget();
97
+ widgetRef.current = widget;
98
+
99
+ // Async initialization
100
+ const initWidget = async () => {
101
+ try {
102
+ await widget.init({
103
+ apiKey: "your-api-key",
104
+ clientId: "your-client-id",
105
+ userId: "user-12345",
106
+ container: "#sophi-widget",
107
+ iframeUrl: "https://widget.sophi.ai",
108
+ environment: "production",
109
+ metadata: { theme: "dark" },
110
+ });
111
+
112
+ widget.on("add_to_cart", (data) => {
113
+ // Handle add to cart
114
+ });
115
+ } catch (error) {
116
+ console.error("Failed to initialize widget:", error);
117
+ }
118
+ };
119
+
120
+ initWidget();
121
+
122
+ return () => widget.destroy();
123
+ }, []);
124
+
125
+ return <div id="sophi-widget" />;
126
+ }
127
+ ```
128
+
129
+ See `examples/react-example/` for a complete React example with custom hooks.
130
+
131
+ ### Vue 3
132
+
133
+ ```vue
134
+ <template>
135
+ <div id="sophi-widget"></div>
136
+ </template>
137
+
138
+ <script setup>
139
+ import { onMounted, onUnmounted } from "vue";
140
+ import { SophiWidget } from "sophi-web-sdk";
141
+
142
+ let widget = null;
143
+
144
+ onMounted(() => {
145
+ widget = new SophiWidget();
146
+
147
+ widget.init({
148
+ apiKey: "your-api-key",
149
+ container: "#sophi-widget",
150
+ iframeUrl: "https://widget.sophi.ai",
151
+ });
152
+
153
+ widget.on("add_to_cart", (data) => {
154
+ // Handle add to cart
155
+ });
156
+ });
157
+
158
+ onUnmounted(() => {
159
+ if (widget) {
160
+ widget.destroy();
161
+ }
162
+ });
163
+ </script>
164
+ ```
165
+
166
+ ### Svelte
167
+
168
+ ```svelte
169
+ <script>
170
+ import { onMount, onDestroy } from 'svelte';
171
+ import { SophiWidget } from 'sophi-web-sdk';
172
+
173
+ let widget;
174
+
175
+ onMount(() => {
176
+ widget = new SophiWidget();
177
+
178
+ widget.init({
179
+ apiKey: 'your-api-key',
180
+ container: '#sophi-widget',
181
+ iframeUrl: 'https://widget.sophi.ai',
182
+ });
183
+
184
+ widget.on('add_to_cart', (data) => {
185
+ // Handle add to cart
186
+ });
187
+ });
188
+
189
+ onDestroy(() => {
190
+ if (widget) {
191
+ widget.destroy();
192
+ }
193
+ });
194
+ </script>
195
+
196
+ <div id="sophi-widget"></div>
197
+ ```
198
+
199
+ ### Angular
200
+
201
+ ```typescript
202
+ import { Component, OnInit, OnDestroy } from "@angular/core";
203
+ import { SophiWidget } from "sophi-web-sdk";
204
+
205
+ @Component({
206
+ selector: "app-widget",
207
+ template: '<div id="sophi-widget"></div>',
208
+ })
209
+ export class WidgetComponent implements OnInit, OnDestroy {
210
+ private widget: SophiWidget | null = null;
211
+
212
+ ngOnInit() {
213
+ this.widget = new SophiWidget();
214
+
215
+ this.widget.init({
216
+ apiKey: "your-api-key",
217
+ container: "#sophi-widget",
218
+ iframeUrl: "https://widget.sophi.ai",
219
+ });
220
+
221
+ this.widget.on("add_to_cart", (data) => {
222
+ // Handle add to cart
223
+ });
224
+ }
225
+
226
+ ngOnDestroy() {
227
+ if (this.widget) {
228
+ this.widget.destroy();
229
+ }
230
+ }
231
+ }
232
+ ```
233
+
234
+ ## API Reference
235
+
236
+ ### `SophiWidget`
237
+
238
+ The main class for interacting with the Sophi widget.
239
+
240
+ #### `init(config: SophiConfig): Promise<void>`
241
+
242
+ Initialize the widget with configuration. This method is now **async** and handles authentication automatically.
243
+
244
+ ```typescript
245
+ await widget.init({
246
+ // Required fields
247
+ apiKey: "your-api-key", // Your API key (x-api-key header)
248
+ clientId: "your-client-id", // Client ID (x-sophi-client-id header)
249
+ userId: "user-12345", // External user ID from your app
250
+ container: "#sophi-widget", // CSS selector or HTMLElement
251
+ iframeUrl: "https://widget.sophi.ai", // Widget iframe URL
252
+
253
+ // Optional fields
254
+ environment: "production", // 'test' | 'staging' | 'production' (determines API URL)
255
+ metadata: {
256
+ // Custom metadata for the session
257
+ theme: "dark",
258
+ plan: "premium",
259
+ },
260
+ width: "100%", // Widget width (default: '100%')
261
+ height: "600px", // Widget height (default: '600px')
262
+ onReady: () => {}, // Callback when widget is ready
263
+ onError: (error) => {}, // Callback for errors
264
+ });
265
+ ```
266
+
267
+ **Authentication Flow:**
268
+
269
+ 1. SDK validates configuration
270
+ 2. SDK determines API URL based on `environment` setting
271
+ 3. SDK calls authentication API with `apiKey`, `clientId`, and `userId`
272
+ 4. API returns session data (accessToken, sessionId, etc.)
273
+ 5. SDK creates iframe and sends session data via postMessage
274
+ 6. Widget is ready to use
275
+
276
+ **Environment to API URL Mapping:**
277
+
278
+ - `test`: Test API URL (for development/testing)
279
+ - `staging`: Staging API URL
280
+ - `production`: Production API URL (default)
281
+
282
+ See [AUTHENTICATION.md](./AUTHENTICATION.md) for detailed authentication documentation.
283
+
284
+ #### `sendUserData(data: UserData): void`
285
+
286
+ Send user data to the widget via postMessage.
287
+
288
+ ```typescript
289
+ widget.sendUserData({
290
+ userId: "user-123",
291
+ email: "user@example.com",
292
+ name: "John Doe",
293
+ // Add any custom fields
294
+ customField: "value",
295
+ });
296
+ ```
297
+
298
+ #### `on<K extends EventName>(event: K, handler: EventHandler<SophiEventMap[K]>): void`
299
+
300
+ Register an event listener.
301
+
302
+ ```typescript
303
+ // Listen for add_to_cart events
304
+ widget.on("add_to_cart", (data) => {
305
+ console.log("Products:", data.products);
306
+ });
307
+
308
+ // Listen for ready events
309
+ widget.on("ready", () => {
310
+ console.log("Widget is ready");
311
+ });
312
+
313
+ // Listen for error events
314
+ widget.on("error", (error) => {
315
+ console.error("Widget error:", error);
316
+ });
317
+ ```
318
+
319
+ #### `off<K extends EventName>(event: K, handler: EventHandler<SophiEventMap[K]>): void`
320
+
321
+ Unregister an event listener.
322
+
323
+ ```typescript
324
+ const handler = (data) => console.log(data);
325
+ widget.on("add_to_cart", handler);
326
+ widget.off("add_to_cart", handler);
327
+ ```
328
+
329
+ #### `show(): void`
330
+
331
+ Show the widget (sets display to 'block').
332
+
333
+ ```typescript
334
+ widget.show();
335
+ ```
336
+
337
+ #### `hide(): void`
338
+
339
+ Hide the widget (sets display to 'none').
340
+
341
+ ```typescript
342
+ widget.hide();
343
+ ```
344
+
345
+ #### `destroy(): void`
346
+
347
+ Destroy the widget and cleanup all resources.
348
+
349
+ ```typescript
350
+ widget.destroy();
351
+ ```
352
+
353
+ #### `isReady(): boolean`
354
+
355
+ Check if the widget is initialized and ready.
356
+
357
+ ```typescript
358
+ if (widget.isReady()) {
359
+ widget.sendUserData({ userId: "123" });
360
+ }
361
+ ```
362
+
363
+ ## TypeScript Support
364
+
365
+ The SDK is written in TypeScript and includes full type definitions.
366
+
367
+ ```typescript
368
+ import { SophiWidget, SophiConfig, AddToCartEvent, UserData } from "sophi-web-sdk";
369
+
370
+ const config: SophiConfig = {
371
+ apiKey: "your-api-key",
372
+ container: "#widget",
373
+ iframeUrl: "https://widget.sophi.ai",
374
+ };
375
+
376
+ const widget = new SophiWidget();
377
+ widget.init(config);
378
+
379
+ widget.on("add_to_cart", (data: AddToCartEvent) => {
380
+ data.products.forEach((product) => {
381
+ console.log(product.productId, product.variantId);
382
+ });
383
+ });
384
+ ```
385
+
386
+ ## Events
387
+
388
+ ### `add_to_cart`
389
+
390
+ Fired when the widget requests to add products to the cart.
391
+
392
+ ```typescript
393
+ interface AddToCartEvent {
394
+ products: Array<{
395
+ productId: string;
396
+ variantId?: string;
397
+ }>;
398
+ }
399
+
400
+ widget.on("add_to_cart", (data: AddToCartEvent) => {
401
+ // Handle cart logic
402
+ // Show toast notifications
403
+ // Update cart UI
404
+ });
405
+ ```
406
+
407
+ ### `ready`
408
+
409
+ Fired when the widget iframe is ready.
410
+
411
+ ```typescript
412
+ widget.on("ready", () => {
413
+ console.log("Widget is ready");
414
+ });
415
+ ```
416
+
417
+ ### `error`
418
+
419
+ Fired when an error occurs.
420
+
421
+ ```typescript
422
+ widget.on("error", (error: Error) => {
423
+ console.error("Error:", error.message);
424
+ });
425
+ ```
426
+
427
+ ## Security
428
+
429
+ The SDK includes comprehensive security features:
430
+
431
+ - **Authentication**: Automatic session creation with JWT tokens
432
+ - **Origin Validation**: All postMessage events are validated against the iframe origin
433
+ - **Message Validation**: Incoming messages are validated for correct structure
434
+ - **Type Safety**: TypeScript ensures type-safe message handling
435
+ - **Secure Headers**: API key and client ID sent via secure headers
436
+ - **Token Expiration**: Session tokens include expiration timestamps
437
+
438
+ See [AUTHENTICATION.md](./AUTHENTICATION.md) for security best practices.
439
+
440
+ ## Documentation
441
+
442
+ - **[AUTHENTICATION.md](./AUTHENTICATION.md)** - Detailed authentication flow and API documentation
443
+ - **[FILE_STRUCTURE.md](./FILE_STRUCTURE.md)** - Project structure and architecture
444
+ - **[GETTING_STARTED.md](./GETTING_STARTED.md)** - Complete getting started guide
445
+ - **[QUICK_START_TESTING.md](./QUICK_START_TESTING.md)** - Quick testing guide
446
+ - **[TESTING.md](./TESTING.md)** - Comprehensive testing documentation
447
+
448
+ ## Examples
449
+
450
+ Check out the `examples/` directory for complete working examples:
451
+
452
+ - **`examples/vanilla-html/`** - Pure HTML/JS example with no build step
453
+ - **`examples/react-example/`** - React example with custom hooks
454
+
455
+ To run the examples:
456
+
457
+ ```bash
458
+ # Vanilla HTML example
459
+ cd examples/vanilla-html
460
+ # Open index.html in your browser
461
+
462
+ # React example
463
+ cd examples/react-example
464
+ npm install
465
+ npm run dev
466
+ ```
467
+
468
+ Both examples include the new authentication flow with all required fields.
469
+
470
+ ## Browser Support
471
+
472
+ - Chrome (latest)
473
+ - Firefox (latest)
474
+ - Safari (latest)
475
+ - Edge (latest)
476
+
477
+ ## Contributing
478
+
479
+ Contributions are welcome! Please feel free to submit a Pull Request.
480
+
481
+ ## License
482
+
483
+ MIT © Sophi
484
+
485
+ ## Support
486
+
487
+ For issues and questions, please open an issue on GitHub or contact support@sophi.ai.
package/dist/index.cjs ADDED
@@ -0,0 +1,3 @@
1
+ 'use strict';var p={ADD_TO_CART:"add_to_cart",READY:"ready",ERROR:"error",SEND_TO_CHECKOUT:"send_to_checkout"},a={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 o=class{constructor(e,r,t){this.baseUrl=e,this.apiKey=r,this.clientId=t;}async createSession(e){let r=`${this.baseUrl}/api/v1/auth/client/session`;try{let t=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json","x-api-key":this.apiKey,"x-sophi-client-id":this.clientId},body:JSON.stringify(e)});if(!t.ok){let s=await t.text();throw new Error(`Failed to create session: ${t.status} ${t.statusText}. ${s}`)}let i=await t.json();if(!i.success)throw new Error(`Session creation failed: ${i.message||"Unknown error"}`);if(!i.data)throw new Error("Session data is missing from response");return i.data}catch(t){throw t instanceof Error?t:new Error(`Unexpected error during session creation: ${String(t)}`)}}validateSessionData(e){return !!(e.accessToken&&e.sessionId&&e.clientId&&e.companyCode&&e.expiresAt)}async mergeUser(e){let r=`${this.baseUrl}/api/v1/auth/client/info`;try{let t=await fetch(r,{method:"PATCH",headers:{"Content-Type":"application/json",Accept:"application/json","x-api-key":this.apiKey,"x-sophi-client-id":e.guestId},body:JSON.stringify({externalUserId:e.userId})});if(!t.ok){let s=await t.text();throw new Error(`Failed to merge user: ${t.status} ${t.statusText}. ${s}`)}let i=await t.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(t){throw t instanceof Error?t:new Error(`Unexpected error during user merge: ${String(t)}`)}}};var h={WIDTH:"100%",HEIGHT:"600px"},d="http://10.0.2.127:80",u={CREATE_SESSION:"/api/v1/auth/client/session"},n={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"};var l=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;}async init(e){if(this.isInitialized){console.warn(n.ALREADY_INITIALIZED);return}try{this.validateConfig(e),this.config=e;let r=d;this.apiService=new o(r,e.apiKey,e.clientId),await this.createAuthSession(),this.isInitialized=!0;try{let t=new URL(e.iframeUrl);this.iframeOrigin=t.origin;}catch{let i=new Error(`${n.INVALID_IFRAME_URL}: ${e.iframeUrl}`);throw this.emitError(i),i}if(this.container=this.resolveContainer(e.container),!this.container){let t=new Error(n.CONTAINER_NOT_FOUND);throw this.emitError(t),t}this.createIframe(),this.setupMessageListener(),this.iframe?.addEventListener("load",()=>{console.log("Iframe loaded, sending session data..."),setTimeout(()=>{this.sendSessionData();},100);}),e.onReady&&e.onReady();}catch(r){this.isInitialized=false,this.config=null,this.apiService=null,this.sessionData=null;let t=r instanceof Error?r:new Error(String(r));throw this.emitError(t),t}}sendUserData(e){if(!this.isInitialized||!this.iframe){console.warn("Widget not initialized. Call init() first.");return}let r={type:a.USER_DATA,data:e};this.postMessage(r);}updateUserCart(e){if(!this.isInitialized||!this.iframe){console.warn("Widget not initialized. Call init() first.");return}let r={type:a.UPDATE_USER_CART,data:e};this.postMessage(r);}async mergeUser(e,r,t){try{let i;if(this.apiService)i=this.apiService;else {if(!t)throw new Error("API key is required when widget is not initialized");let c=d;i=new o(c,t,e);}let s=await i.mergeUser({guestId:e,userId:r});if(this.isInitialized&&this.iframe){let c={type:a.CLIENT_ID_UPDATED,data:{clientId:s.clientId,externalUserId:s.externalUserId}};this.postMessage(c);}return s}catch(i){let s=i instanceof Error?i:new Error(String(i));throw this.isInitialized&&this.emitError(s),s}}static async mergeUser(e,r,t){let i=d;return await new o(i,t,e).mergeUser({guestId:e,userId:r})}onDestroy(){if(!this.isInitialized||!this.iframe){console.warn("Widget not initialized. Call init() first.");return}let e={type:a.DESTROY};this.postMessage(e);}on(e,r){this.eventListeners.has(e)||this.eventListeners.set(e,new Set),this.eventListeners.get(e).add(r);}off(e,r){let t=this.eventListeners.get(e);t&&t.delete(r);}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(n.MISSING_API_KEY);if(!e.clientId||typeof e.clientId!="string")throw new Error(n.MISSING_CLIENT_ID);if(!e.clientId||typeof e.clientId!="string")throw new Error(n.MISSING_USER_ID);if(!e.container)throw new Error(n.MISSING_CONTAINER);if(!e.iframeUrl||typeof e.iframeUrl!="string")throw new Error(n.MISSING_IFRAME_URL)}async createAuthSession(){if(!this.config||!this.apiService)throw new Error(n.NOT_INITIALIZED);try{if(this.sessionData=await this.apiService.createSession({externalUserId:this.config.clientId,metadata:this.config.metadata}),!this.apiService.validateSessionData(this.sessionData))throw new Error(n.INVALID_SESSION_DATA)}catch(e){throw e instanceof Error?e:new Error(n.SESSION_CREATION_FAILED)}}sendSessionData(){if(!this.sessionData){console.warn("Session data not available");return}let e={type:a.SESSION_DATA,data:this.sessionData};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.config.iframeUrl).toString():""}setupMessageListener(){this.messageHandler=e=>{if(this.iframeOrigin&&e.origin!==this.iframeOrigin){console.warn(`Received message from untrusted origin: ${e.origin}`);return}try{let r=typeof e.data=="string"?JSON.parse(e.data):e.data;this.handleIncomingMessage(r);}catch(r){console.error("Error processing message from iframe:",r);}},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 r=e.data instanceof Error?e.data:new Error(String(e.data));this.emitError(r);break;default:console.warn("Unknown message type:",e);}}isValidAddToCartMessage(e){if(!e||typeof e!="object")return false;let r=e;return Array.isArray(r.products)?r.products.every(t=>{if(!t||typeof t!="object")return false;let i=t;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{console.log("Posting message to iframe:",e.type,"to origin:",this.iframeOrigin),this.iframe.contentWindow.postMessage(e,this.iframeOrigin),console.log("Message posted successfully");}catch(r){console.error("Error sending message to iframe:",r),this.emitError(r instanceof Error?r:new Error(String(r)));}}emit(e,r){let t=this.eventListeners.get(e);t&&t.forEach(i=>{try{i(r);}catch(s){console.error(`Error in ${e} event handler:`,s);}});}emitError(e){this.emit("error",e),this.config?.onError&&this.config.onError(e);}};
2
+ exports.API_BASE_URLS=d;exports.API_ENDPOINTS=u;exports.ApiService=o;exports.DEFAULT_CONFIG=h;exports.ERROR_MESSAGES=n;exports.IncomingMessageTypes=p;exports.OutgoingMessageTypes=a;exports.SophiWidget=l;//# sourceMappingURL=index.cjs.map
3
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/services/api.service.ts","../src/constants/config.ts","../src/SophiWidget.ts"],"names":["IncomingMessageTypes","OutgoingMessageTypes","ApiService","baseUrl","apiKey","clientId","request","url","response","errorText","data","error","sessionData","DEFAULT_CONFIG","API_BASE_URLS","API_ENDPOINTS","ERROR_MESSAGES","SophiWidget","config","apiUrl","err","message","cart","guestId","userId","apiService","mergeData","event","handler","handlers","resolve","container","iframe","product","prod"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Configuration for initializing the Sophi Widget\n */\nexport interface SophiConfig {\n /**\n * API key for authentication with Sophi services (x-api-key header)\n * This comes from the parent application\n */\n apiKey: string;\n\n /**\n * Client ID for authentication (x-sophi-client-id header)\n * This comes from the parent application\n */\n clientId: string;\n\n /**\n * Container element selector (e.g., '#widget') or HTMLElement where the widget will be mounted\n */\n container: string | HTMLElement;\n\n /**\n * URL of the iframe to embed (e.g., 'https://widget.sophi.ai' or 'http://localhost:5173')\n */\n iframeUrl: string;\n\n /**\n * Optional metadata to send with the session request\n */\n metadata?: Record<string, unknown>;\n\n /**\n * Width of the widget iframe\n * @default '100%'\n */\n width?: string;\n\n /**\n * Height of the widget iframe\n * @default '600px'\n */\n height?: string;\n\n /**\n * Callback fired when the widget is ready\n */\n onReady?: () => void;\n\n /**\n * Callback fired when an error occurs\n */\n onError?: (error: Error) => void;\n}\n\n/**\n * Product information for add to cart events\n */\nexport interface Product {\n /**\n * Unique product identifier\n */\n productId: string;\n\n /**\n * Optional variant identifier\n */\n variantId?: string;\n}\n\n/**\n * Add to cart event data\n */\nexport interface AddToCartEvent {\n /**\n * List of products to add to cart\n */\n products: Product[];\n}\n\n/**\n * User data that can be sent to the widget\n */\nexport interface UserData {\n /**\n * User ID\n */\n userId?: string;\n\n /**\n * User email\n */\n email?: string;\n\n /**\n * User name\n */\n name?: string;\n\n /**\n * Additional custom data\n */\n [key: string]: unknown;\n}\n\nexport interface IUserCartItem {\n productId: string;\n variantId: string;\n quantity?: number;\n}\n\nexport interface IUserCart {\n cardId: string;\n items: IUserCartItem[];\n}\n\n/**\n * Message types that can be received from the iframe\n */\nexport type IncomingMessageType = \"add_to_cart\" | \"send_to_checkout\" | \"ready\" | \"error\";\n\n/**\n * Incoming message type constants for easy access\n * @example\n * widget.on(IncomingMessageTypes.ADD_TO_CART, (data) => {...})\n */\nexport const IncomingMessageTypes = {\n ADD_TO_CART: \"add_to_cart\",\n READY: \"ready\",\n ERROR: \"error\",\n SEND_TO_CHECKOUT: \"send_to_checkout\",\n} as const;\n\n/**\n * Message types that can be sent to the iframe\n */\nexport type OutgoingMessageType = \"user_data\" | \"config\" | \"update_user_cart\" | \"destroy\" | \"session_data\" | \"client_id_updated\";\n\n/**\n * Outgoing message type constants for easy access\n * @example\n * widget.sendMessage({ type: OutgoingMessageTypes.USER_DATA, data: {...} })\n */\nexport const OutgoingMessageTypes = {\n USER_DATA: \"user_data\",\n CONFIG: \"config\",\n UPDATE_USER_CART: \"update_user_cart\",\n DESTROY: \"destroy\",\n SESSION_DATA: \"session_data\",\n CLIENT_ID_UPDATED: \"client_id_updated\",\n} as const;\n\n/**\n * Incoming message structure from iframe\n */\nexport interface IncomingMessage {\n type: IncomingMessageType;\n data?: unknown;\n}\n\n/**\n * Outgoing message structure to iframe\n */\nexport interface OutgoingMessage {\n type: OutgoingMessageType;\n data?: unknown;\n}\n\n/**\n * Event map for type-safe event handling\n */\nexport interface SophiEventMap {\n /**\n * Fired when products should be added to cart\n */\n add_to_cart: AddToCartEvent;\n\n /**\n * Fired when the widget is ready\n */\n ready: void;\n\n /**\n * Fired when an error occurs\n */\n error: Error;\n\n /**\n * Fired when the user should be sent to checkout\n */\n send_to_checkout: void;\n}\n\n/**\n * Event handler type\n */\nexport type EventHandler<T> = (data: T) => void;\n\n/**\n * Event name type\n */\nexport type EventName = keyof SophiEventMap;\n\n/**\n * Authentication session request payload\n */\nexport interface AuthSessionRequest {\n /**\n * External user ID from the parent application\n */\n externalUserId: string;\n\n /**\n * Optional metadata to include with the session\n */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Session data returned from the authentication API\n */\nexport interface SessionData {\n /**\n * JWT access token for authenticating requests\n */\n accessToken: string;\n\n /**\n * Unique session identifier\n */\n sessionId: string;\n\n /**\n * Client ID\n */\n clientId: string;\n\n /**\n * Company code\n */\n companyCode: string;\n\n /**\n * Session expiration timestamp\n */\n expiresAt: string;\n}\n\n/**\n * Authentication session API response\n */\nexport interface AuthSessionResponse {\n /**\n * Whether the request was successful\n */\n success: boolean;\n\n /**\n * Response message\n */\n message: string;\n\n /**\n * Session data\n */\n data?: SessionData;\n}\n\n/**\n * Merge user request payload\n */\nexport interface MergeUserRequest {\n /**\n * The guest client ID to merge from\n */\n guestId: string;\n\n /**\n * The logged-in user ID to merge to\n */\n userId: string;\n}\n\n/**\n * Merge user data returned from the API\n */\nexport interface MergeUserData {\n /**\n * The resulting client ID after merge\n */\n clientId: string;\n\n /**\n * The external user ID\n */\n externalUserId: string;\n}\n\n/**\n * Merge user API response\n */\nexport interface MergeUserResponse {\n /**\n * Whether the request was successful\n */\n success: boolean;\n\n /**\n * Response message\n */\n message: string;\n\n /**\n * Merge result data\n */\n data?: MergeUserData;\n}\n","import type { AuthSessionRequest, AuthSessionResponse, SessionData, MergeUserRequest, MergeUserResponse, MergeUserData } from \"../types\";\n\n/**\n * API Service for Sophi authentication and session management\n */\nexport class ApiService {\n private baseUrl: string;\n private apiKey: string;\n private clientId: string;\n\n constructor(baseUrl: string, apiKey: string, clientId: string) {\n this.baseUrl = baseUrl;\n this.apiKey = apiKey;\n this.clientId = clientId;\n }\n\n /**\n * Create a client session by calling the authentication endpoint\n * @param request - Session request with externalUserId and optional metadata\n * @returns Session data including accessToken, sessionId, etc.\n */\n async createSession(request: AuthSessionRequest): Promise<SessionData> {\n const url = `${this.baseUrl}/api/v1/auth/client/session`;\n\n try {\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Accept\": \"application/json\",\n \"x-api-key\": this.apiKey,\n \"x-sophi-client-id\": this.clientId,\n },\n body: JSON.stringify(request),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `Failed to create session: ${response.status} ${response.statusText}. ${errorText}`\n );\n }\n\n const data: AuthSessionResponse = await response.json();\n\n if (!data.success) {\n throw new Error(`Session creation failed: ${data.message || \"Unknown error\"}`);\n }\n\n if (!data.data) {\n throw new Error(\"Session data is missing from response\");\n }\n\n return data.data;\n } catch (error) {\n if (error instanceof Error) {\n throw error;\n }\n throw new Error(`Unexpected error during session creation: ${String(error)}`);\n }\n }\n\n /**\n * Validate session data\n */\n validateSessionData(sessionData: SessionData): boolean {\n return !!(\n sessionData.accessToken &&\n sessionData.sessionId &&\n sessionData.clientId &&\n sessionData.companyCode &&\n sessionData.expiresAt\n );\n }\n\n /**\n * Merge guest user with logged-in user\n * @param request - Merge request with guestId and userId\n * @returns Merge result data including the resulting clientId\n */\n async mergeUser(request: MergeUserRequest): Promise<MergeUserData> {\n const url = `${this.baseUrl}/api/v1/auth/client/info`;\n\n try {\n const response = await fetch(url, {\n method: \"PATCH\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Accept\": \"application/json\",\n \"x-api-key\": this.apiKey,\n \"x-sophi-client-id\": request.guestId,\n },\n body: JSON.stringify({\n externalUserId: request.userId,\n }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `Failed to merge user: ${response.status} ${response.statusText}. ${errorText}`\n );\n }\n\n const data: MergeUserResponse = await response.json();\n\n if (!data.success) {\n throw new Error(`User merge failed: ${data.message || \"Unknown error\"}`);\n }\n\n if (!data.data) {\n throw new Error(\"Merge data is missing from response\");\n }\n\n return data.data;\n } catch (error) {\n if (error instanceof Error) {\n throw error;\n }\n throw new Error(`Unexpected error during user merge: ${String(error)}`);\n }\n }\n}\n","/**\n * Default configuration constants for Sophi Widget\n */\nexport const DEFAULT_CONFIG = {\n /**\n * Default width of the widget iframe\n */\n WIDTH: \"100%\",\n\n /**\n * Default height of the widget iframe\n */\n HEIGHT: \"600px\",\n} as const;\n\n/**\n * API base URLs for different environments\n *\n * Note: These URLs will be visible in the client-side bundle.\n * This is normal and safe - the real security is in your API key validation.\n *\n * To use different URLs, modify these values before building the SDK.\n *\n * Production API URL - update before building for production\n */\nexport const API_BASE_URLS = \"http://10.0.2.127:80\" as const;\n\n/**\n * API endpoints configuration\n */\nexport const API_ENDPOINTS = {\n /**\n * Session creation endpoint path\n */\n CREATE_SESSION: \"/api/v1/auth/client/session\",\n} as const;\n\n/**\n * Error messages\n */\nexport const ERROR_MESSAGES = {\n MISSING_API_KEY: \"API key is required and must be a string\",\n MISSING_CLIENT_ID: \"Client ID is required and must be a string\",\n MISSING_USER_ID: \"User ID is required and must be a string\",\n MISSING_CONTAINER: \"Container is required\",\n MISSING_IFRAME_URL: \"Iframe URL is required and must be a string\",\n INVALID_IFRAME_URL: \"Invalid iframe URL\",\n INVALID_ENVIRONMENT: \"Invalid environment. Must be 'dev', 'test', 'staging', or 'production'\",\n CONTAINER_NOT_FOUND: \"Container element not found\",\n NOT_INITIALIZED: \"Widget not initialized. Call init() first.\",\n ALREADY_INITIALIZED: \"Sophi Widget is already initialized. Call destroy() first to reinitialize.\",\n SESSION_CREATION_FAILED: \"Failed to create authentication session\",\n INVALID_SESSION_DATA: \"Invalid session data received from API\",\n} as const;\n","import { type SophiConfig, type UserData, type EventHandler, type EventName, type SophiEventMap, type IncomingMessage, type OutgoingMessage, type AddToCartEvent, type IUserCart, type SessionData, type MergeUserRequest, type MergeUserData, OutgoingMessageTypes } from \"./types\";\nimport { ApiService } from \"./services/api.service\";\nimport { DEFAULT_CONFIG, ERROR_MESSAGES, API_BASE_URLS } from \"./constants/config\";\n\n/**\n * Main Sophi Widget SDK class\n * Provides a framework-agnostic way to embed and interact with the Sophi AI widget\n */\nexport class SophiWidget {\n private config: SophiConfig | null = null;\n private iframe: HTMLIFrameElement | null = null;\n private container: HTMLElement | null = null;\n private eventListeners: Map<EventName, Set<EventHandler<any>>> = new Map();\n private messageHandler: ((event: MessageEvent) => void) | null = null;\n private isInitialized = false;\n private iframeOrigin: string | null = null;\n private sessionData: SessionData | null = null;\n private apiService: ApiService | null = null;\n\n /**\n * Initialize the widget with configuration\n * @param config - Widget configuration\n */\n public async init(config: SophiConfig): Promise<void> {\n if (this.isInitialized) {\n console.warn(ERROR_MESSAGES.ALREADY_INITIALIZED);\n return;\n }\n\n try {\n // Validate configuration\n this.validateConfig(config);\n\n this.config = config;\n\n // Determine API URL based on environment\n const apiUrl = API_BASE_URLS;\n\n // Initialize API service\n this.apiService = new ApiService(apiUrl, config.apiKey, config.clientId);\n\n // Create authentication session\n await this.createAuthSession();\n\n this.isInitialized = true;\n\n // Extract origin from iframe URL for security\n try {\n const url = new URL(config.iframeUrl);\n this.iframeOrigin = url.origin;\n } catch (error) {\n const err = new Error(`${ERROR_MESSAGES.INVALID_IFRAME_URL}: ${config.iframeUrl}`);\n this.emitError(err);\n throw err;\n }\n\n // Resolve container element\n this.container = this.resolveContainer(config.container);\n\n if (!this.container) {\n const err = new Error(ERROR_MESSAGES.CONTAINER_NOT_FOUND);\n this.emitError(err);\n throw err;\n }\n\n // Create and mount iframe\n this.createIframe();\n\n // Setup postMessage listener\n this.setupMessageListener();\n\n // Send session data to iframe after it's loaded\n this.iframe?.addEventListener(\"load\", () => {\n console.log(\"Iframe loaded, sending session data...\");\n // Small delay to ensure iframe is fully ready\n setTimeout(() => {\n this.sendSessionData();\n }, 100);\n });\n\n // Call onReady callback if provided\n if (config.onReady) {\n config.onReady();\n }\n } catch (error) {\n // Reset state on error\n this.isInitialized = false;\n this.config = null;\n this.apiService = null;\n this.sessionData = null;\n\n const err = error instanceof Error ? error : new Error(String(error));\n this.emitError(err);\n throw err;\n }\n }\n\n /**\n * Send user data to the widget\n * @param data - User data object\n */\n public sendUserData(data: UserData): void {\n if (!this.isInitialized || !this.iframe) {\n console.warn(\"Widget not initialized. Call init() first.\");\n return;\n }\n\n const message: OutgoingMessage = {\n type: OutgoingMessageTypes.USER_DATA,\n data,\n };\n\n this.postMessage(message);\n }\n\n public updateUserCart(cart: IUserCart): void {\n if (!this.isInitialized || !this.iframe) {\n console.warn(\"Widget not initialized. Call init() first.\");\n return;\n }\n\n const message: OutgoingMessage = {\n type: OutgoingMessageTypes.UPDATE_USER_CART,\n data: cart,\n };\n\n this.postMessage(message);\n }\n\n /**\n * Merge guest user with logged-in user\n * This can be called anytime, even before widget initialization\n * @param guestId - The guest client ID (previous clientId)\n * @param userId - The logged-in user ID (new externalUserId)\n * @param apiKey - API key for authentication (required if widget not initialized)\n * @returns Promise with merge result data\n */\n public async mergeUser(guestId: string, userId: string, apiKey?: string): Promise<MergeUserData> {\n try {\n let apiService: ApiService;\n\n // Use existing API service if widget is initialized, otherwise create a temporary one\n if (this.apiService) {\n apiService = this.apiService;\n } else {\n // Widget not initialized - create temporary API service\n if (!apiKey) {\n throw new Error(\"API key is required when widget is not initialized\");\n }\n const apiUrl = API_BASE_URLS;\n apiService = new ApiService(apiUrl, apiKey, guestId);\n }\n\n const mergeData = await apiService.mergeUser({ guestId, userId });\n\n // If iframe exists, send the updated clientId to it\n // This allows the iframe to update its own stores automatically\n if (this.isInitialized && this.iframe) {\n const message: OutgoingMessage = {\n type: OutgoingMessageTypes.CLIENT_ID_UPDATED,\n data: {\n clientId: mergeData.clientId,\n externalUserId: mergeData.externalUserId,\n },\n };\n this.postMessage(message);\n }\n\n return mergeData;\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n // Only emit error if widget is initialized\n if (this.isInitialized) {\n this.emitError(err);\n }\n throw err;\n }\n }\n\n /**\n * Static method to merge guest user with logged-in user\n * Use this when you don't have a widget instance or want to merge before initialization\n * @param guestId - The guest client ID (previous clientId)\n * @param userId - The logged-in user ID (new externalUserId)\n * @param apiKey - API key for authentication\n * @returns Promise with merge result data\n */\n public static async mergeUser(guestId: string, userId: string, apiKey: string): Promise<MergeUserData> {\n const apiUrl = API_BASE_URLS;\n const apiService = new ApiService(apiUrl, apiKey, guestId);\n return await apiService.mergeUser({ guestId, userId });\n }\n\n private onDestroy(): void {\n if (!this.isInitialized || !this.iframe) {\n console.warn(\"Widget not initialized. Call init() first.\");\n return;\n }\n\n const message: OutgoingMessage = {\n type: OutgoingMessageTypes.DESTROY,\n };\n\n this.postMessage(message);\n }\n\n /**\n * Register an event listener\n * @param event - Event name\n * @param handler - Event handler function\n */\n public on<K extends EventName>(event: K, handler: EventHandler<SophiEventMap[K]>): void {\n if (!this.eventListeners.has(event)) {\n this.eventListeners.set(event, new Set());\n }\n this.eventListeners.get(event)!.add(handler);\n }\n\n /**\n * Unregister an event listener\n * @param event - Event name\n * @param handler - Event handler function to remove\n */\n public off<K extends EventName>(event: K, handler: EventHandler<SophiEventMap[K]>): void {\n const handlers = this.eventListeners.get(event);\n if (handlers) {\n handlers.delete(handler);\n }\n }\n\n /**\n * Show the widget\n */\n public show(): void {\n if (this.iframe) {\n this.iframe.style.display = \"block\";\n }\n }\n\n /**\n * Hide the widget\n */\n public hide(): void {\n if (this.iframe) {\n this.iframe.style.display = \"none\";\n }\n }\n\n /**\n * Destroy the widget and cleanup resources\n * Waits 300ms after sending destroy message to give iframe time to cleanup\n */\n public async destroy(): Promise<void> {\n this.onDestroy();\n\n // Wait 300ms to give iframe time to process the destroy event and cleanup\n await new Promise((resolve) => setTimeout(resolve, 300));\n\n // Remove message listener\n if (this.messageHandler) {\n window.removeEventListener(\"message\", this.messageHandler);\n this.messageHandler = null;\n }\n\n // Remove iframe\n if (this.iframe && this.iframe.parentNode) {\n this.iframe.parentNode.removeChild(this.iframe);\n this.iframe = null;\n }\n\n // Clear event listeners\n this.eventListeners.clear();\n\n // Reset state\n this.config = null;\n this.container = null;\n this.isInitialized = false;\n this.iframeOrigin = null;\n this.sessionData = null;\n this.apiService = null;\n }\n\n /**\n * Check if widget is initialized\n */\n public isReady(): boolean {\n return this.isInitialized && this.iframe !== null;\n }\n\n /**\n * Validate configuration\n */\n private validateConfig(config: SophiConfig): void {\n if (!config.apiKey || typeof config.apiKey !== \"string\") {\n throw new Error(ERROR_MESSAGES.MISSING_API_KEY);\n }\n\n if (!config.clientId || typeof config.clientId !== \"string\") {\n throw new Error(ERROR_MESSAGES.MISSING_CLIENT_ID);\n }\n\n if (!config.clientId || typeof config.clientId !== \"string\") {\n throw new Error(ERROR_MESSAGES.MISSING_USER_ID);\n }\n\n if (!config.container) {\n throw new Error(ERROR_MESSAGES.MISSING_CONTAINER);\n }\n\n if (!config.iframeUrl || typeof config.iframeUrl !== \"string\") {\n throw new Error(ERROR_MESSAGES.MISSING_IFRAME_URL);\n }\n }\n\n /**\n * Create authentication session with the API\n */\n private async createAuthSession(): Promise<void> {\n if (!this.config || !this.apiService) {\n throw new Error(ERROR_MESSAGES.NOT_INITIALIZED);\n }\n\n try {\n this.sessionData = await this.apiService.createSession({\n externalUserId: this.config.clientId,\n metadata: this.config.metadata,\n });\n\n // Validate session data\n if (!this.apiService.validateSessionData(this.sessionData)) {\n throw new Error(ERROR_MESSAGES.INVALID_SESSION_DATA);\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(ERROR_MESSAGES.SESSION_CREATION_FAILED);\n throw err;\n }\n }\n\n /**\n * Send session data to iframe\n */\n private sendSessionData(): void {\n if (!this.sessionData) {\n console.warn(\"Session data not available\");\n return;\n }\n\n const message: OutgoingMessage = {\n type: OutgoingMessageTypes.SESSION_DATA,\n data: this.sessionData,\n };\n\n this.postMessage(message);\n }\n\n /**\n * Resolve container element from selector or element\n */\n private resolveContainer(container: string | HTMLElement): HTMLElement | null {\n if (typeof container === \"string\") {\n return document.querySelector(container);\n }\n return container;\n }\n\n /**\n * Create and mount the iframe\n */\n private createIframe(): void {\n if (!this.container || !this.config) {\n return;\n }\n\n const iframe = document.createElement(\"iframe\");\n\n // Set iframe attributes\n iframe.src = this.buildIframeUrl();\n iframe.style.width = this.config.width || \"100%\";\n iframe.style.height = this.config.height || \"600px\";\n iframe.style.border = \"none\";\n iframe.title = \"Sophi AI Widget\";\n iframe.allow = \"microphone; camera\";\n\n // Append to container\n this.container.appendChild(iframe);\n this.iframe = iframe;\n }\n\n /**\n * Build iframe URL with query parameters\n */\n private buildIframeUrl(): string {\n if (!this.config) {\n return \"\";\n }\n\n const url = new URL(this.config.iframeUrl);\n\n return url.toString();\n }\n\n /**\n * Setup postMessage listener for iframe communication\n */\n private setupMessageListener(): void {\n this.messageHandler = (event: MessageEvent) => {\n // Validate origin for security\n if (this.iframeOrigin && event.origin !== this.iframeOrigin) {\n console.warn(`Received message from untrusted origin: ${event.origin}`);\n return;\n }\n\n try {\n // Parse message\n const message: IncomingMessage = typeof event.data === \"string\" ? JSON.parse(event.data) : event.data;\n\n // Handle different message types\n this.handleIncomingMessage(message);\n } catch (error) {\n console.error(\"Error processing message from iframe:\", error);\n }\n };\n\n window.addEventListener(\"message\", this.messageHandler);\n }\n\n /**\n * Handle incoming messages from iframe\n */\n private handleIncomingMessage(message: IncomingMessage): void {\n switch (message.type) {\n case \"add_to_cart\":\n if (this.isValidAddToCartMessage(message.data)) {\n this.emit(\"add_to_cart\", message.data);\n } else {\n console.error(\"Invalid add_to_cart message structure:\", message.data);\n this.emitError(new Error(\"Invalid add_to_cart message format\"));\n }\n break;\n\n case \"send_to_checkout\":\n this.emit(\"send_to_checkout\", undefined);\n break;\n case \"ready\":\n this.emit(\"ready\", undefined);\n break;\n\n case \"error\":\n const error = message.data instanceof Error ? message.data : new Error(String(message.data));\n this.emitError(error);\n break;\n\n default:\n console.warn(\"Unknown message type:\", message);\n }\n }\n\n /**\n * Validate add_to_cart message structure\n */\n private isValidAddToCartMessage(data: unknown): data is AddToCartEvent {\n if (!data || typeof data !== \"object\") {\n return false;\n }\n\n const event = data as Record<string, unknown>;\n\n // Check products array exists and is an array\n if (!Array.isArray(event.products)) {\n return false;\n }\n\n // Validate each product in the array\n return event.products.every((product) => {\n if (!product || typeof product !== \"object\") {\n return false;\n }\n const prod = product as Record<string, unknown>;\n\n // productId is required and must be a string\n if (typeof prod.productId !== \"string\") {\n return false;\n }\n\n // variantId is optional, but if present must be a string\n if (prod.variantId !== undefined && typeof prod.variantId !== \"string\") {\n return false;\n }\n\n return true;\n });\n }\n\n /**\n * Post message to iframe\n */\n private postMessage(message: OutgoingMessage): void {\n if (!this.iframe || !this.iframe.contentWindow || !this.iframeOrigin) {\n console.warn(\"Cannot send message: iframe not ready\");\n return;\n }\n\n try {\n console.log(\"Posting message to iframe:\", message.type, \"to origin:\", this.iframeOrigin);\n this.iframe.contentWindow.postMessage(message, this.iframeOrigin);\n console.log(\"Message posted successfully\");\n } catch (error) {\n console.error(\"Error sending message to iframe:\", error);\n this.emitError(error instanceof Error ? error : new Error(String(error)));\n }\n }\n\n /**\n * Emit event to registered listeners\n */\n private emit<K extends EventName>(event: K, data: SophiEventMap[K]): void {\n const handlers = this.eventListeners.get(event);\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(data);\n } catch (error) {\n console.error(`Error in ${event} event handler:`, error);\n }\n });\n }\n }\n\n /**\n * Emit error event\n */\n private emitError(error: Error): void {\n this.emit(\"error\", error);\n\n // Also call onError callback if provided\n if (this.config?.onError) {\n this.config.onError(error);\n }\n }\n}\n"]}