@usesophi/sophi-web-sdk 1.3.3 → 1.3.5-beta.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/dist/index.d.cts CHANGED
@@ -22,6 +22,14 @@ interface SessionDataChatPayload {
22
22
  interface SetLocalePayload {
23
23
  locale: SophiLocale;
24
24
  }
25
+ /**
26
+ * Payload for `set_consent` — tell the iframe to switch its persistence at
27
+ * runtime. `storage: true` promotes the in-memory store to localStorage;
28
+ * `storage: false` demotes back to memory and clears persisted keys.
29
+ */
30
+ interface SetConsentPayload {
31
+ storage: boolean;
32
+ }
25
33
  /**
26
34
  * Layout options sent to the iframe on init so the child app can adjust its layout.
27
35
  */
@@ -85,6 +93,14 @@ interface SophiConfig {
85
93
  * Environment to use for the API
86
94
  */
87
95
  environment?: "test" | "production";
96
+ /**
97
+ * Whether the SDK may persist session data to cookies. Defaults to `true`.
98
+ * Set to `false` to run in anonymous mode: no cookies are written or read,
99
+ * the session lives only in memory, and the user starts fresh on every load.
100
+ * Use {@link SophiWidget.setStorageConsent} to switch to persistent mode at
101
+ * runtime once the user grants cookie consent.
102
+ */
103
+ storageConsent?: boolean;
88
104
  /**
89
105
  * Layout options sent to the iframe on init. The child app receives these via a
90
106
  * postMessage and can show/hide header (and future layout options) accordingly.
@@ -197,7 +213,7 @@ declare const IncomingMessageTypes: {
197
213
  /**
198
214
  * Message types that can be sent to the iframe
199
215
  */
200
- type OutgoingMessageType = "user_data" | "config" | "update_user_cart" | "destroy" | "session_data" | "set_locale" | "client_id_updated" | "layout_config" | "toggle_history" | "send_text_message" | "send_product_reply" | "send_outfit_reply" | "open_product_by_id" | "trigger_product_reply" | "update_favorite";
216
+ type OutgoingMessageType = "user_data" | "config" | "update_user_cart" | "destroy" | "session_data" | "set_locale" | "set_consent" | "client_id_updated" | "layout_config" | "toggle_history" | "send_text_message" | "send_product_reply" | "send_outfit_reply" | "open_product_by_id" | "trigger_product_reply" | "update_favorite";
201
217
  /**
202
218
  * Outgoing message type constants for easy access
203
219
  * @example
@@ -210,6 +226,7 @@ declare const OutgoingMessageTypes: {
210
226
  readonly DESTROY: "destroy";
211
227
  readonly SESSION_DATA: "session_data";
212
228
  readonly SET_LOCALE: "set_locale";
229
+ readonly SET_CONSENT: "set_consent";
213
230
  readonly CLIENT_ID_UPDATED: "client_id_updated";
214
231
  readonly LAYOUT_CONFIG: "layout_config";
215
232
  readonly TOGGLE_HISTORY: "toggle_history";
@@ -568,6 +585,25 @@ declare class SophiWidget {
568
585
  * Use after host-side session refresh or if the child app missed the initial payload.
569
586
  */
570
587
  sendSessionToChat(): void;
588
+ /**
589
+ * Update storage (cookie) consent at runtime without losing the open chat.
590
+ *
591
+ * When granting consent, the current in-memory session is persisted to
592
+ * cookies. For a logged-in user (a userId was supplied at init), the guest
593
+ * session is merged into that user via `/auth/v1/client/update` and a fresh
594
+ * session is opened for the user so the SDK receives their auth data; the new
595
+ * session data is then re-posted to the iframe. The chat iframe is also told
596
+ * to promote its in-memory store to localStorage so the conversation survives
597
+ * the next reload. Withdrawing consent clears cookies and demotes the iframe
598
+ * back to memory-only.
599
+ *
600
+ * @param granted - `true` to enable persistence, `false` to withdraw it.
601
+ */
602
+ setStorageConsent(granted: boolean): Promise<void>;
603
+ /**
604
+ * Tell the chat iframe to switch its persistence strategy at runtime.
605
+ */
606
+ private postSetConsent;
571
607
  /**
572
608
  * Posts `set_locale` so the iframe can switch language without a full `session_data` round trip.
573
609
  *
@@ -672,7 +708,23 @@ declare class ApiService {
672
708
  private userId;
673
709
  private storedUserId;
674
710
  private cookieManager;
675
- constructor(baseUrl: string, apiKey: string, userId?: string);
711
+ /**
712
+ * Whether the SDK is allowed to read/write cookies. When false the SDK runs
713
+ * in anonymous mode: the session lives only in memory and no cookies are
714
+ * touched, so the user starts fresh on every page load.
715
+ */
716
+ private storageConsent;
717
+ /**
718
+ * In-memory access token. Always retained (even in anonymous mode where no
719
+ * cookie exists) so a merge can run when storage consent is granted later.
720
+ */
721
+ private accessToken;
722
+ /**
723
+ * Host-provided userId held back while anonymous. Used to merge the guest
724
+ * session into the logged-in user once storage consent is granted.
725
+ */
726
+ private pendingUserId;
727
+ constructor(baseUrl: string, apiKey: string, userId?: string, storageConsent?: boolean);
676
728
  /**
677
729
  * Create a client session by calling the authentication endpoint
678
730
  * @param request - Session request with optional externalUserId and metadata
@@ -689,6 +741,28 @@ declare class ApiService {
689
741
  * @returns Merge result data including the resulting clientId
690
742
  */
691
743
  mergeUser(request: MergeUserRequest): Promise<MergeUserData>;
744
+ /**
745
+ * Grant storage consent at runtime (e.g. the user accepted cookies while the
746
+ * chat was open).
747
+ *
748
+ * - Guest: persists the existing in-memory anonymous session to cookies so it
749
+ * simply becomes persistent (same session continues).
750
+ * - Logged-in user: merges the anonymous guest session into the user AND opens
751
+ * a fresh session for that user (via {@link createSession}) so we receive the
752
+ * user's auth data (accessToken, sessionId, clientId, layout, ...) from the
753
+ * API — the same merge-then-new-session flow used on a normal login.
754
+ *
755
+ * @param request - Optional metadata for the new session (logged-in case).
756
+ * @returns the newly created {@link SessionData} when a logged-in session was
757
+ * opened, or null when nothing new was created (guest, or no active
758
+ * in-memory session yet).
759
+ */
760
+ enableStorageConsent(request?: AuthSessionRequest): Promise<SessionData | null>;
761
+ /**
762
+ * Withdraw storage consent at runtime. Clears all cookies but keeps the
763
+ * in-memory session so the current chat keeps working until the page is left.
764
+ */
765
+ disableStorageConsent(): void;
692
766
  private persistClientInfo;
693
767
  private restoreSessionFromCookies;
694
768
  private clearStoredClientInfo;
@@ -743,4 +817,4 @@ declare const ERROR_MESSAGES: {
743
817
  readonly YOU_NEED_TO_INIT_THE_WIDGET_FIRST: "You need to initialize the widget first. Call init() before calling this method.";
744
818
  };
745
819
 
746
- export { API_BASE_URLS, API_ENDPOINTS, type AddToCartEvent, type AddToFavoriteEvent, type AddToFavoritePayload, ApiService, type AuthSessionRequest, type AuthSessionResponse, DEFAULT_CONFIG, ERROR_MESSAGES, type EventHandler, type EventName, type FavoriteProduct, type IUserCart, type IUserCartItem, type IncomingMessage, type IncomingMessageType, IncomingMessageTypes, type LayoutConfig, type MergeUserData, type MergeUserRequest, type MergeUserResponse, type OpenProductByIdPayload, type OutfitItem, type OutfitResponse, type OutgoingMessage, type OutgoingMessageType, OutgoingMessageTypes, type Product, type ProductImage, type ProductOffer, type ProductReplyOptions, type ProductSummary, type SendOutfitReplyPayload, type SendProductReplyPayload, type SendTextMessagePayload, type SessionData, type SessionDataChatPayload, type SetLocalePayload, type SophiConfig, type SophiEventMap, type SophiLocale, SophiWidget, type TriggerProductReplyPayload, type UserData };
820
+ export { API_BASE_URLS, API_ENDPOINTS, type AddToCartEvent, type AddToFavoriteEvent, type AddToFavoritePayload, ApiService, type AuthSessionRequest, type AuthSessionResponse, DEFAULT_CONFIG, ERROR_MESSAGES, type EventHandler, type EventName, type FavoriteProduct, type IUserCart, type IUserCartItem, type IncomingMessage, type IncomingMessageType, IncomingMessageTypes, type LayoutConfig, type MergeUserData, type MergeUserRequest, type MergeUserResponse, type OpenProductByIdPayload, type OutfitItem, type OutfitResponse, type OutgoingMessage, type OutgoingMessageType, OutgoingMessageTypes, type Product, type ProductImage, type ProductOffer, type ProductReplyOptions, type ProductSummary, type SendOutfitReplyPayload, type SendProductReplyPayload, type SendTextMessagePayload, type SessionData, type SessionDataChatPayload, type SetConsentPayload, type SetLocalePayload, type SophiConfig, type SophiEventMap, type SophiLocale, SophiWidget, type TriggerProductReplyPayload, type UserData };
package/dist/index.d.ts CHANGED
@@ -22,6 +22,14 @@ interface SessionDataChatPayload {
22
22
  interface SetLocalePayload {
23
23
  locale: SophiLocale;
24
24
  }
25
+ /**
26
+ * Payload for `set_consent` — tell the iframe to switch its persistence at
27
+ * runtime. `storage: true` promotes the in-memory store to localStorage;
28
+ * `storage: false` demotes back to memory and clears persisted keys.
29
+ */
30
+ interface SetConsentPayload {
31
+ storage: boolean;
32
+ }
25
33
  /**
26
34
  * Layout options sent to the iframe on init so the child app can adjust its layout.
27
35
  */
@@ -85,6 +93,14 @@ interface SophiConfig {
85
93
  * Environment to use for the API
86
94
  */
87
95
  environment?: "test" | "production";
96
+ /**
97
+ * Whether the SDK may persist session data to cookies. Defaults to `true`.
98
+ * Set to `false` to run in anonymous mode: no cookies are written or read,
99
+ * the session lives only in memory, and the user starts fresh on every load.
100
+ * Use {@link SophiWidget.setStorageConsent} to switch to persistent mode at
101
+ * runtime once the user grants cookie consent.
102
+ */
103
+ storageConsent?: boolean;
88
104
  /**
89
105
  * Layout options sent to the iframe on init. The child app receives these via a
90
106
  * postMessage and can show/hide header (and future layout options) accordingly.
@@ -197,7 +213,7 @@ declare const IncomingMessageTypes: {
197
213
  /**
198
214
  * Message types that can be sent to the iframe
199
215
  */
200
- type OutgoingMessageType = "user_data" | "config" | "update_user_cart" | "destroy" | "session_data" | "set_locale" | "client_id_updated" | "layout_config" | "toggle_history" | "send_text_message" | "send_product_reply" | "send_outfit_reply" | "open_product_by_id" | "trigger_product_reply" | "update_favorite";
216
+ type OutgoingMessageType = "user_data" | "config" | "update_user_cart" | "destroy" | "session_data" | "set_locale" | "set_consent" | "client_id_updated" | "layout_config" | "toggle_history" | "send_text_message" | "send_product_reply" | "send_outfit_reply" | "open_product_by_id" | "trigger_product_reply" | "update_favorite";
201
217
  /**
202
218
  * Outgoing message type constants for easy access
203
219
  * @example
@@ -210,6 +226,7 @@ declare const OutgoingMessageTypes: {
210
226
  readonly DESTROY: "destroy";
211
227
  readonly SESSION_DATA: "session_data";
212
228
  readonly SET_LOCALE: "set_locale";
229
+ readonly SET_CONSENT: "set_consent";
213
230
  readonly CLIENT_ID_UPDATED: "client_id_updated";
214
231
  readonly LAYOUT_CONFIG: "layout_config";
215
232
  readonly TOGGLE_HISTORY: "toggle_history";
@@ -568,6 +585,25 @@ declare class SophiWidget {
568
585
  * Use after host-side session refresh or if the child app missed the initial payload.
569
586
  */
570
587
  sendSessionToChat(): void;
588
+ /**
589
+ * Update storage (cookie) consent at runtime without losing the open chat.
590
+ *
591
+ * When granting consent, the current in-memory session is persisted to
592
+ * cookies. For a logged-in user (a userId was supplied at init), the guest
593
+ * session is merged into that user via `/auth/v1/client/update` and a fresh
594
+ * session is opened for the user so the SDK receives their auth data; the new
595
+ * session data is then re-posted to the iframe. The chat iframe is also told
596
+ * to promote its in-memory store to localStorage so the conversation survives
597
+ * the next reload. Withdrawing consent clears cookies and demotes the iframe
598
+ * back to memory-only.
599
+ *
600
+ * @param granted - `true` to enable persistence, `false` to withdraw it.
601
+ */
602
+ setStorageConsent(granted: boolean): Promise<void>;
603
+ /**
604
+ * Tell the chat iframe to switch its persistence strategy at runtime.
605
+ */
606
+ private postSetConsent;
571
607
  /**
572
608
  * Posts `set_locale` so the iframe can switch language without a full `session_data` round trip.
573
609
  *
@@ -672,7 +708,23 @@ declare class ApiService {
672
708
  private userId;
673
709
  private storedUserId;
674
710
  private cookieManager;
675
- constructor(baseUrl: string, apiKey: string, userId?: string);
711
+ /**
712
+ * Whether the SDK is allowed to read/write cookies. When false the SDK runs
713
+ * in anonymous mode: the session lives only in memory and no cookies are
714
+ * touched, so the user starts fresh on every page load.
715
+ */
716
+ private storageConsent;
717
+ /**
718
+ * In-memory access token. Always retained (even in anonymous mode where no
719
+ * cookie exists) so a merge can run when storage consent is granted later.
720
+ */
721
+ private accessToken;
722
+ /**
723
+ * Host-provided userId held back while anonymous. Used to merge the guest
724
+ * session into the logged-in user once storage consent is granted.
725
+ */
726
+ private pendingUserId;
727
+ constructor(baseUrl: string, apiKey: string, userId?: string, storageConsent?: boolean);
676
728
  /**
677
729
  * Create a client session by calling the authentication endpoint
678
730
  * @param request - Session request with optional externalUserId and metadata
@@ -689,6 +741,28 @@ declare class ApiService {
689
741
  * @returns Merge result data including the resulting clientId
690
742
  */
691
743
  mergeUser(request: MergeUserRequest): Promise<MergeUserData>;
744
+ /**
745
+ * Grant storage consent at runtime (e.g. the user accepted cookies while the
746
+ * chat was open).
747
+ *
748
+ * - Guest: persists the existing in-memory anonymous session to cookies so it
749
+ * simply becomes persistent (same session continues).
750
+ * - Logged-in user: merges the anonymous guest session into the user AND opens
751
+ * a fresh session for that user (via {@link createSession}) so we receive the
752
+ * user's auth data (accessToken, sessionId, clientId, layout, ...) from the
753
+ * API — the same merge-then-new-session flow used on a normal login.
754
+ *
755
+ * @param request - Optional metadata for the new session (logged-in case).
756
+ * @returns the newly created {@link SessionData} when a logged-in session was
757
+ * opened, or null when nothing new was created (guest, or no active
758
+ * in-memory session yet).
759
+ */
760
+ enableStorageConsent(request?: AuthSessionRequest): Promise<SessionData | null>;
761
+ /**
762
+ * Withdraw storage consent at runtime. Clears all cookies but keeps the
763
+ * in-memory session so the current chat keeps working until the page is left.
764
+ */
765
+ disableStorageConsent(): void;
692
766
  private persistClientInfo;
693
767
  private restoreSessionFromCookies;
694
768
  private clearStoredClientInfo;
@@ -743,4 +817,4 @@ declare const ERROR_MESSAGES: {
743
817
  readonly YOU_NEED_TO_INIT_THE_WIDGET_FIRST: "You need to initialize the widget first. Call init() before calling this method.";
744
818
  };
745
819
 
746
- export { API_BASE_URLS, API_ENDPOINTS, type AddToCartEvent, type AddToFavoriteEvent, type AddToFavoritePayload, ApiService, type AuthSessionRequest, type AuthSessionResponse, DEFAULT_CONFIG, ERROR_MESSAGES, type EventHandler, type EventName, type FavoriteProduct, type IUserCart, type IUserCartItem, type IncomingMessage, type IncomingMessageType, IncomingMessageTypes, type LayoutConfig, type MergeUserData, type MergeUserRequest, type MergeUserResponse, type OpenProductByIdPayload, type OutfitItem, type OutfitResponse, type OutgoingMessage, type OutgoingMessageType, OutgoingMessageTypes, type Product, type ProductImage, type ProductOffer, type ProductReplyOptions, type ProductSummary, type SendOutfitReplyPayload, type SendProductReplyPayload, type SendTextMessagePayload, type SessionData, type SessionDataChatPayload, type SetLocalePayload, type SophiConfig, type SophiEventMap, type SophiLocale, SophiWidget, type TriggerProductReplyPayload, type UserData };
820
+ export { API_BASE_URLS, API_ENDPOINTS, type AddToCartEvent, type AddToFavoriteEvent, type AddToFavoritePayload, ApiService, type AuthSessionRequest, type AuthSessionResponse, DEFAULT_CONFIG, ERROR_MESSAGES, type EventHandler, type EventName, type FavoriteProduct, type IUserCart, type IUserCartItem, type IncomingMessage, type IncomingMessageType, IncomingMessageTypes, type LayoutConfig, type MergeUserData, type MergeUserRequest, type MergeUserResponse, type OpenProductByIdPayload, type OutfitItem, type OutfitResponse, type OutgoingMessage, type OutgoingMessageType, OutgoingMessageTypes, type Product, type ProductImage, type ProductOffer, type ProductReplyOptions, type ProductSummary, type SendOutfitReplyPayload, type SendProductReplyPayload, type SendTextMessagePayload, type SessionData, type SessionDataChatPayload, type SetConsentPayload, type SetLocalePayload, type SophiConfig, type SophiEventMap, type SophiLocale, SophiWidget, type TriggerProductReplyPayload, type UserData };
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- var v={ADD_TO_CART:"add_to_cart",ADD_TO_FAVORITE:"add_to_favorite",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",SET_LOCALE:"set_locale",CLIENT_ID_UPDATED:"client_id_updated",LAYOUT_CONFIG:"layout_config",TOGGLE_HISTORY:"toggle_history",SEND_TEXT_MESSAGE:"send_text_message",SEND_PRODUCT_REPLY:"send_product_reply",SEND_OUTFIT_REPLY:"send_outfit_reply",OPEN_PRODUCT_BY_ID:"open_product_by_id",TRIGGER_PRODUCT_REPLY:"trigger_product_reply",UPDATE_FAVORITE:"update_favorite"};function E(s){let e=2166136261;for(let t=0;t<s.length;t++)e^=s.charCodeAt(t),e=e*16777619>>>0;return e.toString(16).padStart(8,"0")}var d=class d{constructor(e,t){this.namespace=d.sanitizeNamespace(e),this.maxAgeSeconds=t?.maxAgeSeconds??d.DEFAULT_MAX_AGE_SECONDS,this.keys={...d.DEFAULT_KEYS,...t?.keys??{}};}static buildNamespace(e,t){return `sophi-${E(t)}`}static canUseCookies(){return typeof document<"u"&&typeof document.cookie=="string"}set(e,t){d.canUseCookies()&&(document.cookie=`${this.formatName(e)}=${encodeURIComponent(t)}; path=/; max-age=${this.maxAgeSeconds}; samesite=lax`);}get(e){return d.canUseCookies()?d.readRawCookie(this.formatName(e)):null}delete(e){d.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"}};d.DEFAULT_MAX_AGE_SECONDS=3600*24*30,d.DEFAULT_KEYS={clientId:"client-id",externalUserId:"external-user-id",accessToken:"access-token",sessionData:"session-data",metadataHash:"metadata-hash"};var u=d;var p=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 u(u.buildNamespace(e,t)),this.restoreSessionFromCookies();}async createSession(e){let t=`${this.baseUrl}/auth/v1/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 c=this.storedUserId||null,l=this.userId||null;if(c!==l&&l)try{await this.mergeUser({userId:l}),this.storedUserId=l;}catch{this.clearStoredClientInfo(),delete r["x-sophi-client-id"],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 c=await i.text();throw new Error(`Failed to create session: ${i.status} ${i.statusText}. ${c}`)}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,n.data.accessToken,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}/auth/v1/client/update`;try{if(!this.clientId)throw new Error("Client ID is not set. Please create a session first.");let r=this.cookieManager.get("accessToken");if(!r)throw new Error("Access token is not available. Please create a session first.");let i=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json",Authorization:`Bearer ${r}`,"x-sophi-client-id":this.clientId},body:JSON.stringify({externalUserId:e.userId})});if(!i.ok){let c=await i.text();throw new Error(`Failed to merge user: ${i.status} ${i.statusText}. ${c}`)}let n=await i.json();if(!n.success)throw new Error(`User merge failed: ${n.message||"Unknown error"}`);if(!n.data)throw new Error("Merge data is missing from response");return n.data}catch(r){throw r instanceof Error?r:new Error(`Unexpected error during user merge: ${String(r)}`)}}persistClientInfo(e,t,r){this.clientId=e,this.userId=r,this.storedUserId=r||null,u.canUseCookies()&&(this.cookieManager.set("clientId",e),this.cookieManager.set("accessToken",t),this.cookieManager.set("externalUserId",r||""));}restoreSessionFromCookies(){if(!u.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,u.canUseCookies()&&this.cookieManager.clearAll();}};var _={WIDTH:"100%",HEIGHT:"600px"},g={test:"https://api-gateway.dev.usesophi.com",production:"https://api.usesophi.com"},f={test:"http://localhost:5173",production:"https://app.prod.usesophi.com"},T={CREATE_SESSION:"/auth/v1/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."};function m(s){if(!s.apiKey||typeof s.apiKey!="string")throw new Error(a.MISSING_API_KEY);if(s.userId&&typeof s.userId!="string")throw new Error(a.MISSING_USER_ID);if(!s.container)throw new Error(a.MISSING_CONTAINER)}function y(s){if(!s||typeof s!="object")throw new Error("[Sophi] updateUserCart: cart must be a non-null object");if(!s.cartId||typeof s.cartId!="string")throw new Error("[Sophi] updateUserCart: cart.cartId is required and must be a string");if(!Array.isArray(s.items))throw new Error("[Sophi] updateUserCart: cart.items must be an array");s.items.forEach((e,t)=>{if(!e||typeof e!="object")throw new Error(`[Sophi] updateUserCart: cart.items[${t}] must be a non-null object`);if(!e.productId||typeof e.productId!="string")throw new Error(`[Sophi] updateUserCart: cart.items[${t}].productId is required and must be a string`);if(!e.variantId||typeof e.variantId!="string")throw new Error(`[Sophi] updateUserCart: cart.items[${t}].variantId is required and must be a string`);if(e.quantity!==void 0&&(typeof e.quantity!="number"||e.quantity<0))throw new Error(`[Sophi] updateUserCart: cart.items[${t}].quantity must be a non-negative number`)});}function I(s){if(!s||typeof s!="object")return false;let e=s;return Array.isArray(e.products)?e.products.every(t=>{if(!t||typeof t!="object")return false;let r=t;return !(typeof r.productId!="string"||r.variantId!==void 0&&typeof r.variantId!="string")}):false}function S(s){if(!s||typeof s!="object")return console.warn("[Sophi] add_to_favorite: invalid message data \u2014 ignoring"),null;let e=s,t=e.product;if(!t||typeof t!="object")return console.warn("[Sophi] add_to_favorite: data.product is missing or invalid \u2014 ignoring"),null;if(typeof e.isFavorite!="boolean")return console.warn("[Sophi] add_to_favorite: isFavorite must be a boolean \u2014 ignoring"),null;let r=t,i=r.externalId,n=r.id,c=r.productId,l=typeof i=="string"&&i.length>0?i:n!=null?String(n):typeof c=="string"&&c.length>0?c:null;return l==null?(console.warn("[Sophi] add_to_favorite: product has no externalId, id, or productId \u2014 ignoring"),null):{product:t,isFavorite:e.isFavorite,productIdentifier:l}}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.currentLocale=void 0;this.apiService=null;this.apiBaseUrl=null;}async init(e){if(this.isInitialized){console.warn(a.ALREADY_INITIALIZED);return}try{m(e),this.config=this.formatConfig(e),this.currentLocale=this.config.locale;let t=g[e.environment||"production"]||g.production;this.apiBaseUrl=t,this.apiService=new p(this.apiBaseUrl,e.apiKey,e.userId),await this.createAuthSession(),this.isInitialized=!0;try{let r=new URL(this.getIframeUrl());this.iframeOrigin=r.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 r=new Error(a.CONTAINER_NOT_FOUND);throw this.emitError(r),r}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,this.currentLocale=void 0;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: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);}sendTextMessage(e){if(!this.iframe||!this.iframe.contentWindow){console.warn("[Sophi] sendTextMessage: iframe is not ready.");return}let t={type:o.SEND_TEXT_MESSAGE,data:{query:e}};this.postMessage(t);}sendProductReply(e,t){if(!this.iframe||!this.iframe.contentWindow){console.warn("[Sophi] sendProductReply: iframe is not ready.");return}let r={type:o.SEND_PRODUCT_REPLY,data:{query:t?.query||void 0,externalProductId:e,...t?.isOutfit!==void 0&&{isOutfit:t.isOutfit}}};this.postMessage(r);}sendOutfitReply(e,t){if(!this.iframe||!this.iframe.contentWindow){console.warn("[Sophi] sendOutfitReply: iframe is not ready.");return}let r={type:o.SEND_OUTFIT_REPLY,data:{query:e,outfitId:t.recommendationId,outfit:t}};this.postMessage(r);}openProductById(e,t){if(!this.iframe||!this.iframe.contentWindow){console.warn("[Sophi] openProductById: iframe is not ready.");return}let r={type:o.OPEN_PRODUCT_BY_ID,data:{externalProductId:e,...t!==void 0&&{reason:t}}};this.postMessage(r);}updateFavorite(e,t){if(!this.iframe||!this.iframe.contentWindow){console.warn("[Sophi] updateFavorite: iframe is not ready.");return}let r={type:o.UPDATE_FAVORITE,data:{productId:e,isFavorite:t}};this.postMessage(r);}sendSessionToChat(){if(!this.isInitialized||!this.iframe){console.warn("[Sophi] sendSessionToChat: widget not initialized.");return}this.sendSessionData();}setLocale(e){if(e!=="tr"&&e!=="en"){console.warn(`[Sophi] setLocale: invalid locale "${e}" \u2014 expected "tr" or "en".`);return}if(this.currentLocale=e,!this.iframe?.contentWindow){console.warn("[Sophi] setLocale: iframe is not ready.");return}let t={type:o.SET_LOCALE,data:{locale:e}};this.postMessage(t);}updateUserCart(e){if(!this.isInitialized||!this.iframe){console.warn("Widget not initialized. Call init() first.");return}y(e);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 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.currentLocale=void 0,this.apiService=null;}isReady(){return this.isInitialized&&this.iframe!==null}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)}}resolveSessionUserName(e,t){let r=[e,t].find(i=>typeof i=="string"&&i.trim().length>0);return r?r.trim():""}buildChatSessionPayload(){if(!this.sessionData||!this.config)return null;let e=this.sessionData,t=e.layoutId,r=t!=null&&String(t).length>0?String(t):void 0,i=this.resolveSessionUserName(this.config.userName,e.userName);return {accessToken:e.accessToken,sessionId:e.sessionId,clientId:e.clientId,companyCode:e.companyCode,expiresAt:e.expiresAt,userName:i,...r!==void 0?{layoutId:r}:{},...this.currentLocale?{locale:this.currentLocale}:{}}}sendSessionData(){let e=this.buildChatSessionPayload();if(!e){console.warn("Session data not available");return}let t={type:o.SESSION_DATA,data:e};this.postMessage(t);}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; clipboard-write",this.container.appendChild(e),this.iframe=e;}buildIframeUrl(){return this.config?new URL(this.getIframeUrl()).toString():""}getIframeUrl(){return this.config?this.config.environment==="production"?f.production:f.test:""}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":I(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 "add_to_favorite":{let r=S(e.data);r&&(console.log(`[Sophi] add_to_favorite fired: productIdentifier=${r.productIdentifier}, isFavorite=${r.isFavorite}`),this.emit("add_to_favorite",r));break}case "send_to_checkout":this.emit("send_to_checkout",void 0);break;case "ready":this.emit("ready",void 0),setTimeout(()=>{this.sendSessionData(),this.config?.layout&&this.sendLayoutConfig();},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);}}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
- export{g as API_BASE_URLS,T as API_ENDPOINTS,p as ApiService,_ as DEFAULT_CONFIG,a as ERROR_MESSAGES,v as IncomingMessageTypes,o as OutgoingMessageTypes,h as SophiWidget};//# sourceMappingURL=index.js.map
1
+ var v={ADD_TO_CART:"add_to_cart",ADD_TO_FAVORITE:"add_to_favorite",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",SET_LOCALE:"set_locale",SET_CONSENT:"set_consent",CLIENT_ID_UPDATED:"client_id_updated",LAYOUT_CONFIG:"layout_config",TOGGLE_HISTORY:"toggle_history",SEND_TEXT_MESSAGE:"send_text_message",SEND_PRODUCT_REPLY:"send_product_reply",SEND_OUTFIT_REPLY:"send_outfit_reply",OPEN_PRODUCT_BY_ID:"open_product_by_id",TRIGGER_PRODUCT_REPLY:"trigger_product_reply",UPDATE_FAVORITE:"update_favorite"};function E(s){let e=2166136261;for(let t=0;t<s.length;t++)e^=s.charCodeAt(t),e=e*16777619>>>0;return e.toString(16).padStart(8,"0")}var d=class d{constructor(e,t){this.namespace=d.sanitizeNamespace(e),this.maxAgeSeconds=t?.maxAgeSeconds??d.DEFAULT_MAX_AGE_SECONDS,this.keys={...d.DEFAULT_KEYS,...t?.keys??{}};}static buildNamespace(e,t){return `sophi-${E(t)}`}static canUseCookies(){return typeof document<"u"&&typeof document.cookie=="string"}set(e,t){d.canUseCookies()&&(document.cookie=`${this.formatName(e)}=${encodeURIComponent(t)}; path=/; max-age=${this.maxAgeSeconds}; samesite=lax`);}get(e){return d.canUseCookies()?d.readRawCookie(this.formatName(e)):null}delete(e){d.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"}};d.DEFAULT_MAX_AGE_SECONDS=3600*24*30,d.DEFAULT_KEYS={clientId:"client-id",externalUserId:"external-user-id",accessToken:"access-token",sessionData:"session-data",metadataHash:"metadata-hash"};var c=d;var p=class{constructor(e,t,r,i=true){this.clientId=null;this.userId=void 0;this.storedUserId=null;this.accessToken=null;this.pendingUserId=null;this.baseUrl=e,this.apiKey=t,this.storageConsent=i,this.cookieManager=new c(c.buildNamespace(e,t)),i?(this.userId=r,this.restoreSessionFromCookies()):(this.pendingUserId=r||null,this.userId=void 0);}async createSession(e){let t=`${this.baseUrl}/auth/v1/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 l=this.storedUserId||null,u=this.userId||null;if(l!==u&&u)try{await this.mergeUser({userId:u}),this.storedUserId=u;}catch{this.clearStoredClientInfo(),delete r["x-sophi-client-id"],this.storedUserId=u;}}let i=await fetch(t,{method:"POST",headers:r,body:JSON.stringify({externalUserId:this.userId,metadata:e.metadata})});if(!i.ok){let l=await i.text();throw new Error(`Failed to create session: ${i.status} ${i.statusText}. ${l}`)}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,n.data.accessToken,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}/auth/v1/client/update`;try{if(!this.clientId)throw new Error("Client ID is not set. Please create a session first.");let r=this.accessToken??this.cookieManager.get("accessToken");if(!r)throw new Error("Access token is not available. Please create a session first.");let i=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json",Authorization:`Bearer ${r}`,"x-sophi-client-id":this.clientId},body:JSON.stringify({externalUserId:e.userId})});if(!i.ok){let l=await i.text();throw new Error(`Failed to merge user: ${i.status} ${i.statusText}. ${l}`)}let n=await i.json();if(!n.success)throw new Error(`User merge failed: ${n.message||"Unknown error"}`);if(!n.data)throw new Error("Merge data is missing from response");return n.data}catch(r){throw r instanceof Error?r:new Error(`Unexpected error during user merge: ${String(r)}`)}}async enableStorageConsent(e={}){return this.storageConsent=true,!this.clientId||!this.accessToken?(this.pendingUserId&&!this.userId&&(this.userId=this.pendingUserId),this.pendingUserId=null,null):(c.canUseCookies()&&(this.cookieManager.set("clientId",this.clientId),this.cookieManager.set("accessToken",this.accessToken),this.cookieManager.set("externalUserId",this.userId||"")),this.pendingUserId?(this.userId=this.pendingUserId,this.pendingUserId=null,this.createSession(e)):null)}disableStorageConsent(){this.storageConsent=false,c.canUseCookies()&&this.cookieManager.clearAll();}persistClientInfo(e,t,r){this.clientId=e,this.accessToken=t,this.userId=r,this.storedUserId=r||null,!(!this.storageConsent||!c.canUseCookies())&&(this.cookieManager.set("clientId",e),this.cookieManager.set("accessToken",t),this.cookieManager.set("externalUserId",r||""));}restoreSessionFromCookies(){if(!this.storageConsent||!c.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,c.canUseCookies()&&this.cookieManager.clearAll();}};var _={WIDTH:"100%",HEIGHT:"600px"},g={test:"https://api-gateway.dev.usesophi.com",production:"https://api.usesophi.com"},h={test:"http://localhost:5173/",production:"https://app.prod.usesophi.com"},C={CREATE_SESSION:"/auth/v1/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."};function m(s){if(!s.apiKey||typeof s.apiKey!="string")throw new Error(a.MISSING_API_KEY);if(s.userId&&typeof s.userId!="string")throw new Error(a.MISSING_USER_ID);if(!s.container)throw new Error(a.MISSING_CONTAINER)}function I(s){if(!s||typeof s!="object")throw new Error("[Sophi] updateUserCart: cart must be a non-null object");if(!s.cartId||typeof s.cartId!="string")throw new Error("[Sophi] updateUserCart: cart.cartId is required and must be a string");if(!Array.isArray(s.items))throw new Error("[Sophi] updateUserCart: cart.items must be an array");s.items.forEach((e,t)=>{if(!e||typeof e!="object")throw new Error(`[Sophi] updateUserCart: cart.items[${t}] must be a non-null object`);if(!e.productId||typeof e.productId!="string")throw new Error(`[Sophi] updateUserCart: cart.items[${t}].productId is required and must be a string`);if(!e.variantId||typeof e.variantId!="string")throw new Error(`[Sophi] updateUserCart: cart.items[${t}].variantId is required and must be a string`);if(e.quantity!==void 0&&(typeof e.quantity!="number"||e.quantity<0))throw new Error(`[Sophi] updateUserCart: cart.items[${t}].quantity must be a non-negative number`)});}function y(s){if(!s||typeof s!="object")return false;let e=s;return Array.isArray(e.products)?e.products.every(t=>{if(!t||typeof t!="object")return false;let r=t;return !(typeof r.productId!="string"||r.variantId!==void 0&&typeof r.variantId!="string")}):false}function S(s){if(!s||typeof s!="object")return console.warn("[Sophi] add_to_favorite: invalid message data \u2014 ignoring"),null;let e=s,t=e.product;if(!t||typeof t!="object")return console.warn("[Sophi] add_to_favorite: data.product is missing or invalid \u2014 ignoring"),null;if(typeof e.isFavorite!="boolean")return console.warn("[Sophi] add_to_favorite: isFavorite must be a boolean \u2014 ignoring"),null;let r=t,i=r.externalId,n=r.id,l=r.productId,u=typeof i=="string"&&i.length>0?i:n!=null?String(n):typeof l=="string"&&l.length>0?l:null;return u==null?(console.warn("[Sophi] add_to_favorite: product has no externalId, id, or productId \u2014 ignoring"),null):{product:t,isFavorite:e.isFavorite,productIdentifier:u}}var f=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.currentLocale=void 0;this.apiService=null;this.apiBaseUrl=null;}async init(e){if(this.isInitialized){console.warn(a.ALREADY_INITIALIZED);return}try{m(e),this.config=this.formatConfig(e),this.currentLocale=this.config.locale;let t=g[e.environment||"production"]||g.production;this.apiBaseUrl=t,this.apiService=new p(this.apiBaseUrl,e.apiKey,e.userId,e.storageConsent!==!1),await this.createAuthSession(),this.isInitialized=!0;try{let r=new URL(this.getIframeUrl());this.iframeOrigin=r.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 r=new Error(a.CONTAINER_NOT_FOUND);throw this.emitError(r),r}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,this.currentLocale=void 0;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: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);}sendTextMessage(e){if(!this.iframe||!this.iframe.contentWindow){console.warn("[Sophi] sendTextMessage: iframe is not ready.");return}let t={type:o.SEND_TEXT_MESSAGE,data:{query:e}};this.postMessage(t);}sendProductReply(e,t){if(!this.iframe||!this.iframe.contentWindow){console.warn("[Sophi] sendProductReply: iframe is not ready.");return}let r={type:o.SEND_PRODUCT_REPLY,data:{query:t?.query||void 0,externalProductId:e,...t?.isOutfit!==void 0&&{isOutfit:t.isOutfit}}};this.postMessage(r);}sendOutfitReply(e,t){if(!this.iframe||!this.iframe.contentWindow){console.warn("[Sophi] sendOutfitReply: iframe is not ready.");return}let r={type:o.SEND_OUTFIT_REPLY,data:{query:e,outfitId:t.recommendationId,outfit:t}};this.postMessage(r);}openProductById(e,t){if(!this.iframe||!this.iframe.contentWindow){console.warn("[Sophi] openProductById: iframe is not ready.");return}let r={type:o.OPEN_PRODUCT_BY_ID,data:{externalProductId:e,...t!==void 0&&{reason:t}}};this.postMessage(r);}updateFavorite(e,t){if(!this.iframe||!this.iframe.contentWindow){console.warn("[Sophi] updateFavorite: iframe is not ready.");return}let r={type:o.UPDATE_FAVORITE,data:{productId:e,isFavorite:t}};this.postMessage(r);}sendSessionToChat(){if(!this.isInitialized||!this.iframe){console.warn("[Sophi] sendSessionToChat: widget not initialized.");return}this.sendSessionData();}async setStorageConsent(e){if(this.config&&(this.config.storageConsent=e),!this.apiService){console.warn("[Sophi] setStorageConsent: widget not initialized yet.");return}if(e){try{let t=await this.apiService.enableStorageConsent({metadata:this.config?.metadata});t&&(this.sessionData=t);}catch(t){this.emitError(t instanceof Error?t:new Error(String(t)));}this.postSetConsent(true),this.sendSessionData();}else this.apiService.disableStorageConsent(),this.postSetConsent(false);}postSetConsent(e){if(!this.iframe||!this.iframe.contentWindow)return;let t={type:o.SET_CONSENT,data:{storage:e}};this.postMessage(t);}setLocale(e){if(e!=="tr"&&e!=="en"){console.warn(`[Sophi] setLocale: invalid locale "${e}" \u2014 expected "tr" or "en".`);return}if(this.currentLocale=e,!this.iframe?.contentWindow){console.warn("[Sophi] setLocale: iframe is not ready.");return}let t={type:o.SET_LOCALE,data:{locale:e}};this.postMessage(t);}updateUserCart(e){if(!this.isInitialized||!this.iframe){console.warn("Widget not initialized. Call init() first.");return}I(e);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 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.currentLocale=void 0,this.apiService=null;}isReady(){return this.isInitialized&&this.iframe!==null}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)}}resolveSessionUserName(e,t){let r=[e,t].find(i=>typeof i=="string"&&i.trim().length>0);return r?r.trim():""}buildChatSessionPayload(){if(!this.sessionData||!this.config)return null;let e=this.sessionData,t=e.layoutId,r=t!=null&&String(t).length>0?String(t):void 0,i=this.resolveSessionUserName(this.config.userName,e.userName);return {accessToken:e.accessToken,sessionId:e.sessionId,clientId:e.clientId,companyCode:e.companyCode,expiresAt:e.expiresAt,userName:i,...r!==void 0?{layoutId:r}:{},...this.currentLocale?{locale:this.currentLocale}:{}}}sendSessionData(){let e=this.buildChatSessionPayload();if(!e){console.warn("Session data not available");return}let t={type:o.SESSION_DATA,data:e};this.postMessage(t);}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; clipboard-write",this.container.appendChild(e),this.iframe=e;}buildIframeUrl(){if(!this.config)return "";let e=new URL(this.getIframeUrl());return this.config.storageConsent===false&&e.searchParams.set("storageConsent","0"),e.toString()}getIframeUrl(){return this.config?this.config.environment==="production"?h.production:h.test:""}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":y(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 "add_to_favorite":{let r=S(e.data);r&&(console.log(`[Sophi] add_to_favorite fired: productIdentifier=${r.productIdentifier}, isFavorite=${r.isFavorite}`),this.emit("add_to_favorite",r));break}case "send_to_checkout":this.emit("send_to_checkout",void 0);break;case "ready":this.emit("ready",void 0),setTimeout(()=>{this.sendSessionData(),this.config?.layout&&this.sendLayoutConfig();},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);}}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
+ export{g as API_BASE_URLS,C as API_ENDPOINTS,p as ApiService,_ as DEFAULT_CONFIG,a as ERROR_MESSAGES,v as IncomingMessageTypes,o as OutgoingMessageTypes,f as SophiWidget};//# sourceMappingURL=index.js.map
3
3
  //# sourceMappingURL=index.js.map