embeddedaichatux 2.1.1 → 2.2.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.
Files changed (2) hide show
  1. package/EmbeddedChat.js +305 -188
  2. package/package.json +1 -1
package/EmbeddedChat.js CHANGED
@@ -1,5 +1,10 @@
1
1
  class EmbeddedChat {
2
+ static instanceCounter = 0;
3
+
2
4
  constructor(containerDiv, chatId, options) {
5
+ EmbeddedChat.instanceCounter++;
6
+ this.instanceId = `ec_${EmbeddedChat.instanceCounter}_${Date.now()}`;
7
+
3
8
  if (!containerDiv) {
4
9
  containerDiv = this.createChatContainer();
5
10
  }
@@ -8,16 +13,17 @@
8
13
  this.options = options || {};
9
14
  this.isPreview = this.options.isPreview || false;
10
15
  this.locale = options.locale || 'en';
11
- this.previewParam = this.isPreview ? "?isPreview=true" : "";
16
+
12
17
  this.serverUrl = this.options.serverUrl || 'https://app.bizdriver.ai/';
13
18
  this.height = options.height || 600; // Default height
14
- this.width = options.width || 320; // Default width
19
+ this.width = options.width || 400; // Default width
15
20
  this.marginBottom = options.marginBottom || 0;
16
21
  this.enableAnimation = options.enableAnimation !== false; // Default to true if not provided
17
- this.minimizedHeight = "180px"; // Sensible default instead of 30% calculation
22
+ this.theme = options.theme || this.containerDiv.dataset.theme || 'Standard';
23
+ this.minimizedHeight = this.theme === 'MinimalInput' ? "75px" : "80px"; // Default heights for different themes
18
24
  this.hasMeasuredSize = false;
19
25
  this.mouseInsideChat = false;
20
- this.hasRefreshed = false; // Flag to prevent endless loop
26
+
21
27
  this.conversationId = null;
22
28
  this.userTokenProvider = options.userTokenProvider;
23
29
  this.targetOrigin = "*";
@@ -25,19 +31,27 @@
25
31
  // Determine transition styles
26
32
  const transitionStyle = this.enableAnimation ? 'transition: height 0.3s ease, opacity 0.3s ease;' : '';
27
33
 
28
- this.positionStyle = this.containerDiv.dataset.position === 'in-place' ?
29
- `width:100%; height:${this.height}px; ${transitionStyle}` :
30
- `position: fixed; right: 1em; bottom: 0; width: ${this.width}px; z-index: 100000; max-width:90vw; max-height: 90vh; ${transitionStyle}`;
34
+ this.position = this.options.position || this.containerDiv.dataset.position || 'fixed';
35
+ this.positionStyle = this.position === 'in-place' ?
36
+ `position: relative; width:100%; height:${this.height}px; ${transitionStyle}` :
37
+ (this.theme === 'MinimalInput' ?
38
+ `position: fixed; left: 0; right: 0; margin-left: auto; margin-right: auto; width: 100%; max-width: ${this.width}px; bottom: ${this.marginBottom}px; z-index: 100000; max-height: 90vh; ${transitionStyle}` :
39
+ `position: fixed; right: 1em; bottom: ${this.marginBottom}px; width: 100%; max-width: ${this.width}px; z-index: 100000; max-height: 90vh; ${transitionStyle}`);
31
40
  this.mode = options.mode || 'Chat'; // default to 'chat'
32
41
  this.minimizeOnScroll = false; // Default to false
33
- if (this.mode === 'ContactForm') {
34
- // Adjust position style for contact form if mode is 'ContactForm'
35
- this.positionStyle = `width:100%; height:${this.height}px; ${transitionStyle}`;
42
+ if (this.mode === 'ContactForm' || this.mode === 'PartnerOffers') {
43
+ // Adjust position style for contact form/offers - force relative/absolute to occupy container
44
+ this.positionStyle = `position: relative; width:100%; height:${this.height}px; ${transitionStyle}`;
45
+ this.containerDiv.style.position = 'relative';
36
46
  this.containerDiv.style.height = `${this.height}px`;
37
47
  }
38
48
 
39
49
  this.conversationId = this.getStoredConversationId();
40
50
  this.sessionInfo = options.sessionInfo || null;
51
+
52
+ // Initialize window event listeners only once per instance
53
+ this.addWindowEventListeners();
54
+
41
55
  this.init();
42
56
  }
43
57
 
@@ -82,31 +96,47 @@
82
96
  ? this.serverUrl.slice(0, -1)
83
97
  : this.serverUrl;
84
98
  const baseIframeUrl = `${cleanedServerUrl}/ChatUX/${this.chatId}`;
85
- const minimizedPositionStyle = `
86
- position: fixed; right: 0; bottom: 0;
87
- width: 150px;
88
- height: ${this.minimizedHeight};
89
- z-index: 100000;
90
- ${this.enableAnimation ? 'transition: height 0.3s ease, opacity 0.3s ease;' : ''}
91
- `;
99
+ const transitionStyle = this.enableAnimation ? 'transition: height 0.3s ease, opacity 0.3s ease;' : '';
100
+
101
+ // Recalculate positionStyle conditionally in case theme changed dynamically
102
+ this.positionStyle = this.position === 'in-place' ?
103
+ `position: relative; width:100%; height:${this.height}px; ${transitionStyle}` :
104
+ (this.theme === 'MinimalInput' ?
105
+ `position: fixed; left: 0; right: 0; margin-left: auto; margin-right: auto; bottom: ${this.marginBottom}px; width: 95vw; max-width: ${this.width}px; z-index: 100000; max-height: 90vh; pointer-events: auto; ${transitionStyle}` :
106
+ `position: fixed; right: 1em; bottom: ${this.marginBottom}px; width: ${this.width}px; z-index: 100000; max-width:90vw; max-height: 90vh; pointer-events: auto; ${transitionStyle}`);
107
+
108
+ const minimizedPositionStyle = this.position === 'in-place' ?
109
+ `position: relative; width: 100%; height: ${this.minimizedHeight}; ${transitionStyle}` :
110
+ (this.theme === 'MinimalInput' ?
111
+ `position: fixed; left: 0; right: 0; margin-left: auto; margin-right: auto; bottom: ${this.marginBottom}px; width: 100vw; max-width: ${Math.min(300, this.width) + 40}px; height: ${this.minimizedHeight}; z-index: 100000; pointer-events: auto; ${transitionStyle}` :
112
+ `position: fixed; right: 0; bottom: ${this.marginBottom}px; width: 150px; height: ${this.minimizedHeight}; z-index: 100000; max-width: 95vw; pointer-events: auto; ${transitionStyle}`);
92
113
 
93
114
  const params = {
94
115
  isPreview: this.isPreview,
95
116
  mode: this.mode,
117
+ theme: this.theme,
96
118
  locale: this.locale,
97
- conversationId: this.conversationId
119
+ conversationId: this.conversationId,
120
+ instanceId: this.instanceId,
121
+ customStyle: this.options.customStyle,
122
+ customJs: this.options.customJs
98
123
  };
99
124
 
125
+ const iframeId = `ec-iframe-${this.instanceId}`;
126
+ const minimizedIframeId = `ec-minimized-${this.instanceId}`;
127
+
100
128
  const iframeHtml = `
101
129
  <iframe
102
- id="embedded-chat"
130
+ id="${iframeId}"
131
+ title="AI chatbot"
103
132
  style="${this.positionStyle}; display: none; border:none; overflow:hidden;"
104
133
  frameborder="0"
105
134
  sandbox="allow-same-origin allow-scripts allow-popups allow-forms allow-top-navigation-by-user-activation"
106
135
  src="${this.buildIframeUrl(baseIframeUrl, params)}"
107
136
  ></iframe>
108
137
  <iframe
109
- id="embedded-chat-minimized"
138
+ id="${minimizedIframeId}"
139
+ title="AI chatbot"
110
140
  style="${minimizedPositionStyle}; display: none; border:none; overflow:hidden;"
111
141
  frameborder="0"
112
142
  sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
@@ -116,27 +146,34 @@
116
146
 
117
147
  // inject and capture references
118
148
  this.containerDiv.innerHTML = iframeHtml;
119
- this.iframe = this.containerDiv.querySelector("#embedded-chat");
120
- this.minimizedIframe = this.containerDiv.querySelector("#embedded-chat-minimized");
149
+ this.iframe = this.containerDiv.querySelector(`#${iframeId}`);
150
+ this.minimizedIframe = this.containerDiv.querySelector(`#${minimizedIframeId}`);
121
151
 
122
152
  // Wait for main iframe to load
123
153
  if (this.iframe) {
124
154
  await this.waitForIframeLoad(this.iframe);
125
155
  }
126
156
 
127
- this.addEventListeners();
157
+ this.addIframeEventListeners();
128
158
 
129
159
  // Send auth to iframes after load
130
160
  await this.postAuthToIframes();
131
161
 
132
- // restore your ContactForm logic
133
- if (this.mode === 'ContactForm') {
162
+ // Restore ContactForm logic
163
+ if (this.mode === 'ContactForm' || this.mode === 'PartnerOffers') {
134
164
  this.iframe.style.maxWidth = '100%';
135
165
  this.iframe.style.display = 'block';
136
166
  }
167
+
168
+ // Apply pointer-events to container if theme is MinimalInput to prevent blocking page
169
+ if (this.theme === 'MinimalInput' && this.position !== 'in-place') {
170
+ this.containerDiv.style.pointerEvents = 'none';
171
+ } else {
172
+ this.containerDiv.style.pointerEvents = 'auto'; // Reset for other themes
173
+ }
137
174
  }
138
175
 
139
- addEventListeners() {
176
+ addIframeEventListeners() {
140
177
  if (!this.iframe || !this.minimizedIframe) {
141
178
  console.error('EmbeddedChat: iframe or minimizedIframe is not defined when trying to add event listeners.', this.iframe, this.minimizedIframe);
142
179
  return;
@@ -145,9 +182,12 @@
145
182
  this.iframe.addEventListener("mouseleave", () => { this.mouseInsideChat = false; });
146
183
  this.minimizedIframe.addEventListener("mouseenter", () => { this.mouseInsideChat = true; });
147
184
  this.minimizedIframe.addEventListener("mouseleave", () => { this.mouseInsideChat = false; });
185
+ }
186
+
187
+ addWindowEventListeners() {
188
+ if (this.windowListenersAdded) return;
148
189
 
149
190
  window.addEventListener("message", (e) => {
150
- // Only trust messages from our server (when targetOrigin is not "*")
151
191
  if (this.targetOrigin !== "*" && e.origin !== this.targetOrigin) return;
152
192
  this.handleMessage(e);
153
193
  });
@@ -158,31 +198,35 @@
158
198
  }
159
199
  });
160
200
 
161
- window.addEventListener("load", () => {
201
+ const handleResizeTrigger = () => {
162
202
  this.postResizeMessage();
163
- });
203
+ };
164
204
 
165
- window.addEventListener("resize", () => {
166
- this.postResizeMessage();
167
- });
205
+ if (document.readyState === 'complete') {
206
+ handleResizeTrigger();
207
+ } else {
208
+ window.addEventListener("load", handleResizeTrigger);
209
+ }
210
+
211
+ window.addEventListener("resize", handleResizeTrigger);
212
+ this.windowListenersAdded = true;
168
213
  }
169
214
 
170
215
  postResizeMessage() {
171
216
  if (this.iframe?.contentWindow) {
172
217
  this.iframe.contentWindow.postMessage({
173
218
  message: "resize",
174
- width: window.innerWidth, // Ensure correct size
175
- height: window.innerHeight
219
+ width: window.innerWidth,
220
+ height: window.innerHeight,
221
+ instanceId: this.instanceId
176
222
  }, this.targetOrigin);
177
223
  }
178
224
  }
179
225
 
180
226
  handleMessage(e) {
181
- // Only trust messages from our server (when targetOrigin is not "*")
182
227
  if (this.targetOrigin !== "*" && e.origin !== this.targetOrigin) return;
183
228
 
184
229
  if (typeof e.data !== "object") {
185
- // Preserve original handling for string messages
186
230
  if (typeof e.data === "string") {
187
231
  if (e.data === "minimize") {
188
232
  this.animateMinimize();
@@ -205,73 +249,102 @@
205
249
  const msg = (data.message || data.type || "").toString();
206
250
  const incomingChatId = (data.chatId || "").toString().toLowerCase();
207
251
  const targetChatId = (this.chatId || "").toString().toLowerCase();
252
+ const incomingInstanceId = data.instanceId;
208
253
 
209
- // Bail out if chatId doesn't match and is provided
210
- if (incomingChatId && targetChatId && incomingChatId !== targetChatId) return;
254
+ if (incomingInstanceId && incomingInstanceId !== this.instanceId) return;
255
+ if (!incomingInstanceId && incomingChatId && targetChatId && incomingChatId !== targetChatId) return;
211
256
 
212
- // Allow the iframe to request a fresh token
213
257
  if (data.type === "requestAuth") {
214
258
  this.postAuthToIframes();
215
259
  return;
216
260
  }
217
261
 
218
- // Priority 1: Handle minimized size updates (CRITICAL: Bail out early and DO NOT pollute configuration)
219
262
  if (msg === "updateMinimizedSize") {
220
- // Support both new and legacy keys for compatibility with cached scripts
221
263
  const h = data.minimizedHeight || data.height;
222
264
  const w = data.minimizedWidth || data.width;
223
265
  this.updateMinimizedSize(h, w);
224
266
  return;
225
267
  }
226
268
 
227
- const updates = {};
269
+ if (msg === "collapseMinimalInput") {
270
+ if (this.minimizedIframe) {
271
+ this.minimizedIframe.style.width = '80px';
272
+ this.minimizedIframe.style.height = '80px';
273
+ this.minimizedIframe.style.left = 'auto';
274
+ this.minimizedIframe.style.right = '0';
275
+ this.minimizedIframe.style.marginLeft = '0';
276
+ this.minimizedIframe.style.marginRight = '0';
277
+ this.minimizedIframe.style.pointerEvents = 'auto';
278
+ }
279
+ return;
280
+ }
228
281
 
282
+ if (msg === "restoreMinimalInput") {
283
+ if (this.minimizedIframe) {
284
+ this.minimizedIframe.style.width = '100vw';
285
+ this.minimizedIframe.style.maxWidth = `${Math.min(300, this.width) + 40}px`;
286
+ this.minimizedIframe.style.height = this.theme === 'MinimalInput' ? "75px" : this.minimizedHeight;
287
+ this.minimizedIframe.style.left = '0';
288
+ this.minimizedIframe.style.right = '0';
289
+ this.minimizedIframe.style.marginLeft = 'auto';
290
+ this.minimizedIframe.style.marginRight = 'auto';
291
+ this.minimizedIframe.style.pointerEvents = 'auto';
292
+ }
293
+ if (this.theme === 'MinimalInput' && this.position !== 'in-place') {
294
+ this.containerDiv.style.pointerEvents = 'none';
295
+ }
296
+ return;
297
+ }
298
+
299
+ const updates = {};
229
300
  if (data.type === "setMinimizeOnScroll" || data.minimizeOnScroll !== undefined) {
230
301
  const minimizeValue = data.type === "setMinimizeOnScroll" ? data.value : data.minimizeOnScroll;
231
302
  this.minimizeOnScroll = minimizeValue === true || minimizeValue === "true";
232
303
  }
233
304
 
234
- if (data.locale) {
235
- updates.locale = data.locale;
236
- }
237
- if (data.width) {
238
- updates.width = data.width;
239
- }
240
- if (data.height) {
241
- updates.height = data.height;
242
- }
243
- if (data.marginBottom) {
244
- updates.marginBottom = data.marginBottom;
245
- }
246
- if (data.scale) {
247
- updates.scale = data.scale;
248
- }
305
+ if (data.locale) updates.locale = data.locale;
306
+ if (data.width) updates.width = data.width;
307
+ if (data.height) updates.height = data.height;
308
+ if (data.marginBottom) updates.marginBottom = data.marginBottom;
309
+ if (data.scale) updates.scale = data.scale;
310
+ if (data.theme) updates.theme = data.theme;
249
311
 
250
- // Apply iframe updates in one go
251
312
  if (Object.keys(updates).length > 0) {
252
313
  this.applyIframeUpdates(updates);
253
314
  }
254
315
 
255
- // Handle maximize/minimize/navigate
256
316
  if (msg === "minimize") {
257
- if (this.mode !== 'ContactForm') {
317
+ if (typeof this.options.onMinimize === 'function') {
318
+ this.options.onMinimize();
319
+ } else if (this.mode !== 'ContactForm' && this.mode !== 'PartnerOffers') {
258
320
  this.animateMinimize();
259
321
  }
260
322
  } else if (msg === "show" || msg === "maximize") {
261
323
  const animate = data.animate === true;
262
- this.showMaximized(animate);
324
+ this.showMaximized(animate, data.initialMessage);
263
325
  } else if (msg === "navigate" && data.url) {
264
326
  window.location.href = data.url;
327
+ } else if (data.type === "switchToChat" || msg === "switchToChat") {
328
+ if (typeof this.options.onSwitchToChat === 'function') {
329
+ this.options.onSwitchToChat(data.partner);
330
+ } else {
331
+ this.setMode('Chat', { ...data, initialState: 'Maximized' });
332
+ }
333
+ } else if (data.type === "switchToContactForm" || msg === "switchToContactForm") {
334
+ if (typeof this.options.onSwitchToContactForm === 'function') {
335
+ this.options.onSwitchToContactForm(data.partner);
336
+ } else {
337
+ this.setMode('ContactForm', { partner: data.partner, initialState: 'Maximized' });
338
+ }
265
339
  }
266
340
 
267
- // Handle conversationId update
268
341
  if (data.conversationId) {
269
342
  this.setConversationId(data.conversationId);
270
343
  }
271
344
  }
272
345
 
273
346
  animateMinimize() {
274
- if (this.mode !== 'ContactForm') {
347
+ if (this.mode !== 'ContactForm' && this.mode !== 'PartnerOffers') {
275
348
  this.iframe.style.height = this.minimizedHeight;
276
349
  this.iframe.style.opacity = '0';
277
350
  setTimeout(() => {
@@ -281,20 +354,18 @@
281
354
  this.minimizedIframe.style.height = this.minimizedHeight;
282
355
  this.minimizedIframe.style.opacity = '1';
283
356
 
284
- // Request a height update after showing
285
357
  if (this.minimizedIframe.contentWindow) {
286
- this.minimizedIframe.contentWindow.postMessage({ message: "requestHeightUpdate" }, this.targetOrigin);
358
+ this.minimizedIframe.contentWindow.postMessage({ message: "requestHeightUpdate", instanceId: this.instanceId }, this.targetOrigin);
287
359
  }
288
360
  }, this.enableAnimation ? 300 : 0);
289
361
  }
290
362
  }
291
363
 
292
364
  updateMinimizedSize(contentHeight, contentWidth) {
293
- if (this.mode === 'ContactForm') return;
365
+ if (this.mode === 'ContactForm' || this.mode === 'PartnerOffers') return;
294
366
 
295
367
  this.hasMeasuredSize = true;
296
368
 
297
- // Apply height update
298
369
  if (contentHeight && contentHeight >= 10) {
299
370
  const newHeight = `${contentHeight}px`;
300
371
  this.minimizedHeight = newHeight;
@@ -303,119 +374,153 @@
303
374
  }
304
375
  }
305
376
 
306
- // Apply width update
307
377
  if (contentWidth && contentWidth >= 10) {
308
- if (this.minimizedIframe) {
378
+ if (this.minimizedIframe && this.theme !== 'MinimalInput') {
309
379
  this.minimizedIframe.style.width = `${contentWidth}px`;
310
380
  }
311
381
  }
312
382
  }
313
383
 
314
384
  animateMaximize() {
315
- if (this.mode !== 'ContactForm') {
316
- // Start the animation for minimizing iframe
317
- this.minimizedIframe.style.height = `${this.height}px`;
318
- this.minimizedIframe.style.opacity = '0';
319
-
320
- setTimeout(() => {
321
- // Hide the minimized iframe
385
+ if (this.mode !== 'ContactForm' && this.mode !== 'PartnerOffers') {
386
+ const onMinimizedAnimateEnd = () => {
387
+ this.minimizedIframe.removeEventListener("transitionend", onMinimizedAnimateEnd);
322
388
  this.minimizedIframe.style.display = "none";
323
- this.minimizedIframe.style.opacity = '1'; // Reset opacity
389
+ this.minimizedIframe.style.opacity = '1';
324
390
 
325
- // Show and animate the main iframe
326
391
  this.iframe.style.display = "block";
327
- this.iframe.style.height = this.minimizedHeight; // Start with minimized height
328
- this.iframe.style.opacity = '0'; // Start with zero opacity
392
+ this.iframe.style.height = this.minimizedHeight;
393
+ this.iframe.style.opacity = "0";
329
394
 
330
- // Trigger the maximize animation
331
- setTimeout(() => {
332
- this.iframe.style.height = `${this.height}px`; // Animate to full height
333
- this.iframe.style.opacity = '1'; // Animate opacity to full
334
- }, 10); // Small delay to allow display changes to take effect
335
- }, this.enableAnimation ? 300 : 0); // Match the minimize animation duration
395
+ this.iframe.offsetHeight;
396
+
397
+ this.iframe.style.height = `${this.height}px`;
398
+ this.iframe.style.opacity = "1";
399
+ };
400
+
401
+ if (this.enableAnimation) {
402
+ this.minimizedIframe.addEventListener("transitionend", onMinimizedAnimateEnd);
403
+ this.minimizedIframe.style.height = this.minimizedHeight;
404
+ this.minimizedIframe.style.opacity = "0";
405
+
406
+ // Notify iframe when maximize transition finishes to ensure final scroll-into-view
407
+ this.iframe.addEventListener("transitionend", (e) => {
408
+ if (e.propertyName === 'height' && this.iframe.style.display !== 'none') {
409
+ if (this.iframe.contentWindow) {
410
+ this.iframe.contentWindow.postMessage({ message: "maximizeComplete", instanceId: this.instanceId }, this.targetOrigin);
411
+ }
412
+ }
413
+ }, { once: true });
414
+ } else {
415
+ onMinimizedAnimateEnd();
416
+ }
336
417
  }
337
418
  }
338
419
 
339
- applyIframeUpdates({ locale, width, height, marginBottom, scale } = {}) {
340
- // Update locale if provided and different (but do not update the iframe URL)
420
+ applyIframeUpdates({ locale, width, height, marginBottom, scale, theme } = {}) {
421
+ if (theme && typeof theme === 'string' && theme !== this.theme) {
422
+ this.theme = theme;
423
+ console.log(`Theme updated to: ${this.theme}`);
424
+ this.minimizedHeight = this.theme === 'MinimalInput' ? "75px" : "80px";
425
+ this.updateIframes();
426
+ return;
427
+ }
428
+
341
429
  if (locale && typeof locale === 'string' && locale !== this.locale) {
342
430
  this.locale = locale;
343
431
  console.log(`Locale stored locally: ${this.locale}`);
344
432
  }
345
433
 
346
- // Update width if provided and different
347
434
  const parsedWidth = parseInt(width, 10);
348
435
  if (!isNaN(parsedWidth) && parsedWidth > 0 && parsedWidth !== this.width) {
349
436
  this.width = parsedWidth;
350
437
  console.log(`Width updated to: ${this.width}px`);
351
438
 
352
- // Apply width to container
353
- this.containerDiv.style.width = `${this.width}px`;
354
-
355
- // Apply width to the main iframe
356
- if (this.iframe) {
357
- this.iframe.style.width = `${this.width}px`;
439
+ if (this.position !== 'in-place') {
440
+ if (this.theme === 'MinimalInput') {
441
+ this.containerDiv.style.width = '100vw'; // Use 100vw but keep pointerEvents none
442
+ this.containerDiv.style.left = '0';
443
+ this.containerDiv.style.right = '0';
444
+ this.containerDiv.style.marginLeft = 'auto';
445
+ this.containerDiv.style.marginRight = 'auto';
446
+ this.containerDiv.style.pointerEvents = 'none'; // Prevent invisible area from blocking clicks
447
+ if (this.iframe) {
448
+ this.iframe.style.width = '95vw';
449
+ this.iframe.style.maxWidth = `${this.width}px`;
450
+ this.iframe.style.pointerEvents = 'auto';
451
+ this.iframe.style.left = '0';
452
+ this.iframe.style.right = '0';
453
+ this.iframe.style.margin = 'auto';
454
+ }
455
+ if (this.minimizedIframe) {
456
+ this.minimizedIframe.style.width = '100vw'; // Use 100vw to allow centering
457
+ this.minimizedIframe.style.maxWidth = `${Math.min(300, this.width) + 40}px`;
458
+ this.minimizedIframe.style.pointerEvents = 'auto';
459
+ this.minimizedIframe.style.left = '0';
460
+ this.minimizedIframe.style.right = '0';
461
+ this.minimizedIframe.style.margin = 'auto';
462
+ }
463
+ } else {
464
+ this.containerDiv.style.width = `${this.width}px`;
465
+ this.containerDiv.style.pointerEvents = 'auto'; // Ensure auto for other themes
466
+ if (this.iframe) this.iframe.style.width = `${this.width}px`;
467
+ }
468
+ } else {
469
+ this.containerDiv.style.width = '100%';
470
+ this.containerDiv.style.pointerEvents = 'auto';
471
+ if (this.iframe) this.iframe.style.width = '100%';
358
472
  }
359
473
  }
360
474
 
361
- // Apply scale if provided
362
475
  if (typeof scale === 'number' && scale > 0) {
363
- if (this.iframe) {
364
- this.iframe.style.transform = `scale(${scale})`;
365
- this.iframe.style.transformOrigin = "bottom right";
366
- }
367
- if (this.minimizedIframe) {
368
- this.minimizedIframe.style.transform = `scale(${scale})`;
369
- this.minimizedIframe.style.transformOrigin = "bottom right";
370
- }
476
+ this.applyScale(scale);
371
477
  console.log(`Scale applied: ${scale}`);
372
478
  }
373
479
 
374
- // Handle height and marginBottom
375
480
  let parsedHeight = parseInt(height, 10);
376
481
  let parsedMarginBottom = parseInt(marginBottom, 10);
377
482
 
378
- // Default to existing values if not provided or invalid
379
- if (isNaN(parsedHeight) || parsedHeight <= 0) {
380
- parsedHeight = this.height;
381
- }
483
+ if (isNaN(parsedHeight) || parsedHeight <= 0) parsedHeight = this.height;
382
484
  if (isNaN(parsedMarginBottom) || parsedMarginBottom < 0) {
383
485
  parsedMarginBottom = (typeof this.marginBottom === 'number') ? this.marginBottom : 0;
384
486
  }
385
487
 
386
- // Clamp so that height + marginBottom doesn't exceed 90% of viewport
387
488
  const viewportHeight = window.innerHeight;
388
489
  let effectiveHeight = parsedHeight;
389
490
  if (effectiveHeight + parsedMarginBottom > 0.9 * viewportHeight) {
390
491
  effectiveHeight = Math.max(100, 0.9 * viewportHeight - parsedMarginBottom);
391
492
  }
392
493
 
393
- // Only apply if there's an actual change
394
494
  if (parsedHeight !== this.height || parsedMarginBottom !== this.marginBottom) {
395
495
  this.height = parsedHeight;
396
496
  this.marginBottom = parsedMarginBottom;
397
497
 
398
- // Only use the 30% estimate if we haven't received a real measurement yet
399
498
  if (!this.hasMeasuredSize) {
400
- this.minimizedHeight = `${this.height * 0.3}px`;
499
+ this.minimizedHeight = this.theme === 'MinimalInput' ? "75px" : "80px";
401
500
  }
402
501
 
403
- console.log(`Height updated to: ${this.height}px (clamped to ${effectiveHeight}px for display), marginBottom: ${this.marginBottom}px`);
502
+ console.log(`Height updated to: ${this.height}px, marginBottom: ${this.marginBottom}px`);
404
503
 
405
- // Apply the updated height/marginBottom
406
- if (this.mode !== 'ContactForm') {
407
- if (this.containerDiv.dataset.position === 'in-place') {
408
- // "in-place" uses full width, sets containerDiv height
504
+ if (this.mode !== 'ContactForm' && this.mode !== 'PartnerOffers') {
505
+ if (this.position === 'in-place') {
506
+ this.containerDiv.style.position = 'relative';
507
+ this.containerDiv.style.width = '100%';
409
508
  this.containerDiv.style.height = `${effectiveHeight}px`;
410
509
  if (this.iframe) {
510
+ this.iframe.style.position = 'relative';
511
+ this.iframe.style.width = '100%';
411
512
  this.iframe.style.height = `${effectiveHeight}px`;
513
+ this.iframe.style.bottom = 'unset';
514
+ this.iframe.style.right = 'unset';
412
515
  }
413
516
  if (this.minimizedIframe) {
517
+ this.minimizedIframe.style.position = 'relative';
518
+ this.minimizedIframe.style.width = '100%';
414
519
  this.minimizedIframe.style.height = this.minimizedHeight;
520
+ this.minimizedIframe.style.bottom = 'unset';
521
+ this.minimizedIframe.style.right = 'unset';
415
522
  }
416
523
  } else {
417
- // position: fixed (typical floating chat)
418
- // Update bottom offset, main height
419
524
  if (this.iframe) {
420
525
  this.iframe.style.height = `${this.height}px`;
421
526
  this.iframe.style.bottom = `${this.marginBottom}px`;
@@ -423,32 +528,36 @@
423
528
  if (this.minimizedIframe) {
424
529
  this.minimizedIframe.style.height = this.minimizedHeight;
425
530
  this.minimizedIframe.style.bottom = `${this.marginBottom}px`;
531
+ if (this.theme === 'MinimalInput') {
532
+ this.minimizedIframe.style.width = '100vw';
533
+ this.minimizedIframe.style.maxWidth = `${Math.min(300, this.width) + 40}px`;
534
+ }
426
535
  }
427
536
  }
428
537
  } else {
429
- // ContactForm mode
430
538
  this.containerDiv.style.height = `${this.height}px`;
431
- if (this.iframe) {
432
- this.iframe.style.height = `${this.height}px`;
433
- }
539
+ if (this.iframe) this.iframe.style.height = `${this.height}px`;
434
540
  }
435
541
  }
436
542
  }
437
543
 
438
- showMaximized(animate = false) {
544
+ showMaximized(animate = false, initialMessage = null) {
439
545
  const schedulePostMessage = () => {
440
546
  if (this.iframe.contentWindow) {
441
547
  this.iframe.contentWindow.postMessage({
442
548
  message: "show",
443
- sessionInfo: this.sessionInfo || null
549
+ sessionInfo: this.sessionInfo || null,
550
+ instanceId: this.instanceId,
551
+ initialMessage: initialMessage
444
552
  }, this.targetOrigin);
445
553
  } else {
446
- // Schedule a single retry with a delay
447
554
  setTimeout(() => {
448
555
  if (this.iframe.contentWindow) {
449
556
  this.iframe.contentWindow.postMessage({
450
557
  message: "show",
451
- sessionInfo: this.sessionInfo || null
558
+ sessionInfo: this.sessionInfo || null,
559
+ instanceId: this.instanceId,
560
+ initialMessage: initialMessage
452
561
  }, this.targetOrigin);
453
562
  } else {
454
563
  console.warn("iframe contentWindow is not available.");
@@ -461,8 +570,7 @@
461
570
  this.animateMaximize();
462
571
  } else {
463
572
  this.minimizedIframe.style.display = "none";
464
- this.minimizedIframe.style.height = `${this.height}px`;
465
- this.minimizedIframe.style.opacity = ''; // Reset opacity to unset
573
+ this.minimizedIframe.style.opacity = '';
466
574
 
467
575
  this.iframe.style.display = "block";
468
576
  this.iframe.style.height = `${this.height}px`;
@@ -473,27 +581,30 @@
473
581
  }
474
582
 
475
583
  applyScale(scale) {
476
- if (this.mode !== 'ContactForm') {
477
- this.iframe.style.transform = `scale(${scale})`;
478
- this.iframe.style.transformOrigin = "bottom right";
479
- this.minimizedIframe.style.transform = `scale(${scale})`;
480
- this.minimizedIframe.style.transformOrigin = "bottom right";
584
+ if (this.mode !== 'ContactForm' && this.mode !== 'PartnerOffers') {
585
+ let origin = this.position === 'in-place' ? 'top center' : 'bottom right';
586
+ if (this.theme === 'MinimalInput') origin = 'bottom center';
587
+ if (this.iframe) {
588
+ this.iframe.style.transform = `scale(${scale})`;
589
+ this.iframe.style.transformOrigin = origin;
590
+ }
591
+ if (this.minimizedIframe) {
592
+ this.minimizedIframe.style.transform = `scale(${scale})`;
593
+ this.minimizedIframe.style.transformOrigin = origin;
594
+ }
481
595
  }
482
596
  }
483
597
 
484
598
  minimizeOnScrollAction() {
485
- if (this.mode !== 'ContactForm' && this.iframe.style.display !== "none") {
599
+ if (this.mode !== 'ContactForm' && this.mode !== 'PartnerOffers' && this.iframe.style.display !== "none") {
486
600
  this.animateMinimize();
487
601
  }
488
602
  }
489
603
 
490
604
  setSessionInfo(sessionInfo) {
491
- console.log('setSessionInfo called with sessionInfo:', sessionInfo);
492
605
  this.sessionInfo = sessionInfo;
493
-
494
- // Send session info immediately if the chat is already maximized
495
606
  if (this.iframe?.contentWindow && this.iframe.style.display !== "none") {
496
- this.iframe.contentWindow.postMessage({ type: 'setSessionInfo', sessionInfo: this.sessionInfo }, this.targetOrigin);
607
+ this.iframe.contentWindow.postMessage({ type: 'setSessionInfo', sessionInfo: this.sessionInfo, instanceId: this.instanceId }, this.targetOrigin);
497
608
  }
498
609
  }
499
610
 
@@ -505,86 +616,86 @@
505
616
  return chatContainer;
506
617
  }
507
618
 
619
+ maximizeWithText(text) {
620
+ this.showMaximized(true, text);
621
+ }
622
+
508
623
  buildIframeUrl(baseIframeUrl, params = {}) {
509
624
  const urlParams = new URLSearchParams();
510
-
511
- if (params.isPreview) {
512
- urlParams.set('isPreview', 'true');
513
- }
514
-
515
- if (params.mode === 'ContactForm') {
516
- urlParams.set('mode', 'ContactForm');
517
- }
518
-
519
- if (params.locale) {
520
- urlParams.set('locale', params.locale);
521
- }
522
-
523
- if (params.conversationId) {
524
- urlParams.set('conversationId', params.conversationId);
625
+ if (params.isPreview) urlParams.set('isPreview', 'true');
626
+ if (params.mode === 'ContactForm' || params.mode === 'PartnerOffers' || params.mode === 'Chat') {
627
+ urlParams.set('mode', params.mode);
525
628
  }
629
+ if (params.locale) urlParams.set('locale', params.locale);
630
+ if (params.conversationId) urlParams.set('conversationId', params.conversationId);
631
+ if (params.instanceId) urlParams.set('instanceId', params.instanceId);
526
632
 
527
633
  for (const [key, value] of Object.entries(params)) {
528
- if (key !== 'mode' && key !== 'isPreview' && key !== 'conversationId' && key !== 'userToken' && value !== null && value !== undefined) {
634
+ if (!['mode', 'isPreview', 'conversationId', 'instanceId', 'userToken'].includes(key) && value !== null && value !== undefined) {
529
635
  urlParams.set(key, value);
530
636
  }
531
637
  }
532
-
533
638
  return `${baseIframeUrl}?${urlParams.toString()}`;
534
639
  }
535
640
 
536
- waitForIframeLoad(iframe, timeout = 5000) {
537
- return new Promise((resolve, reject) => {
538
- // Check if the iframe is already loaded
539
- if (iframe.contentDocument || iframe.contentWindow) {
540
- resolve();
541
- return;
542
- }
543
-
544
- // Set up a timeout to avoid waiting indefinitely
641
+ waitForIframeLoad(iframe, timeout = 10000) {
642
+ return new Promise((resolve) => {
545
643
  const timeoutId = setTimeout(() => {
546
- reject(new Error('Iframe did not load within the timeout period.'));
644
+ console.warn('EmbeddedChat: Iframe load timeout, proceeding with initialization.');
645
+ resolve();
547
646
  }, timeout);
548
647
 
549
- // Add the load event listener
550
648
  iframe.addEventListener('load', () => {
551
- clearTimeout(timeoutId); // Clear the timeout
649
+ clearTimeout(timeoutId);
552
650
  resolve();
553
- }, { once: true }); // Ensure the listener is triggered only once
651
+ }, { once: true });
554
652
  });
555
653
  }
556
654
 
557
655
  async postAuthToIframes() {
558
- console.log('[EmbeddedChat] postAuthToIframes called');
559
- if (typeof this.userTokenProvider !== 'function') {
560
- console.warn('[EmbeddedChat] No userTokenProvider function provided');
561
- return;
562
- }
563
-
656
+ if (typeof this.userTokenProvider !== 'function') return;
564
657
  try {
565
658
  const userToken = await this.userTokenProvider();
566
-
567
- if (!userToken) {
568
- console.warn('[EmbeddedChat] userTokenProvider returned null/empty token');
569
- return;
570
- }
571
-
659
+ if (!userToken) return;
572
660
  if (this.iframe?.contentWindow) {
573
- console.log('[EmbeddedChat] Sending auth message to iframe with targetOrigin:', this.targetOrigin);
574
661
  this.iframe.contentWindow.postMessage({
575
662
  type: 'auth',
576
663
  userToken: userToken,
577
- chatId: this.chatId
664
+ chatId: this.chatId,
665
+ instanceId: this.instanceId
578
666
  }, this.targetOrigin);
579
- console.log('[EmbeddedChat] Auth message sent successfully');
580
- } else {
581
- console.error('[EmbeddedChat] iframe.contentWindow is not available');
582
667
  }
583
- // No longer send auth to minimizedIframe
584
668
  } catch (err) {
585
669
  console.error('[EmbeddedChat] Failed to get user token:', err);
586
670
  }
587
671
  }
672
+
673
+ setMode(newMode, options = {}) {
674
+ if (this.mode === newMode && (!options.chatId || options.chatId === this.chatId)) return;
675
+
676
+ console.log(`[EmbeddedChat] Setting mode to ${newMode}`, options);
677
+ this.mode = newMode;
678
+ if (options.chatId) this.chatId = options.chatId;
679
+ this.options = { ...this.options, ...options };
680
+ const transitionStyle = this.enableAnimation ? 'transition: height 0.3s ease, opacity 0.3s ease;' : '';
681
+ if (this.mode === 'ContactForm' || this.mode === 'PartnerOffers') {
682
+ this.positionStyle = `position: relative; width:100%; height:${this.height}px; ${transitionStyle}`;
683
+ this.containerDiv.style.position = 'relative';
684
+ this.containerDiv.style.height = `${this.height}px`;
685
+ } else {
686
+ this.positionStyle = this.position === 'in-place' ?
687
+ `position: relative; width:100%; height:${this.height}px; ${transitionStyle}` :
688
+ (this.theme === 'MinimalInput' ?
689
+ `position: fixed; left: 0; right: 0; margin-left: auto; margin-right: auto; bottom: ${this.marginBottom}px; width: ${this.width}px; z-index: 100000; max-width:90vw; max-height: 90vh; ${transitionStyle}` :
690
+ `position: fixed; right: 1em; bottom: ${this.marginBottom}px; width: ${this.width}px; z-index: 100000; max-width:90vw; max-height: 90vh; ${transitionStyle}`);
691
+ if (this.position === 'in-place') {
692
+ this.containerDiv.style.position = 'relative';
693
+ this.containerDiv.style.width = '100%';
694
+ this.containerDiv.style.height = `${this.height}px`;
695
+ }
696
+ }
697
+ this.updateIframes();
698
+ }
588
699
  }
589
700
 
590
701
  export function initEmbeddedChat(containerDiv, chatId, options) {
@@ -595,3 +706,9 @@ export function initContactForm(containerDiv, chatId, options) {
595
706
  const clonedOptions = { ...options, mode: 'ContactForm' };
596
707
  return new EmbeddedChat(containerDiv, chatId, clonedOptions);
597
708
  }
709
+
710
+ export function initOffers(options = {}) {
711
+ const chatId = options.chatConfigId;
712
+ const mergedOptions = { ...options, mode: 'PartnerOffers', height: options.height || 500 };
713
+ return new EmbeddedChat(options.container, chatId, mergedOptions);
714
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "embeddedaichatux",
3
- "version": "2.1.1",
3
+ "version": "2.2.1",
4
4
  "description": "A lightweight and customizable embedded AI chat UI component that seamlessly integrates into web applications, offering minimized and expanded views, with iframe-based content rendering.",
5
5
  "main": "EmbeddedChat.js",
6
6
  "scripts": {