cuoral-ionic 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -157,6 +157,89 @@ export class SupportPage implements OnInit, OnDestroy {
157
157
  }
158
158
  ```
159
159
 
160
+ ### Enable Page View Tracking (One-Time Setup)
161
+
162
+ To automatically track page views, add router tracking to your **app.component.ts** (or main app component):
163
+
164
+ ```typescript
165
+ import { Component } from '@angular/core';
166
+ import { Router, NavigationEnd } from '@angular/router';
167
+ import { Cuoral } from 'cuoral-ionic';
168
+
169
+ @Component({
170
+ selector: 'app-root',
171
+ templateUrl: 'app.component.html',
172
+ })
173
+ export class AppComponent {
174
+ private cuoral: Cuoral;
175
+
176
+ constructor(private router: Router) {
177
+ this.cuoral = new Cuoral({
178
+ publicKey: 'your-public-key-here',
179
+ email: 'user@example.com',
180
+ firstName: 'John',
181
+ lastName: 'Doe',
182
+ });
183
+ }
184
+
185
+ async ngOnInit() {
186
+ // Initialize Cuoral (intelligence auto-enabled from dashboard)
187
+ await this.cuoral.initialize();
188
+
189
+ // Track all page navigation automatically
190
+ this.router.events.subscribe(event => {
191
+ if (event instanceof NavigationEnd) {
192
+ this.cuoral.trackPageView(event.url);
193
+ }
194
+ });
195
+ }
196
+ }
197
+ ```
198
+
199
+ **That's it!** All page views are now automatically tracked. Console errors, network errors, and native crashes are tracked with zero additional setup.
200
+
201
+ ## User Logout & Session Management
202
+
203
+ When users log out, properly clear and end the session before destroying the Cuoral instance:
204
+
205
+ ```typescript
206
+ async logout() {
207
+ // Step 1: Clear and end the session
208
+ await this.cuoral.clearSession();
209
+
210
+ // Step 2: Destroy the Cuoral instance
211
+ this.cuoral.destroy();
212
+
213
+ // Step 3: Your logout logic
214
+ // Navigate to login, clear user data, etc.
215
+ }
216
+ ```
217
+
218
+ When a different user logs in on the same device, create a fresh Cuoral instance:
219
+
220
+ ```typescript
221
+ async login(email: string, firstName: string, lastName: string) {
222
+ // Create new instance for the new user
223
+ this.cuoral = new Cuoral({
224
+ publicKey: 'your-public-key-here',
225
+ email: email,
226
+ firstName: firstName,
227
+ lastName: lastName,
228
+ });
229
+
230
+ // Initialize for new user
231
+ await this.cuoral.initialize();
232
+ }
233
+ ```
234
+
235
+ **Important Notes:**
236
+
237
+ - `clearSession()` marks the session as ended on the backend
238
+ - `destroy()` cleans up local resources
239
+ - Always call `clearSession()` before `destroy()` on logout
240
+ - Create a new Cuoral instance for each user login
241
+ - Email, first name, and last name are all required for identified users
242
+
160
243
  ## Configuration
161
244
 
162
245
  ### CuoralOptions
package/dist/cuoral.d.ts CHANGED
@@ -56,6 +56,10 @@ export declare class Cuoral {
56
56
  * Get the widget URL for iframe embedding
57
57
  */
58
58
  getWidgetUrl(): string;
59
+ /**
60
+ * Clear and end the current session (call before logout)
61
+ */
62
+ clearSession(): Promise<void>;
59
63
  /**
60
64
  * Clean up resources
61
65
  */
@@ -1 +1 @@
1
- {"version":3,"file":"cuoral.d.ts","sourceRoot":"","sources":["../src/cuoral.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAeD;;GAEG;AACH,qBAAa,MAAM;IACjB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,KAAK,CAAC,CAAc;IAC5B,OAAO,CAAC,YAAY,CAAC,CAAqB;IAC1C,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAuC;IACpF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAwB;gBAElD,OAAO,EAAE,aAAa;IA2ClC;;OAEG;IACU,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAYxC;;OAEG;YACW,sBAAsB;IAwCpC;;OAEG;YACW,yBAAyB;IAoCvC;;OAEG;IACI,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,GAAG,GAAG,IAAI;IAM1D;;OAEG;IACI,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,GAAG,GAAG,IAAI;IAM7E;;OAEG;IACI,SAAS,IAAI,IAAI;IAMxB;;OAEG;IACI,UAAU,IAAI,IAAI;IAMzB;;OAEG;IACI,WAAW,IAAI,OAAO;IAI7B;;OAEG;IACI,YAAY,IAAI,MAAM;IAe7B;;OAEG;IACI,OAAO,IAAI,IAAI;IActB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAa5B;;OAEG;YACW,eAAe;IAmC7B;;OAEG;YACW,UAAU;IAmBxB;;OAEG;IACH,OAAO,CAAC,oBAAoB;CAuE7B"}
1
+ {"version":3,"file":"cuoral.d.ts","sourceRoot":"","sources":["../src/cuoral.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAeD;;GAEG;AACH,qBAAa,MAAM;IACjB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,KAAK,CAAC,CAAc;IAC5B,OAAO,CAAC,YAAY,CAAC,CAAqB;IAC1C,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAuC;IACpF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAwB;gBAElD,OAAO,EAAE,aAAa;IA2ClC;;OAEG;IACU,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAkBxC;;OAEG;YACW,sBAAsB;IAwCpC;;OAEG;YACW,yBAAyB;IAoCvC;;OAEG;IACI,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,GAAG,GAAG,IAAI;IAM1D;;OAEG;IACI,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,GAAG,GAAG,IAAI;IAM7E;;OAEG;IACI,SAAS,IAAI,IAAI;IASxB;;OAEG;IACI,UAAU,IAAI,IAAI;IAMzB;;OAEG;IACI,WAAW,IAAI,OAAO;IAI7B;;OAEG;IACI,YAAY,IAAI,MAAM;IAuB7B;;OAEG;IACU,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IA6C1C;;OAEG;IACI,OAAO,IAAI,IAAI;IActB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAa5B;;OAEG;YACW,eAAe;IAkC7B;;OAEG;YACW,UAAU;IAmBxB;;OAEG;IACH,OAAO,CAAC,oBAAoB;CAuE7B"}
package/dist/cuoral.js CHANGED
@@ -50,6 +50,11 @@ export class Cuoral {
50
50
  */
51
51
  async initialize() {
52
52
  this.bridge.initialize();
53
+ // Recreate modal if it was destroyed (e.g., after clearSession)
54
+ if (this.options.useModal && !this.modal) {
55
+ const widgetUrl = this.getWidgetUrl();
56
+ this.modal = new CuoralModal(widgetUrl, this.options.showFloatingButton);
57
+ }
53
58
  // Initialize modal if enabled
54
59
  if (this.modal) {
55
60
  this.modal.initialize();
@@ -151,6 +156,9 @@ export class Cuoral {
151
156
  */
152
157
  openModal() {
153
158
  if (this.modal) {
159
+ // Update iframe URL to include current session_id before opening
160
+ const currentUrl = this.getWidgetUrl();
161
+ this.modal.updateWidgetUrl(currentUrl);
154
162
  this.modal.open();
155
163
  }
156
164
  }
@@ -176,15 +184,65 @@ export class Cuoral {
176
184
  const params = new URLSearchParams({
177
185
  auto_start: 'true',
178
186
  key: this.options.publicKey,
187
+ is_mobile: 'true',
179
188
  _t: Date.now().toString(),
180
189
  });
190
+ // Add session_id if available
191
+ const sessionId = localStorage.getItem('__x_loadID');
192
+ if (sessionId) {
193
+ params.set('session_id', sessionId);
194
+ }
181
195
  if (this.options.email)
182
196
  params.set('email', this.options.email);
183
197
  if (this.options.firstName)
184
198
  params.set('first_name', this.options.firstName);
185
199
  if (this.options.lastName)
186
200
  params.set('last_name', this.options.lastName);
187
- return `${baseUrl}?${params.toString()}`;
201
+ const url = `${baseUrl}?${params.toString()}`;
202
+ return url;
203
+ }
204
+ /**
205
+ * Clear and end the current session (call before logout)
206
+ */
207
+ async clearSession() {
208
+ try {
209
+ const sessionId = localStorage.getItem('__x_loadID');
210
+ if (sessionId) {
211
+ // Notify backend to close the session
212
+ await fetch('https://api.cuoral.com/conversation/end-session', {
213
+ method: 'POST',
214
+ headers: {
215
+ 'Content-Type': 'application/json',
216
+ 'x-org-id': this.options.publicKey,
217
+ },
218
+ body: JSON.stringify({
219
+ session_id: sessionId
220
+ }),
221
+ });
222
+ }
223
+ // Clear local session storage
224
+ localStorage.removeItem('__x_loadID');
225
+ // Destroy modal to clear cached iframe
226
+ if (this.modal) {
227
+ this.modal.destroy();
228
+ this.modal = undefined;
229
+ }
230
+ // Destroy bridge to clear any cached data
231
+ this.bridge.destroy();
232
+ // Clear intelligence session
233
+ if (this.intelligence) {
234
+ this.intelligence.destroy();
235
+ this.intelligence = undefined;
236
+ }
237
+ }
238
+ catch (error) {
239
+ // Fail silently - always clear local storage
240
+ localStorage.removeItem('__x_loadID');
241
+ if (this.modal) {
242
+ this.modal.destroy();
243
+ this.modal = undefined;
244
+ }
245
+ }
188
246
  }
189
247
  /**
190
248
  * Clean up resources
@@ -224,7 +282,12 @@ export class Cuoral {
224
282
  'Content-Type': 'application/json',
225
283
  'x-org-id': this.options.publicKey,
226
284
  },
227
- body: JSON.stringify({ public_key: this.options.publicKey }),
285
+ body: JSON.stringify({
286
+ public_key: this.options.publicKey,
287
+ email: this.options.email,
288
+ first_name: this.options.firstName,
289
+ last_name: this.options.lastName
290
+ }),
228
291
  });
229
292
  if (!response.ok) {
230
293
  return null;
@@ -232,16 +295,11 @@ export class Cuoral {
232
295
  const data = await response.json();
233
296
  if (data.status && data.session_id) {
234
297
  localStorage.setItem('__x_loadID', data.session_id);
235
- // Set profile if email, firstName, lastName are provided
236
- if (this.options.email && this.options.firstName && this.options.lastName) {
237
- await this.setProfile(data.session_id);
238
- }
239
298
  return data.session_id;
240
299
  }
241
300
  return null;
242
301
  }
243
302
  catch (error) {
244
- console.warn('[Cuoral] Failed to initiate session:', error);
245
303
  return null;
246
304
  }
247
305
  }
package/dist/index.esm.js CHANGED
@@ -344,6 +344,44 @@ class CuoralModal {
344
344
  this.widgetUrl = widgetUrl;
345
345
  this.showFloatingButton = showFloatingButton;
346
346
  }
347
+ /**
348
+ * Update the widget URL (e.g., to include new session_id)
349
+ * Forces complete iframe recreation if URL changed to clear all cached state
350
+ */
351
+ updateWidgetUrl(newUrl) {
352
+ this.widgetUrl = newUrl;
353
+ if (!this.iframeElement)
354
+ return;
355
+ // Compare URLs without timestamp parameter to check if content changed
356
+ const stripTimestamp = (url) => url.replace(/[&?]_t=[^&]*/, '');
357
+ const newUrlWithoutTimestamp = stripTimestamp(newUrl);
358
+ const lastUrlWithoutTimestamp = this.lastLoadedUrl ? stripTimestamp(this.lastLoadedUrl) : '';
359
+ // Only reload iframe if the URL actually changed (different session or user)
360
+ if (newUrlWithoutTimestamp !== lastUrlWithoutTimestamp) {
361
+ // Store reference to parent container
362
+ const parentContainer = this.iframeElement.parentElement;
363
+ if (parentContainer) {
364
+ // Remove old iframe completely
365
+ parentContainer.removeChild(this.iframeElement);
366
+ // Create fresh iframe element with cache-busting attributes
367
+ this.iframeElement = document.createElement('iframe');
368
+ this.iframeElement.id = 'cuoral-widget-iframe';
369
+ // Add cache-busting attributes to force fresh load
370
+ this.iframeElement.setAttribute('data-cache-bust', Date.now().toString());
371
+ // Use src with force reload parameter
372
+ this.iframeElement.src = newUrl + '&_reload=1';
373
+ Object.assign(this.iframeElement.style, {
374
+ width: '100%',
375
+ height: '100%',
376
+ border: 'none',
377
+ backgroundColor: 'white'
378
+ });
379
+ // Append new iframe
380
+ parentContainer.appendChild(this.iframeElement);
381
+ }
382
+ this.lastLoadedUrl = newUrl;
383
+ }
384
+ }
347
385
  /**
348
386
  * Initialize the modal and floating button
349
387
  */
@@ -420,7 +458,7 @@ class CuoralModal {
420
458
  zIndex: '1000000',
421
459
  opacity: '0',
422
460
  transition: 'opacity 0.3s ease',
423
- padding: '60px 0'
461
+ padding: '60px 0 30px 0'
424
462
  });
425
463
  // Modal content (with margin)
426
464
  const modalContent = document.createElement('div');
@@ -471,6 +509,7 @@ class CuoralModal {
471
509
  this.iframeElement = document.createElement('iframe');
472
510
  this.iframeElement.id = 'cuoral-widget-iframe';
473
511
  this.iframeElement.src = this.widgetUrl;
512
+ this.lastLoadedUrl = this.widgetUrl; // Track initial URL
474
513
  Object.assign(this.iframeElement.style, {
475
514
  width: '100%',
476
515
  height: '100%',
@@ -1108,6 +1147,11 @@ class Cuoral {
1108
1147
  */
1109
1148
  async initialize() {
1110
1149
  this.bridge.initialize();
1150
+ // Recreate modal if it was destroyed (e.g., after clearSession)
1151
+ if (this.options.useModal && !this.modal) {
1152
+ const widgetUrl = this.getWidgetUrl();
1153
+ this.modal = new CuoralModal(widgetUrl, this.options.showFloatingButton);
1154
+ }
1111
1155
  // Initialize modal if enabled
1112
1156
  if (this.modal) {
1113
1157
  this.modal.initialize();
@@ -1209,6 +1253,9 @@ class Cuoral {
1209
1253
  */
1210
1254
  openModal() {
1211
1255
  if (this.modal) {
1256
+ // Update iframe URL to include current session_id before opening
1257
+ const currentUrl = this.getWidgetUrl();
1258
+ this.modal.updateWidgetUrl(currentUrl);
1212
1259
  this.modal.open();
1213
1260
  }
1214
1261
  }
@@ -1234,15 +1281,65 @@ class Cuoral {
1234
1281
  const params = new URLSearchParams({
1235
1282
  auto_start: 'true',
1236
1283
  key: this.options.publicKey,
1284
+ is_mobile: 'true',
1237
1285
  _t: Date.now().toString(),
1238
1286
  });
1287
+ // Add session_id if available
1288
+ const sessionId = localStorage.getItem('__x_loadID');
1289
+ if (sessionId) {
1290
+ params.set('session_id', sessionId);
1291
+ }
1239
1292
  if (this.options.email)
1240
1293
  params.set('email', this.options.email);
1241
1294
  if (this.options.firstName)
1242
1295
  params.set('first_name', this.options.firstName);
1243
1296
  if (this.options.lastName)
1244
1297
  params.set('last_name', this.options.lastName);
1245
- return `${baseUrl}?${params.toString()}`;
1298
+ const url = `${baseUrl}?${params.toString()}`;
1299
+ return url;
1300
+ }
1301
+ /**
1302
+ * Clear and end the current session (call before logout)
1303
+ */
1304
+ async clearSession() {
1305
+ try {
1306
+ const sessionId = localStorage.getItem('__x_loadID');
1307
+ if (sessionId) {
1308
+ // Notify backend to close the session
1309
+ await fetch('https://api.cuoral.com/conversation/end-session', {
1310
+ method: 'POST',
1311
+ headers: {
1312
+ 'Content-Type': 'application/json',
1313
+ 'x-org-id': this.options.publicKey,
1314
+ },
1315
+ body: JSON.stringify({
1316
+ session_id: sessionId
1317
+ }),
1318
+ });
1319
+ }
1320
+ // Clear local session storage
1321
+ localStorage.removeItem('__x_loadID');
1322
+ // Destroy modal to clear cached iframe
1323
+ if (this.modal) {
1324
+ this.modal.destroy();
1325
+ this.modal = undefined;
1326
+ }
1327
+ // Destroy bridge to clear any cached data
1328
+ this.bridge.destroy();
1329
+ // Clear intelligence session
1330
+ if (this.intelligence) {
1331
+ this.intelligence.destroy();
1332
+ this.intelligence = undefined;
1333
+ }
1334
+ }
1335
+ catch (error) {
1336
+ // Fail silently - always clear local storage
1337
+ localStorage.removeItem('__x_loadID');
1338
+ if (this.modal) {
1339
+ this.modal.destroy();
1340
+ this.modal = undefined;
1341
+ }
1342
+ }
1246
1343
  }
1247
1344
  /**
1248
1345
  * Clean up resources
@@ -1282,7 +1379,12 @@ class Cuoral {
1282
1379
  'Content-Type': 'application/json',
1283
1380
  'x-org-id': this.options.publicKey,
1284
1381
  },
1285
- body: JSON.stringify({ public_key: this.options.publicKey }),
1382
+ body: JSON.stringify({
1383
+ public_key: this.options.publicKey,
1384
+ email: this.options.email,
1385
+ first_name: this.options.firstName,
1386
+ last_name: this.options.lastName
1387
+ }),
1286
1388
  });
1287
1389
  if (!response.ok) {
1288
1390
  return null;
@@ -1290,16 +1392,11 @@ class Cuoral {
1290
1392
  const data = await response.json();
1291
1393
  if (data.status && data.session_id) {
1292
1394
  localStorage.setItem('__x_loadID', data.session_id);
1293
- // Set profile if email, firstName, lastName are provided
1294
- if (this.options.email && this.options.firstName && this.options.lastName) {
1295
- await this.setProfile(data.session_id);
1296
- }
1297
1395
  return data.session_id;
1298
1396
  }
1299
1397
  return null;
1300
1398
  }
1301
1399
  catch (error) {
1302
- console.warn('[Cuoral] Failed to initiate session:', error);
1303
1400
  return null;
1304
1401
  }
1305
1402
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/index.js CHANGED
@@ -346,6 +346,44 @@ class CuoralModal {
346
346
  this.widgetUrl = widgetUrl;
347
347
  this.showFloatingButton = showFloatingButton;
348
348
  }
349
+ /**
350
+ * Update the widget URL (e.g., to include new session_id)
351
+ * Forces complete iframe recreation if URL changed to clear all cached state
352
+ */
353
+ updateWidgetUrl(newUrl) {
354
+ this.widgetUrl = newUrl;
355
+ if (!this.iframeElement)
356
+ return;
357
+ // Compare URLs without timestamp parameter to check if content changed
358
+ const stripTimestamp = (url) => url.replace(/[&?]_t=[^&]*/, '');
359
+ const newUrlWithoutTimestamp = stripTimestamp(newUrl);
360
+ const lastUrlWithoutTimestamp = this.lastLoadedUrl ? stripTimestamp(this.lastLoadedUrl) : '';
361
+ // Only reload iframe if the URL actually changed (different session or user)
362
+ if (newUrlWithoutTimestamp !== lastUrlWithoutTimestamp) {
363
+ // Store reference to parent container
364
+ const parentContainer = this.iframeElement.parentElement;
365
+ if (parentContainer) {
366
+ // Remove old iframe completely
367
+ parentContainer.removeChild(this.iframeElement);
368
+ // Create fresh iframe element with cache-busting attributes
369
+ this.iframeElement = document.createElement('iframe');
370
+ this.iframeElement.id = 'cuoral-widget-iframe';
371
+ // Add cache-busting attributes to force fresh load
372
+ this.iframeElement.setAttribute('data-cache-bust', Date.now().toString());
373
+ // Use src with force reload parameter
374
+ this.iframeElement.src = newUrl + '&_reload=1';
375
+ Object.assign(this.iframeElement.style, {
376
+ width: '100%',
377
+ height: '100%',
378
+ border: 'none',
379
+ backgroundColor: 'white'
380
+ });
381
+ // Append new iframe
382
+ parentContainer.appendChild(this.iframeElement);
383
+ }
384
+ this.lastLoadedUrl = newUrl;
385
+ }
386
+ }
349
387
  /**
350
388
  * Initialize the modal and floating button
351
389
  */
@@ -422,7 +460,7 @@ class CuoralModal {
422
460
  zIndex: '1000000',
423
461
  opacity: '0',
424
462
  transition: 'opacity 0.3s ease',
425
- padding: '60px 0'
463
+ padding: '60px 0 30px 0'
426
464
  });
427
465
  // Modal content (with margin)
428
466
  const modalContent = document.createElement('div');
@@ -473,6 +511,7 @@ class CuoralModal {
473
511
  this.iframeElement = document.createElement('iframe');
474
512
  this.iframeElement.id = 'cuoral-widget-iframe';
475
513
  this.iframeElement.src = this.widgetUrl;
514
+ this.lastLoadedUrl = this.widgetUrl; // Track initial URL
476
515
  Object.assign(this.iframeElement.style, {
477
516
  width: '100%',
478
517
  height: '100%',
@@ -1110,6 +1149,11 @@ class Cuoral {
1110
1149
  */
1111
1150
  async initialize() {
1112
1151
  this.bridge.initialize();
1152
+ // Recreate modal if it was destroyed (e.g., after clearSession)
1153
+ if (this.options.useModal && !this.modal) {
1154
+ const widgetUrl = this.getWidgetUrl();
1155
+ this.modal = new CuoralModal(widgetUrl, this.options.showFloatingButton);
1156
+ }
1113
1157
  // Initialize modal if enabled
1114
1158
  if (this.modal) {
1115
1159
  this.modal.initialize();
@@ -1211,6 +1255,9 @@ class Cuoral {
1211
1255
  */
1212
1256
  openModal() {
1213
1257
  if (this.modal) {
1258
+ // Update iframe URL to include current session_id before opening
1259
+ const currentUrl = this.getWidgetUrl();
1260
+ this.modal.updateWidgetUrl(currentUrl);
1214
1261
  this.modal.open();
1215
1262
  }
1216
1263
  }
@@ -1236,15 +1283,65 @@ class Cuoral {
1236
1283
  const params = new URLSearchParams({
1237
1284
  auto_start: 'true',
1238
1285
  key: this.options.publicKey,
1286
+ is_mobile: 'true',
1239
1287
  _t: Date.now().toString(),
1240
1288
  });
1289
+ // Add session_id if available
1290
+ const sessionId = localStorage.getItem('__x_loadID');
1291
+ if (sessionId) {
1292
+ params.set('session_id', sessionId);
1293
+ }
1241
1294
  if (this.options.email)
1242
1295
  params.set('email', this.options.email);
1243
1296
  if (this.options.firstName)
1244
1297
  params.set('first_name', this.options.firstName);
1245
1298
  if (this.options.lastName)
1246
1299
  params.set('last_name', this.options.lastName);
1247
- return `${baseUrl}?${params.toString()}`;
1300
+ const url = `${baseUrl}?${params.toString()}`;
1301
+ return url;
1302
+ }
1303
+ /**
1304
+ * Clear and end the current session (call before logout)
1305
+ */
1306
+ async clearSession() {
1307
+ try {
1308
+ const sessionId = localStorage.getItem('__x_loadID');
1309
+ if (sessionId) {
1310
+ // Notify backend to close the session
1311
+ await fetch('https://api.cuoral.com/conversation/end-session', {
1312
+ method: 'POST',
1313
+ headers: {
1314
+ 'Content-Type': 'application/json',
1315
+ 'x-org-id': this.options.publicKey,
1316
+ },
1317
+ body: JSON.stringify({
1318
+ session_id: sessionId
1319
+ }),
1320
+ });
1321
+ }
1322
+ // Clear local session storage
1323
+ localStorage.removeItem('__x_loadID');
1324
+ // Destroy modal to clear cached iframe
1325
+ if (this.modal) {
1326
+ this.modal.destroy();
1327
+ this.modal = undefined;
1328
+ }
1329
+ // Destroy bridge to clear any cached data
1330
+ this.bridge.destroy();
1331
+ // Clear intelligence session
1332
+ if (this.intelligence) {
1333
+ this.intelligence.destroy();
1334
+ this.intelligence = undefined;
1335
+ }
1336
+ }
1337
+ catch (error) {
1338
+ // Fail silently - always clear local storage
1339
+ localStorage.removeItem('__x_loadID');
1340
+ if (this.modal) {
1341
+ this.modal.destroy();
1342
+ this.modal = undefined;
1343
+ }
1344
+ }
1248
1345
  }
1249
1346
  /**
1250
1347
  * Clean up resources
@@ -1284,7 +1381,12 @@ class Cuoral {
1284
1381
  'Content-Type': 'application/json',
1285
1382
  'x-org-id': this.options.publicKey,
1286
1383
  },
1287
- body: JSON.stringify({ public_key: this.options.publicKey }),
1384
+ body: JSON.stringify({
1385
+ public_key: this.options.publicKey,
1386
+ email: this.options.email,
1387
+ first_name: this.options.firstName,
1388
+ last_name: this.options.lastName
1389
+ }),
1288
1390
  });
1289
1391
  if (!response.ok) {
1290
1392
  return null;
@@ -1292,16 +1394,11 @@ class Cuoral {
1292
1394
  const data = await response.json();
1293
1395
  if (data.status && data.session_id) {
1294
1396
  localStorage.setItem('__x_loadID', data.session_id);
1295
- // Set profile if email, firstName, lastName are provided
1296
- if (this.options.email && this.options.firstName && this.options.lastName) {
1297
- await this.setProfile(data.session_id);
1298
- }
1299
1397
  return data.session_id;
1300
1398
  }
1301
1399
  return null;
1302
1400
  }
1303
1401
  catch (error) {
1304
- console.warn('[Cuoral] Failed to initiate session:', error);
1305
1402
  return null;
1306
1403
  }
1307
1404
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/modal.d.ts CHANGED
@@ -9,7 +9,13 @@ export declare class CuoralModal {
9
9
  private isOpen;
10
10
  private widgetUrl;
11
11
  private showFloatingButton;
12
+ private lastLoadedUrl?;
12
13
  constructor(widgetUrl: string, showFloatingButton?: boolean);
14
+ /**
15
+ * Update the widget URL (e.g., to include new session_id)
16
+ * Forces complete iframe recreation if URL changed to clear all cached state
17
+ */
18
+ updateWidgetUrl(newUrl: string): void;
13
19
  /**
14
20
  * Initialize the modal and floating button
15
21
  */
@@ -1 +1 @@
1
- {"version":3,"file":"modal.d.ts","sourceRoot":"","sources":["../src/modal.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,YAAY,CAAC,CAAiB;IACtC,OAAO,CAAC,aAAa,CAAC,CAAoB;IAC1C,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,kBAAkB,CAAU;gBAExB,SAAS,EAAE,MAAM,EAAE,kBAAkB,UAAO;IAKxD;;OAEG;IACI,UAAU,IAAI,IAAI;IAOzB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAkD5B;;OAEG;IACH,OAAO,CAAC,WAAW;IAkGnB;;OAEG;IACI,IAAI,IAAI,IAAI;IAsBnB;;OAEG;IACI,KAAK,IAAI,IAAI;IAqBpB;;OAEG;IACI,WAAW,IAAI,OAAO;IAI7B;;OAEG;IACI,OAAO,IAAI,IAAI;IAUtB;;OAEG;IACI,SAAS,IAAI,iBAAiB,GAAG,SAAS;CAGlD"}
1
+ {"version":3,"file":"modal.d.ts","sourceRoot":"","sources":["../src/modal.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,YAAY,CAAC,CAAiB;IACtC,OAAO,CAAC,aAAa,CAAC,CAAoB;IAC1C,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,kBAAkB,CAAU;IACpC,OAAO,CAAC,aAAa,CAAC,CAAS;gBAEnB,SAAS,EAAE,MAAM,EAAE,kBAAkB,UAAO;IAKxD;;;OAGG;IACI,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IA4C5C;;OAEG;IACI,UAAU,IAAI,IAAI;IAOzB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAkD5B;;OAEG;IACH,OAAO,CAAC,WAAW;IAmGnB;;OAEG;IACI,IAAI,IAAI,IAAI;IAsBnB;;OAEG;IACI,KAAK,IAAI,IAAI;IAqBpB;;OAEG;IACI,WAAW,IAAI,OAAO;IAI7B;;OAEG;IACI,OAAO,IAAI,IAAI;IAUtB;;OAEG;IACI,SAAS,IAAI,iBAAiB,GAAG,SAAS;CAGlD"}
package/dist/modal.js CHANGED
@@ -8,6 +8,44 @@ export class CuoralModal {
8
8
  this.widgetUrl = widgetUrl;
9
9
  this.showFloatingButton = showFloatingButton;
10
10
  }
11
+ /**
12
+ * Update the widget URL (e.g., to include new session_id)
13
+ * Forces complete iframe recreation if URL changed to clear all cached state
14
+ */
15
+ updateWidgetUrl(newUrl) {
16
+ this.widgetUrl = newUrl;
17
+ if (!this.iframeElement)
18
+ return;
19
+ // Compare URLs without timestamp parameter to check if content changed
20
+ const stripTimestamp = (url) => url.replace(/[&?]_t=[^&]*/, '');
21
+ const newUrlWithoutTimestamp = stripTimestamp(newUrl);
22
+ const lastUrlWithoutTimestamp = this.lastLoadedUrl ? stripTimestamp(this.lastLoadedUrl) : '';
23
+ // Only reload iframe if the URL actually changed (different session or user)
24
+ if (newUrlWithoutTimestamp !== lastUrlWithoutTimestamp) {
25
+ // Store reference to parent container
26
+ const parentContainer = this.iframeElement.parentElement;
27
+ if (parentContainer) {
28
+ // Remove old iframe completely
29
+ parentContainer.removeChild(this.iframeElement);
30
+ // Create fresh iframe element with cache-busting attributes
31
+ this.iframeElement = document.createElement('iframe');
32
+ this.iframeElement.id = 'cuoral-widget-iframe';
33
+ // Add cache-busting attributes to force fresh load
34
+ this.iframeElement.setAttribute('data-cache-bust', Date.now().toString());
35
+ // Use src with force reload parameter
36
+ this.iframeElement.src = newUrl + '&_reload=1';
37
+ Object.assign(this.iframeElement.style, {
38
+ width: '100%',
39
+ height: '100%',
40
+ border: 'none',
41
+ backgroundColor: 'white'
42
+ });
43
+ // Append new iframe
44
+ parentContainer.appendChild(this.iframeElement);
45
+ }
46
+ this.lastLoadedUrl = newUrl;
47
+ }
48
+ }
11
49
  /**
12
50
  * Initialize the modal and floating button
13
51
  */
@@ -84,7 +122,7 @@ export class CuoralModal {
84
122
  zIndex: '1000000',
85
123
  opacity: '0',
86
124
  transition: 'opacity 0.3s ease',
87
- padding: '60px 0'
125
+ padding: '60px 0 30px 0'
88
126
  });
89
127
  // Modal content (with margin)
90
128
  const modalContent = document.createElement('div');
@@ -135,6 +173,7 @@ export class CuoralModal {
135
173
  this.iframeElement = document.createElement('iframe');
136
174
  this.iframeElement.id = 'cuoral-widget-iframe';
137
175
  this.iframeElement.src = this.widgetUrl;
176
+ this.lastLoadedUrl = this.widgetUrl; // Track initial URL
138
177
  Object.assign(this.iframeElement.style, {
139
178
  width: '100%',
140
179
  height: '100%',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cuoral-ionic",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Cuoral Ionic Framework Library - Proactive customer success platform with support ticketing, customer intelligence, screen recording, and engagement tools",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/cuoral.ts CHANGED
@@ -90,6 +90,12 @@ export class Cuoral {
90
90
  public async initialize(): Promise<void> {
91
91
  this.bridge.initialize();
92
92
 
93
+ // Recreate modal if it was destroyed (e.g., after clearSession)
94
+ if (this.options.useModal && !this.modal) {
95
+ const widgetUrl = this.getWidgetUrl();
96
+ this.modal = new CuoralModal(widgetUrl, this.options.showFloatingButton);
97
+ }
98
+
93
99
  // Initialize modal if enabled
94
100
  if (this.modal) {
95
101
  this.modal.initialize();
@@ -204,6 +210,9 @@ export class Cuoral {
204
210
  */
205
211
  public openModal(): void {
206
212
  if (this.modal) {
213
+ // Update iframe URL to include current session_id before opening
214
+ const currentUrl = this.getWidgetUrl();
215
+ this.modal.updateWidgetUrl(currentUrl);
207
216
  this.modal.open();
208
217
  }
209
218
  }
@@ -232,14 +241,70 @@ export class Cuoral {
232
241
  const params = new URLSearchParams({
233
242
  auto_start: 'true',
234
243
  key: this.options.publicKey,
244
+ is_mobile: 'true',
235
245
  _t: Date.now().toString(),
236
246
  });
237
247
 
248
+ // Add session_id if available
249
+ const sessionId = localStorage.getItem('__x_loadID');
250
+ if (sessionId) {
251
+ params.set('session_id', sessionId);
252
+ }
253
+
238
254
  if (this.options.email) params.set('email', this.options.email);
239
255
  if (this.options.firstName) params.set('first_name', this.options.firstName);
240
256
  if (this.options.lastName) params.set('last_name', this.options.lastName);
241
257
 
242
- return `${baseUrl}?${params.toString()}`;
258
+ const url = `${baseUrl}?${params.toString()}`;
259
+ return url;
260
+ }
261
+
262
+ /**
263
+ * Clear and end the current session (call before logout)
264
+ */
265
+ public async clearSession(): Promise<void> {
266
+ try {
267
+ const sessionId = localStorage.getItem('__x_loadID');
268
+
269
+ if (sessionId) {
270
+ // Notify backend to close the session
271
+ await fetch('https://api.cuoral.com/conversation/end-session', {
272
+ method: 'POST',
273
+ headers: {
274
+ 'Content-Type': 'application/json',
275
+ 'x-org-id': this.options.publicKey,
276
+ },
277
+ body: JSON.stringify({
278
+ session_id: sessionId
279
+ }),
280
+ });
281
+ }
282
+
283
+ // Clear local session storage
284
+ localStorage.removeItem('__x_loadID');
285
+
286
+ // Destroy modal to clear cached iframe
287
+ if (this.modal) {
288
+ this.modal.destroy();
289
+ this.modal = undefined;
290
+ }
291
+
292
+ // Destroy bridge to clear any cached data
293
+ this.bridge.destroy();
294
+
295
+ // Clear intelligence session
296
+ if (this.intelligence) {
297
+ this.intelligence.destroy();
298
+ this.intelligence = undefined;
299
+ }
300
+ } catch (error) {
301
+ // Fail silently - always clear local storage
302
+ localStorage.removeItem('__x_loadID');
303
+ if (this.modal) {
304
+ this.modal.destroy();
305
+ this.modal = undefined;
306
+ }
307
+ }
243
308
  }
244
309
 
245
310
  /**
@@ -280,13 +345,19 @@ export class Cuoral {
280
345
  */
281
346
  private async initiateSession(): Promise<string | null> {
282
347
  try {
348
+
283
349
  const response = await fetch('https://api.cuoral.com/conversation/initiate-session', {
284
350
  method: 'POST',
285
351
  headers: {
286
352
  'Content-Type': 'application/json',
287
353
  'x-org-id': this.options.publicKey,
288
354
  },
289
- body: JSON.stringify({ public_key: this.options.publicKey }),
355
+ body: JSON.stringify({
356
+ public_key: this.options.publicKey,
357
+ email: this.options.email,
358
+ first_name: this.options.firstName,
359
+ last_name: this.options.lastName
360
+ }),
290
361
  });
291
362
 
292
363
  if (!response.ok) {
@@ -297,18 +368,11 @@ export class Cuoral {
297
368
 
298
369
  if (data.status && data.session_id) {
299
370
  localStorage.setItem('__x_loadID', data.session_id);
300
-
301
- // Set profile if email, firstName, lastName are provided
302
- if (this.options.email && this.options.firstName && this.options.lastName) {
303
- await this.setProfile(data.session_id);
304
- }
305
-
306
371
  return data.session_id;
307
372
  }
308
373
 
309
374
  return null;
310
375
  } catch (error) {
311
- console.warn('[Cuoral] Failed to initiate session:', error);
312
376
  return null;
313
377
  }
314
378
  }
package/src/modal.ts CHANGED
@@ -9,12 +9,61 @@ export class CuoralModal {
9
9
  private isOpen = false;
10
10
  private widgetUrl: string;
11
11
  private showFloatingButton: boolean;
12
+ private lastLoadedUrl?: string;
12
13
 
13
14
  constructor(widgetUrl: string, showFloatingButton = true) {
14
15
  this.widgetUrl = widgetUrl;
15
16
  this.showFloatingButton = showFloatingButton;
16
17
  }
17
18
 
19
+ /**
20
+ * Update the widget URL (e.g., to include new session_id)
21
+ * Forces complete iframe recreation if URL changed to clear all cached state
22
+ */
23
+ public updateWidgetUrl(newUrl: string): void {
24
+ this.widgetUrl = newUrl;
25
+
26
+ if (!this.iframeElement) return;
27
+
28
+ // Compare URLs without timestamp parameter to check if content changed
29
+ const stripTimestamp = (url: string) => url.replace(/[&?]_t=[^&]*/, '');
30
+ const newUrlWithoutTimestamp = stripTimestamp(newUrl);
31
+ const lastUrlWithoutTimestamp = this.lastLoadedUrl ? stripTimestamp(this.lastLoadedUrl) : '';
32
+
33
+ // Only reload iframe if the URL actually changed (different session or user)
34
+ if (newUrlWithoutTimestamp !== lastUrlWithoutTimestamp) {
35
+ // Store reference to parent container
36
+ const parentContainer = this.iframeElement.parentElement;
37
+
38
+ if (parentContainer) {
39
+ // Remove old iframe completely
40
+ parentContainer.removeChild(this.iframeElement);
41
+
42
+ // Create fresh iframe element with cache-busting attributes
43
+ this.iframeElement = document.createElement('iframe');
44
+ this.iframeElement.id = 'cuoral-widget-iframe';
45
+
46
+ // Add cache-busting attributes to force fresh load
47
+ this.iframeElement.setAttribute('data-cache-bust', Date.now().toString());
48
+
49
+ // Use src with force reload parameter
50
+ this.iframeElement.src = newUrl + '&_reload=1';
51
+
52
+ Object.assign(this.iframeElement.style, {
53
+ width: '100%',
54
+ height: '100%',
55
+ border: 'none',
56
+ backgroundColor: 'white'
57
+ });
58
+
59
+ // Append new iframe
60
+ parentContainer.appendChild(this.iframeElement);
61
+ }
62
+
63
+ this.lastLoadedUrl = newUrl;
64
+ }
65
+ }
66
+
18
67
  /**
19
68
  * Initialize the modal and floating button
20
69
  */
@@ -99,7 +148,7 @@ export class CuoralModal {
99
148
  zIndex: '1000000',
100
149
  opacity: '0',
101
150
  transition: 'opacity 0.3s ease',
102
- padding: '60px 0'
151
+ padding: '60px 0 30px 0'
103
152
  });
104
153
 
105
154
  // Modal content (with margin)
@@ -157,6 +206,7 @@ export class CuoralModal {
157
206
  this.iframeElement = document.createElement('iframe');
158
207
  this.iframeElement.id = 'cuoral-widget-iframe';
159
208
  this.iframeElement.src = this.widgetUrl;
209
+ this.lastLoadedUrl = this.widgetUrl; // Track initial URL
160
210
 
161
211
  Object.assign(this.iframeElement.style, {
162
212
  width: '100%',