cuoral-ionic 0.0.3 → 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 +42 -0
- package/dist/cuoral.d.ts +4 -0
- package/dist/cuoral.d.ts.map +1 -1
- package/dist/cuoral.js +65 -7
- package/dist/index.esm.js +105 -8
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +105 -8
- package/dist/index.js.map +1 -1
- package/dist/modal.d.ts +6 -0
- package/dist/modal.d.ts.map +1 -1
- package/dist/modal.js +40 -1
- package/package.json +1 -1
- package/src/cuoral.ts +73 -9
- package/src/modal.ts +51 -1
package/README.md
CHANGED
|
@@ -198,6 +198,48 @@ export class AppComponent {
|
|
|
198
198
|
|
|
199
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
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
|
+
|
|
201
243
|
## Configuration
|
|
202
244
|
|
|
203
245
|
### CuoralOptions
|
package/dist/cuoral.d.ts
CHANGED
package/dist/cuoral.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
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({
|
|
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
|
}
|
package/dist/index.esm.js.map
CHANGED
|
@@ -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
|
-
|
|
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({
|
|
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
|
*/
|
package/dist/modal.d.ts.map
CHANGED
|
@@ -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;
|
|
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.
|
|
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
|
-
|
|
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({
|
|
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%',
|