@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 +22 -0
- package/README.md +487 -0
- package/dist/index.cjs +3 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +472 -0
- package/dist/index.d.ts +472 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/package.json +47 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for initializing the Sophi Widget
|
|
3
|
+
*/
|
|
4
|
+
interface SophiConfig {
|
|
5
|
+
/**
|
|
6
|
+
* API key for authentication with Sophi services (x-api-key header)
|
|
7
|
+
* This comes from the parent application
|
|
8
|
+
*/
|
|
9
|
+
apiKey: string;
|
|
10
|
+
/**
|
|
11
|
+
* Client ID for authentication (x-sophi-client-id header)
|
|
12
|
+
* This comes from the parent application
|
|
13
|
+
*/
|
|
14
|
+
clientId: string;
|
|
15
|
+
/**
|
|
16
|
+
* Container element selector (e.g., '#widget') or HTMLElement where the widget will be mounted
|
|
17
|
+
*/
|
|
18
|
+
container: string | HTMLElement;
|
|
19
|
+
/**
|
|
20
|
+
* URL of the iframe to embed (e.g., 'https://widget.sophi.ai' or 'http://localhost:5173')
|
|
21
|
+
*/
|
|
22
|
+
iframeUrl: string;
|
|
23
|
+
/**
|
|
24
|
+
* Optional metadata to send with the session request
|
|
25
|
+
*/
|
|
26
|
+
metadata?: Record<string, unknown>;
|
|
27
|
+
/**
|
|
28
|
+
* Width of the widget iframe
|
|
29
|
+
* @default '100%'
|
|
30
|
+
*/
|
|
31
|
+
width?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Height of the widget iframe
|
|
34
|
+
* @default '600px'
|
|
35
|
+
*/
|
|
36
|
+
height?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Callback fired when the widget is ready
|
|
39
|
+
*/
|
|
40
|
+
onReady?: () => void;
|
|
41
|
+
/**
|
|
42
|
+
* Callback fired when an error occurs
|
|
43
|
+
*/
|
|
44
|
+
onError?: (error: Error) => void;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Product information for add to cart events
|
|
48
|
+
*/
|
|
49
|
+
interface Product {
|
|
50
|
+
/**
|
|
51
|
+
* Unique product identifier
|
|
52
|
+
*/
|
|
53
|
+
productId: string;
|
|
54
|
+
/**
|
|
55
|
+
* Optional variant identifier
|
|
56
|
+
*/
|
|
57
|
+
variantId?: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Add to cart event data
|
|
61
|
+
*/
|
|
62
|
+
interface AddToCartEvent {
|
|
63
|
+
/**
|
|
64
|
+
* List of products to add to cart
|
|
65
|
+
*/
|
|
66
|
+
products: Product[];
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* User data that can be sent to the widget
|
|
70
|
+
*/
|
|
71
|
+
interface UserData {
|
|
72
|
+
/**
|
|
73
|
+
* User ID
|
|
74
|
+
*/
|
|
75
|
+
userId?: string;
|
|
76
|
+
/**
|
|
77
|
+
* User email
|
|
78
|
+
*/
|
|
79
|
+
email?: string;
|
|
80
|
+
/**
|
|
81
|
+
* User name
|
|
82
|
+
*/
|
|
83
|
+
name?: string;
|
|
84
|
+
/**
|
|
85
|
+
* Additional custom data
|
|
86
|
+
*/
|
|
87
|
+
[key: string]: unknown;
|
|
88
|
+
}
|
|
89
|
+
interface IUserCartItem {
|
|
90
|
+
productId: string;
|
|
91
|
+
variantId: string;
|
|
92
|
+
quantity?: number;
|
|
93
|
+
}
|
|
94
|
+
interface IUserCart {
|
|
95
|
+
cardId: string;
|
|
96
|
+
items: IUserCartItem[];
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Message types that can be received from the iframe
|
|
100
|
+
*/
|
|
101
|
+
type IncomingMessageType = "add_to_cart" | "send_to_checkout" | "ready" | "error";
|
|
102
|
+
/**
|
|
103
|
+
* Incoming message type constants for easy access
|
|
104
|
+
* @example
|
|
105
|
+
* widget.on(IncomingMessageTypes.ADD_TO_CART, (data) => {...})
|
|
106
|
+
*/
|
|
107
|
+
declare const IncomingMessageTypes: {
|
|
108
|
+
readonly ADD_TO_CART: "add_to_cart";
|
|
109
|
+
readonly READY: "ready";
|
|
110
|
+
readonly ERROR: "error";
|
|
111
|
+
readonly SEND_TO_CHECKOUT: "send_to_checkout";
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Message types that can be sent to the iframe
|
|
115
|
+
*/
|
|
116
|
+
type OutgoingMessageType = "user_data" | "config" | "update_user_cart" | "destroy" | "session_data" | "client_id_updated";
|
|
117
|
+
/**
|
|
118
|
+
* Outgoing message type constants for easy access
|
|
119
|
+
* @example
|
|
120
|
+
* widget.sendMessage({ type: OutgoingMessageTypes.USER_DATA, data: {...} })
|
|
121
|
+
*/
|
|
122
|
+
declare const OutgoingMessageTypes: {
|
|
123
|
+
readonly USER_DATA: "user_data";
|
|
124
|
+
readonly CONFIG: "config";
|
|
125
|
+
readonly UPDATE_USER_CART: "update_user_cart";
|
|
126
|
+
readonly DESTROY: "destroy";
|
|
127
|
+
readonly SESSION_DATA: "session_data";
|
|
128
|
+
readonly CLIENT_ID_UPDATED: "client_id_updated";
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* Incoming message structure from iframe
|
|
132
|
+
*/
|
|
133
|
+
interface IncomingMessage {
|
|
134
|
+
type: IncomingMessageType;
|
|
135
|
+
data?: unknown;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Outgoing message structure to iframe
|
|
139
|
+
*/
|
|
140
|
+
interface OutgoingMessage {
|
|
141
|
+
type: OutgoingMessageType;
|
|
142
|
+
data?: unknown;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Event map for type-safe event handling
|
|
146
|
+
*/
|
|
147
|
+
interface SophiEventMap {
|
|
148
|
+
/**
|
|
149
|
+
* Fired when products should be added to cart
|
|
150
|
+
*/
|
|
151
|
+
add_to_cart: AddToCartEvent;
|
|
152
|
+
/**
|
|
153
|
+
* Fired when the widget is ready
|
|
154
|
+
*/
|
|
155
|
+
ready: void;
|
|
156
|
+
/**
|
|
157
|
+
* Fired when an error occurs
|
|
158
|
+
*/
|
|
159
|
+
error: Error;
|
|
160
|
+
/**
|
|
161
|
+
* Fired when the user should be sent to checkout
|
|
162
|
+
*/
|
|
163
|
+
send_to_checkout: void;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Event handler type
|
|
167
|
+
*/
|
|
168
|
+
type EventHandler<T> = (data: T) => void;
|
|
169
|
+
/**
|
|
170
|
+
* Event name type
|
|
171
|
+
*/
|
|
172
|
+
type EventName = keyof SophiEventMap;
|
|
173
|
+
/**
|
|
174
|
+
* Authentication session request payload
|
|
175
|
+
*/
|
|
176
|
+
interface AuthSessionRequest {
|
|
177
|
+
/**
|
|
178
|
+
* External user ID from the parent application
|
|
179
|
+
*/
|
|
180
|
+
externalUserId: string;
|
|
181
|
+
/**
|
|
182
|
+
* Optional metadata to include with the session
|
|
183
|
+
*/
|
|
184
|
+
metadata?: Record<string, unknown>;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Session data returned from the authentication API
|
|
188
|
+
*/
|
|
189
|
+
interface SessionData {
|
|
190
|
+
/**
|
|
191
|
+
* JWT access token for authenticating requests
|
|
192
|
+
*/
|
|
193
|
+
accessToken: string;
|
|
194
|
+
/**
|
|
195
|
+
* Unique session identifier
|
|
196
|
+
*/
|
|
197
|
+
sessionId: string;
|
|
198
|
+
/**
|
|
199
|
+
* Client ID
|
|
200
|
+
*/
|
|
201
|
+
clientId: string;
|
|
202
|
+
/**
|
|
203
|
+
* Company code
|
|
204
|
+
*/
|
|
205
|
+
companyCode: string;
|
|
206
|
+
/**
|
|
207
|
+
* Session expiration timestamp
|
|
208
|
+
*/
|
|
209
|
+
expiresAt: string;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Authentication session API response
|
|
213
|
+
*/
|
|
214
|
+
interface AuthSessionResponse {
|
|
215
|
+
/**
|
|
216
|
+
* Whether the request was successful
|
|
217
|
+
*/
|
|
218
|
+
success: boolean;
|
|
219
|
+
/**
|
|
220
|
+
* Response message
|
|
221
|
+
*/
|
|
222
|
+
message: string;
|
|
223
|
+
/**
|
|
224
|
+
* Session data
|
|
225
|
+
*/
|
|
226
|
+
data?: SessionData;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Merge user request payload
|
|
230
|
+
*/
|
|
231
|
+
interface MergeUserRequest {
|
|
232
|
+
/**
|
|
233
|
+
* The guest client ID to merge from
|
|
234
|
+
*/
|
|
235
|
+
guestId: string;
|
|
236
|
+
/**
|
|
237
|
+
* The logged-in user ID to merge to
|
|
238
|
+
*/
|
|
239
|
+
userId: string;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Merge user data returned from the API
|
|
243
|
+
*/
|
|
244
|
+
interface MergeUserData {
|
|
245
|
+
/**
|
|
246
|
+
* The resulting client ID after merge
|
|
247
|
+
*/
|
|
248
|
+
clientId: string;
|
|
249
|
+
/**
|
|
250
|
+
* The external user ID
|
|
251
|
+
*/
|
|
252
|
+
externalUserId: string;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Merge user API response
|
|
256
|
+
*/
|
|
257
|
+
interface MergeUserResponse {
|
|
258
|
+
/**
|
|
259
|
+
* Whether the request was successful
|
|
260
|
+
*/
|
|
261
|
+
success: boolean;
|
|
262
|
+
/**
|
|
263
|
+
* Response message
|
|
264
|
+
*/
|
|
265
|
+
message: string;
|
|
266
|
+
/**
|
|
267
|
+
* Merge result data
|
|
268
|
+
*/
|
|
269
|
+
data?: MergeUserData;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Main Sophi Widget SDK class
|
|
274
|
+
* Provides a framework-agnostic way to embed and interact with the Sophi AI widget
|
|
275
|
+
*/
|
|
276
|
+
declare class SophiWidget {
|
|
277
|
+
private config;
|
|
278
|
+
private iframe;
|
|
279
|
+
private container;
|
|
280
|
+
private eventListeners;
|
|
281
|
+
private messageHandler;
|
|
282
|
+
private isInitialized;
|
|
283
|
+
private iframeOrigin;
|
|
284
|
+
private sessionData;
|
|
285
|
+
private apiService;
|
|
286
|
+
/**
|
|
287
|
+
* Initialize the widget with configuration
|
|
288
|
+
* @param config - Widget configuration
|
|
289
|
+
*/
|
|
290
|
+
init(config: SophiConfig): Promise<void>;
|
|
291
|
+
/**
|
|
292
|
+
* Send user data to the widget
|
|
293
|
+
* @param data - User data object
|
|
294
|
+
*/
|
|
295
|
+
sendUserData(data: UserData): void;
|
|
296
|
+
updateUserCart(cart: IUserCart): void;
|
|
297
|
+
/**
|
|
298
|
+
* Merge guest user with logged-in user
|
|
299
|
+
* This can be called anytime, even before widget initialization
|
|
300
|
+
* @param guestId - The guest client ID (previous clientId)
|
|
301
|
+
* @param userId - The logged-in user ID (new externalUserId)
|
|
302
|
+
* @param apiKey - API key for authentication (required if widget not initialized)
|
|
303
|
+
* @returns Promise with merge result data
|
|
304
|
+
*/
|
|
305
|
+
mergeUser(guestId: string, userId: string, apiKey?: string): Promise<MergeUserData>;
|
|
306
|
+
/**
|
|
307
|
+
* Static method to merge guest user with logged-in user
|
|
308
|
+
* Use this when you don't have a widget instance or want to merge before initialization
|
|
309
|
+
* @param guestId - The guest client ID (previous clientId)
|
|
310
|
+
* @param userId - The logged-in user ID (new externalUserId)
|
|
311
|
+
* @param apiKey - API key for authentication
|
|
312
|
+
* @returns Promise with merge result data
|
|
313
|
+
*/
|
|
314
|
+
static mergeUser(guestId: string, userId: string, apiKey: string): Promise<MergeUserData>;
|
|
315
|
+
private onDestroy;
|
|
316
|
+
/**
|
|
317
|
+
* Register an event listener
|
|
318
|
+
* @param event - Event name
|
|
319
|
+
* @param handler - Event handler function
|
|
320
|
+
*/
|
|
321
|
+
on<K extends EventName>(event: K, handler: EventHandler<SophiEventMap[K]>): void;
|
|
322
|
+
/**
|
|
323
|
+
* Unregister an event listener
|
|
324
|
+
* @param event - Event name
|
|
325
|
+
* @param handler - Event handler function to remove
|
|
326
|
+
*/
|
|
327
|
+
off<K extends EventName>(event: K, handler: EventHandler<SophiEventMap[K]>): void;
|
|
328
|
+
/**
|
|
329
|
+
* Show the widget
|
|
330
|
+
*/
|
|
331
|
+
show(): void;
|
|
332
|
+
/**
|
|
333
|
+
* Hide the widget
|
|
334
|
+
*/
|
|
335
|
+
hide(): void;
|
|
336
|
+
/**
|
|
337
|
+
* Destroy the widget and cleanup resources
|
|
338
|
+
* Waits 300ms after sending destroy message to give iframe time to cleanup
|
|
339
|
+
*/
|
|
340
|
+
destroy(): Promise<void>;
|
|
341
|
+
/**
|
|
342
|
+
* Check if widget is initialized
|
|
343
|
+
*/
|
|
344
|
+
isReady(): boolean;
|
|
345
|
+
/**
|
|
346
|
+
* Validate configuration
|
|
347
|
+
*/
|
|
348
|
+
private validateConfig;
|
|
349
|
+
/**
|
|
350
|
+
* Create authentication session with the API
|
|
351
|
+
*/
|
|
352
|
+
private createAuthSession;
|
|
353
|
+
/**
|
|
354
|
+
* Send session data to iframe
|
|
355
|
+
*/
|
|
356
|
+
private sendSessionData;
|
|
357
|
+
/**
|
|
358
|
+
* Resolve container element from selector or element
|
|
359
|
+
*/
|
|
360
|
+
private resolveContainer;
|
|
361
|
+
/**
|
|
362
|
+
* Create and mount the iframe
|
|
363
|
+
*/
|
|
364
|
+
private createIframe;
|
|
365
|
+
/**
|
|
366
|
+
* Build iframe URL with query parameters
|
|
367
|
+
*/
|
|
368
|
+
private buildIframeUrl;
|
|
369
|
+
/**
|
|
370
|
+
* Setup postMessage listener for iframe communication
|
|
371
|
+
*/
|
|
372
|
+
private setupMessageListener;
|
|
373
|
+
/**
|
|
374
|
+
* Handle incoming messages from iframe
|
|
375
|
+
*/
|
|
376
|
+
private handleIncomingMessage;
|
|
377
|
+
/**
|
|
378
|
+
* Validate add_to_cart message structure
|
|
379
|
+
*/
|
|
380
|
+
private isValidAddToCartMessage;
|
|
381
|
+
/**
|
|
382
|
+
* Post message to iframe
|
|
383
|
+
*/
|
|
384
|
+
private postMessage;
|
|
385
|
+
/**
|
|
386
|
+
* Emit event to registered listeners
|
|
387
|
+
*/
|
|
388
|
+
private emit;
|
|
389
|
+
/**
|
|
390
|
+
* Emit error event
|
|
391
|
+
*/
|
|
392
|
+
private emitError;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* API Service for Sophi authentication and session management
|
|
397
|
+
*/
|
|
398
|
+
declare class ApiService {
|
|
399
|
+
private baseUrl;
|
|
400
|
+
private apiKey;
|
|
401
|
+
private clientId;
|
|
402
|
+
constructor(baseUrl: string, apiKey: string, clientId: string);
|
|
403
|
+
/**
|
|
404
|
+
* Create a client session by calling the authentication endpoint
|
|
405
|
+
* @param request - Session request with externalUserId and optional metadata
|
|
406
|
+
* @returns Session data including accessToken, sessionId, etc.
|
|
407
|
+
*/
|
|
408
|
+
createSession(request: AuthSessionRequest): Promise<SessionData>;
|
|
409
|
+
/**
|
|
410
|
+
* Validate session data
|
|
411
|
+
*/
|
|
412
|
+
validateSessionData(sessionData: SessionData): boolean;
|
|
413
|
+
/**
|
|
414
|
+
* Merge guest user with logged-in user
|
|
415
|
+
* @param request - Merge request with guestId and userId
|
|
416
|
+
* @returns Merge result data including the resulting clientId
|
|
417
|
+
*/
|
|
418
|
+
mergeUser(request: MergeUserRequest): Promise<MergeUserData>;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Default configuration constants for Sophi Widget
|
|
423
|
+
*/
|
|
424
|
+
declare const DEFAULT_CONFIG: {
|
|
425
|
+
/**
|
|
426
|
+
* Default width of the widget iframe
|
|
427
|
+
*/
|
|
428
|
+
readonly WIDTH: "100%";
|
|
429
|
+
/**
|
|
430
|
+
* Default height of the widget iframe
|
|
431
|
+
*/
|
|
432
|
+
readonly HEIGHT: "600px";
|
|
433
|
+
};
|
|
434
|
+
/**
|
|
435
|
+
* API base URLs for different environments
|
|
436
|
+
*
|
|
437
|
+
* Note: These URLs will be visible in the client-side bundle.
|
|
438
|
+
* This is normal and safe - the real security is in your API key validation.
|
|
439
|
+
*
|
|
440
|
+
* To use different URLs, modify these values before building the SDK.
|
|
441
|
+
*
|
|
442
|
+
* Production API URL - update before building for production
|
|
443
|
+
*/
|
|
444
|
+
declare const API_BASE_URLS: "http://10.0.2.127:80";
|
|
445
|
+
/**
|
|
446
|
+
* API endpoints configuration
|
|
447
|
+
*/
|
|
448
|
+
declare const API_ENDPOINTS: {
|
|
449
|
+
/**
|
|
450
|
+
* Session creation endpoint path
|
|
451
|
+
*/
|
|
452
|
+
readonly CREATE_SESSION: "/api/v1/auth/client/session";
|
|
453
|
+
};
|
|
454
|
+
/**
|
|
455
|
+
* Error messages
|
|
456
|
+
*/
|
|
457
|
+
declare const ERROR_MESSAGES: {
|
|
458
|
+
readonly MISSING_API_KEY: "API key is required and must be a string";
|
|
459
|
+
readonly MISSING_CLIENT_ID: "Client ID is required and must be a string";
|
|
460
|
+
readonly MISSING_USER_ID: "User ID is required and must be a string";
|
|
461
|
+
readonly MISSING_CONTAINER: "Container is required";
|
|
462
|
+
readonly MISSING_IFRAME_URL: "Iframe URL is required and must be a string";
|
|
463
|
+
readonly INVALID_IFRAME_URL: "Invalid iframe URL";
|
|
464
|
+
readonly INVALID_ENVIRONMENT: "Invalid environment. Must be 'dev', 'test', 'staging', or 'production'";
|
|
465
|
+
readonly CONTAINER_NOT_FOUND: "Container element not found";
|
|
466
|
+
readonly NOT_INITIALIZED: "Widget not initialized. Call init() first.";
|
|
467
|
+
readonly ALREADY_INITIALIZED: "Sophi Widget is already initialized. Call destroy() first to reinitialize.";
|
|
468
|
+
readonly SESSION_CREATION_FAILED: "Failed to create authentication session";
|
|
469
|
+
readonly INVALID_SESSION_DATA: "Invalid session data received from API";
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
export { API_BASE_URLS, API_ENDPOINTS, type AddToCartEvent, ApiService, type AuthSessionRequest, type AuthSessionResponse, DEFAULT_CONFIG, ERROR_MESSAGES, type EventHandler, type EventName, type IUserCart, type IUserCartItem, type IncomingMessage, type IncomingMessageType, IncomingMessageTypes, type MergeUserData, type MergeUserRequest, type MergeUserResponse, type OutgoingMessage, type OutgoingMessageType, OutgoingMessageTypes, type Product, type SessionData, type SophiConfig, type SophiEventMap, SophiWidget, type UserData };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
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
|
+
export{d as API_BASE_URLS,u as API_ENDPOINTS,o as ApiService,h as DEFAULT_CONFIG,n as ERROR_MESSAGES,p as IncomingMessageTypes,a as OutgoingMessageTypes,l as SophiWidget};//# sourceMappingURL=index.js.map
|
|
3
|
+
//# sourceMappingURL=index.js.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.js","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"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@usesophi/sophi-web-sdk",
|
|
3
|
+
"version": "0.1.0-beta.1",
|
|
4
|
+
"description": "Framework-agnostic SDK for embedding Sophi AI widget",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"dev": "tsup --watch",
|
|
27
|
+
"prepublishOnly": "npm run build"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"sophi",
|
|
31
|
+
"ai",
|
|
32
|
+
"widget",
|
|
33
|
+
"sdk",
|
|
34
|
+
"iframe",
|
|
35
|
+
"vanilla",
|
|
36
|
+
"typescript"
|
|
37
|
+
],
|
|
38
|
+
"author": "",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"tsup": "^8.0.0",
|
|
42
|
+
"typescript": "^5.3.0"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
}
|
|
47
|
+
}
|