cuoral-ionic 0.0.6 → 0.0.8
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/android/build/.transforms/bb54161301273cf9b5b94a21c0fb3f23/transformed/classes/classes_dex/classes.dex +0 -0
- package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$1.dex +0 -0
- package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$2.dex +0 -0
- package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$3.dex +0 -0
- package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$4.dex +0 -0
- package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$5.dex +0 -0
- package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$6.dex +0 -0
- package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$InitiateCallback.dex +0 -0
- package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$UploadCallback.dex +0 -0
- package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin.dex +0 -0
- package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/desugar_graph.bin +0 -0
- package/android/build/intermediates/compile_library_classes_jar/debug/classes.jar +0 -0
- package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$1.class +0 -0
- package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$2.class +0 -0
- package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$3.class +0 -0
- package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$4.class +0 -0
- package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$5.class +0 -0
- package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$6.class +0 -0
- package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$InitiateCallback.class +0 -0
- package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$UploadCallback.class +0 -0
- package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$1.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$2.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$3.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$4.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$5.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$6.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$InitiateCallback.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$UploadCallback.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin.class +0 -0
- package/android/build/intermediates/runtime_library_classes_jar/debug/classes.jar +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/{CuoralPlugin$1.class.uniqueId1 → CuoralPlugin$1.class.uniqueId2} +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/{CuoralPlugin$2.class.uniqueId2 → CuoralPlugin$2.class.uniqueId5} +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$3.class.uniqueId3 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$4.class.uniqueId6 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$5.class.uniqueId4 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$6.class.uniqueId8 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$InitiateCallback.class.uniqueId0 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$UploadCallback.class.uniqueId7 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin.class.uniqueId1 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
- package/android/build.gradle +1 -0
- package/android/src/main/java/com/cuoral/ionic/CuoralPlugin.java +209 -6
- package/dist/bridge.d.ts.map +1 -1
- package/dist/cuoral.d.ts +15 -1
- package/dist/cuoral.d.ts.map +1 -1
- package/dist/cuoral.js +141 -12
- package/dist/index.esm.js +218 -46
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +218 -46
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +4 -0
- package/dist/intelligence.d.ts.map +1 -1
- package/dist/intelligence.js +19 -0
- package/dist/plugin.d.ts +16 -3
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +53 -36
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -0
- package/ios/Plugin/CuoralPlugin.swift +249 -13
- package/package.json +1 -1
- package/src/bridge.ts +1 -0
- package/src/cuoral.ts +158 -14
- package/src/intelligence.ts +23 -0
- package/src/plugin.ts +70 -37
- package/src/types.ts +4 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin.class.uniqueId0 +0 -0
package/dist/cuoral.js
CHANGED
|
@@ -9,6 +9,7 @@ import { Capacitor } from '@capacitor/core';
|
|
|
9
9
|
*/
|
|
10
10
|
export class Cuoral {
|
|
11
11
|
constructor(options) {
|
|
12
|
+
console.log('[Cuoral] Constructor called with options:', options);
|
|
12
13
|
// Check if running on a mobile platform
|
|
13
14
|
if (!Capacitor.isNativePlatform()) {
|
|
14
15
|
throw new Error('Cuoral Ionic library only works on native mobile platforms (iOS/Android). Web is not supported.');
|
|
@@ -18,6 +19,7 @@ export class Cuoral {
|
|
|
18
19
|
useModal: true,
|
|
19
20
|
...options
|
|
20
21
|
};
|
|
22
|
+
console.log('[Cuoral] Merged options:', this.options);
|
|
21
23
|
// Determine widget base URL
|
|
22
24
|
const baseUrl = options.widgetBaseUrl || Cuoral.PRODUCTION_WIDGET_URL;
|
|
23
25
|
const params = new URLSearchParams({
|
|
@@ -25,6 +27,11 @@ export class Cuoral {
|
|
|
25
27
|
key: options.publicKey,
|
|
26
28
|
_t: Date.now().toString(),
|
|
27
29
|
});
|
|
30
|
+
// Add session_id if available (for widget to use existing session)
|
|
31
|
+
const existingSessionId = localStorage.getItem('__x_loadID');
|
|
32
|
+
if (existingSessionId) {
|
|
33
|
+
params.set('cuoral_mobile_session_id', existingSessionId);
|
|
34
|
+
}
|
|
28
35
|
if (options.email)
|
|
29
36
|
params.set('email', options.email);
|
|
30
37
|
if (options.firstName)
|
|
@@ -42,25 +49,36 @@ export class Cuoral {
|
|
|
42
49
|
debug: options.debug || false
|
|
43
50
|
});
|
|
44
51
|
this.recorder = new CuoralRecorder();
|
|
52
|
+
// Expose bridge on window for widget detection and debugging
|
|
53
|
+
window.CuoralCapacitorBridge = true;
|
|
54
|
+
window.__cuoralBridge = this.bridge; // For debugging
|
|
55
|
+
window.__cuoralRecorder = this.recorder; // For debugging
|
|
56
|
+
console.log('[Cuoral] Bridge and recorder created, exposed on window');
|
|
45
57
|
// Setup automatic message handlers
|
|
46
58
|
this.setupMessageHandlers();
|
|
59
|
+
console.log('[Cuoral] Constructor complete');
|
|
47
60
|
}
|
|
48
61
|
/**
|
|
49
62
|
* Initialize Cuoral
|
|
50
63
|
*/
|
|
51
64
|
async initialize() {
|
|
65
|
+
// Fetch session configuration and initialize intelligence if enabled by backend
|
|
66
|
+
await this.initializeIntelligence();
|
|
67
|
+
console.log('[Cuoral] Initialize - Session ID:', localStorage.getItem('__x_loadID'));
|
|
68
|
+
// Setup localStorage listener to detect when widget changes session
|
|
69
|
+
this.setupStorageListener();
|
|
52
70
|
this.bridge.initialize();
|
|
53
71
|
// Recreate modal if it was destroyed (e.g., after clearSession)
|
|
54
72
|
if (this.options.useModal && !this.modal) {
|
|
55
73
|
const widgetUrl = this.getWidgetUrl();
|
|
56
74
|
this.modal = new CuoralModal(widgetUrl, this.options.showFloatingButton);
|
|
57
75
|
}
|
|
58
|
-
//
|
|
76
|
+
// Update modal URL with session ID
|
|
59
77
|
if (this.modal) {
|
|
78
|
+
const widgetUrl = this.getWidgetUrl();
|
|
79
|
+
this.modal.updateWidgetUrl(widgetUrl);
|
|
60
80
|
this.modal.initialize();
|
|
61
81
|
}
|
|
62
|
-
// Fetch session configuration and initialize intelligence if enabled by backend
|
|
63
|
-
await this.initializeIntelligence();
|
|
64
82
|
}
|
|
65
83
|
/**
|
|
66
84
|
* Initialize intelligence based on backend configuration
|
|
@@ -92,9 +110,13 @@ export class Cuoral {
|
|
|
92
110
|
}
|
|
93
111
|
// Only initialize intelligence if customer_intelligence is enabled in backend
|
|
94
112
|
if (config && config.customer_intelligence === true) {
|
|
113
|
+
console.log('[Cuoral] Initializing intelligence with session:', sessionId);
|
|
95
114
|
this.intelligence = new CuoralIntelligence(sessionId);
|
|
96
115
|
this.intelligence.init();
|
|
97
116
|
}
|
|
117
|
+
else {
|
|
118
|
+
console.log('[Cuoral] Intelligence not enabled or no config for session:', sessionId);
|
|
119
|
+
}
|
|
98
120
|
}
|
|
99
121
|
catch (error) {
|
|
100
122
|
console.warn('[Cuoral] Failed to initialize intelligence:', error);
|
|
@@ -151,6 +173,48 @@ export class Cuoral {
|
|
|
151
173
|
this.intelligence.trackError(message, stackTrace, metadata);
|
|
152
174
|
}
|
|
153
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Update user profile for the current session
|
|
178
|
+
* Call this after user logs in to update the intelligence session with their profile
|
|
179
|
+
* @param email - User's email address
|
|
180
|
+
* @param name - User's full name
|
|
181
|
+
*/
|
|
182
|
+
async updateUserProfile(email, name) {
|
|
183
|
+
try {
|
|
184
|
+
const sessionId = localStorage.getItem('__x_loadID');
|
|
185
|
+
if (!sessionId) {
|
|
186
|
+
console.warn('[Cuoral] No session ID found, cannot update profile');
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
console.log('[Cuoral] Updating user profile for session:', sessionId);
|
|
190
|
+
const response = await fetch('https://api.cuoral.com/conversation/set-profile', {
|
|
191
|
+
method: 'POST',
|
|
192
|
+
headers: { 'Content-Type': 'application/json' },
|
|
193
|
+
body: JSON.stringify({
|
|
194
|
+
session_id: sessionId,
|
|
195
|
+
email: email,
|
|
196
|
+
name: name,
|
|
197
|
+
}),
|
|
198
|
+
});
|
|
199
|
+
if (!response.ok) {
|
|
200
|
+
console.error('[Cuoral] Failed to update profile:', response.statusText);
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
console.log('[Cuoral] ✓ User profile updated successfully for session:', sessionId);
|
|
204
|
+
// Store user info locally
|
|
205
|
+
this.options.email = email;
|
|
206
|
+
const nameParts = name.split(' ');
|
|
207
|
+
if (nameParts.length > 0) {
|
|
208
|
+
this.options.firstName = nameParts[0];
|
|
209
|
+
this.options.lastName = nameParts.slice(1).join(' ');
|
|
210
|
+
}
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
console.error('[Cuoral] Error updating user profile:', error);
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
154
218
|
/**
|
|
155
219
|
* Start native screen recording programmatically
|
|
156
220
|
* @returns Promise<boolean> - true if recording started successfully
|
|
@@ -166,11 +230,19 @@ export class Cuoral {
|
|
|
166
230
|
}
|
|
167
231
|
/**
|
|
168
232
|
* Stop native screen recording programmatically
|
|
169
|
-
*
|
|
233
|
+
* Recording will be automatically uploaded to the portal
|
|
234
|
+
* @returns Promise<{filePath?: string; duration?: number; uploaded?: boolean} | null> - Recording result or null if failed
|
|
170
235
|
*/
|
|
171
236
|
async stopRecording() {
|
|
172
237
|
try {
|
|
173
|
-
|
|
238
|
+
const sessionId = localStorage.getItem('__x_loadID');
|
|
239
|
+
const customerId = localStorage.getItem('cuoralCustomerId');
|
|
240
|
+
return await this.recorder.stopRecording({
|
|
241
|
+
autoUpload: true,
|
|
242
|
+
sessionId: sessionId || undefined,
|
|
243
|
+
publicKey: this.options.publicKey,
|
|
244
|
+
customerId: customerId || undefined,
|
|
245
|
+
});
|
|
174
246
|
}
|
|
175
247
|
catch (error) {
|
|
176
248
|
console.error('[Cuoral] Failed to stop recording:', error);
|
|
@@ -216,7 +288,7 @@ export class Cuoral {
|
|
|
216
288
|
// Add session_id if available
|
|
217
289
|
const sessionId = localStorage.getItem('__x_loadID');
|
|
218
290
|
if (sessionId) {
|
|
219
|
-
params.set('
|
|
291
|
+
params.set('cuoral_mobile_session_id', sessionId);
|
|
220
292
|
}
|
|
221
293
|
if (this.options.email)
|
|
222
294
|
params.set('email', this.options.email);
|
|
@@ -270,6 +342,28 @@ export class Cuoral {
|
|
|
270
342
|
}
|
|
271
343
|
}
|
|
272
344
|
}
|
|
345
|
+
/**
|
|
346
|
+
* Setup localStorage listener for session changes
|
|
347
|
+
* Widget updates localStorage when creating new session, SDK detects and syncs
|
|
348
|
+
*/
|
|
349
|
+
setupStorageListener() {
|
|
350
|
+
// Poll localStorage every 2 seconds to detect session changes
|
|
351
|
+
// (storage event doesn't fire for same-window changes)
|
|
352
|
+
let lastKnownSession = localStorage.getItem('__x_loadID');
|
|
353
|
+
setInterval(() => {
|
|
354
|
+
const currentSession = localStorage.getItem('__x_loadID');
|
|
355
|
+
if (currentSession && currentSession !== lastKnownSession) {
|
|
356
|
+
console.log('[Cuoral] 🔄 Session changed in localStorage');
|
|
357
|
+
console.log('[Cuoral] Old session:', lastKnownSession);
|
|
358
|
+
console.log('[Cuoral] New session:', currentSession);
|
|
359
|
+
lastKnownSession = currentSession;
|
|
360
|
+
// Update intelligence to use new session
|
|
361
|
+
if (this.intelligence) {
|
|
362
|
+
this.intelligence.updateSessionId(currentSession);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}, 2000);
|
|
366
|
+
}
|
|
273
367
|
/**
|
|
274
368
|
* Clean up resources
|
|
275
369
|
*/
|
|
@@ -357,16 +451,51 @@ export class Cuoral {
|
|
|
357
451
|
setupMessageHandlers() {
|
|
358
452
|
// Handle start recording requests from widget
|
|
359
453
|
this.bridge.on(CuoralMessageType.START_RECORDING, async () => {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
454
|
+
// Pass sendMessages: false to prevent duplicate messages
|
|
455
|
+
const success = await this.recorder.startRecording(undefined, false);
|
|
456
|
+
if (success) {
|
|
457
|
+
// Send RECORDING_STARTED back to widget
|
|
458
|
+
this.bridge.sendToWidget({
|
|
459
|
+
type: CuoralMessageType.RECORDING_STARTED,
|
|
460
|
+
payload: {
|
|
461
|
+
timestamp: Date.now()
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
this.bridge.sendToWidget({
|
|
467
|
+
type: CuoralMessageType.RECORDING_ERROR,
|
|
468
|
+
payload: {
|
|
469
|
+
error: 'Failed to start recording'
|
|
470
|
+
}
|
|
471
|
+
});
|
|
363
472
|
}
|
|
364
473
|
});
|
|
365
474
|
// Handle stop recording requests from widget
|
|
366
475
|
this.bridge.on(CuoralMessageType.STOP_RECORDING, async () => {
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
476
|
+
const sessionId = localStorage.getItem('__x_loadID');
|
|
477
|
+
const customerId = localStorage.getItem('cuoralCustomerId');
|
|
478
|
+
// Pass sendMessages: false to prevent duplicate messages
|
|
479
|
+
const result = await this.recorder.stopRecording({
|
|
480
|
+
autoUpload: true,
|
|
481
|
+
sessionId: sessionId || undefined,
|
|
482
|
+
publicKey: this.options.publicKey,
|
|
483
|
+
customerId: customerId || undefined,
|
|
484
|
+
}, false);
|
|
485
|
+
if (result && result.uploaded) {
|
|
486
|
+
// Video was automatically uploaded, notify widget with RECORDING_STOPPED
|
|
487
|
+
this.bridge.sendToWidget({
|
|
488
|
+
type: CuoralMessageType.RECORDING_STOPPED,
|
|
489
|
+
payload: {
|
|
490
|
+
duration: result.duration,
|
|
491
|
+
uploaded: true,
|
|
492
|
+
uploadedToBackend: true,
|
|
493
|
+
timestamp: Date.now()
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
else if (result) {
|
|
498
|
+
// Upload failed or was disabled, send video data to widget (old behavior)
|
|
370
499
|
const capacitorUrl = result.filePath ? Capacitor.convertFileSrc(result.filePath) : '';
|
|
371
500
|
try {
|
|
372
501
|
// Fetch the video blob from the capacitor URL
|
package/dist/index.esm.js
CHANGED
|
@@ -11,6 +11,7 @@ var CuoralMessageType;
|
|
|
11
11
|
CuoralMessageType["STOP_RECORDING"] = "CUORAL_STOP_RECORDING";
|
|
12
12
|
CuoralMessageType["RECORDING_STARTED"] = "CUORAL_RECORDING_STARTED";
|
|
13
13
|
CuoralMessageType["RECORDING_STOPPED"] = "CUORAL_RECORDING_STOPPED";
|
|
14
|
+
CuoralMessageType["RECORDING_UPLOADED"] = "CUORAL_RECORDING_UPLOADED";
|
|
14
15
|
CuoralMessageType["RECORDING_ERROR"] = "CUORAL_RECORDING_ERROR";
|
|
15
16
|
// Screenshot
|
|
16
17
|
CuoralMessageType["TAKE_SCREENSHOT"] = "CUORAL_TAKE_SCREENSHOT";
|
|
@@ -19,6 +20,9 @@ var CuoralMessageType;
|
|
|
19
20
|
// Widget Communication
|
|
20
21
|
CuoralMessageType["WIDGET_READY"] = "CUORAL_WIDGET_READY";
|
|
21
22
|
CuoralMessageType["WIDGET_CLOSED"] = "CUORAL_WIDGET_CLOSED";
|
|
23
|
+
CuoralMessageType["SESSION_UPDATED"] = "CUORAL_SESSION_UPDATED";
|
|
24
|
+
CuoralMessageType["REQUEST_SESSION_ID"] = "CUORAL_REQUEST_SESSION_ID";
|
|
25
|
+
CuoralMessageType["SESSION_ID_RESPONSE"] = "CUORAL_SESSION_ID_RESPONSE";
|
|
22
26
|
// File Upload
|
|
23
27
|
CuoralMessageType["UPLOAD_FILE"] = "CUORAL_UPLOAD_FILE";
|
|
24
28
|
CuoralMessageType["UPLOAD_PROGRESS"] = "CUORAL_UPLOAD_PROGRESS";
|
|
@@ -40,7 +44,7 @@ class CuoralRecorder {
|
|
|
40
44
|
/**
|
|
41
45
|
* Start recording with automatic permission handling
|
|
42
46
|
*/
|
|
43
|
-
async startRecording(options) {
|
|
47
|
+
async startRecording(options, sendMessages = true) {
|
|
44
48
|
try {
|
|
45
49
|
// Check if already recording
|
|
46
50
|
if (this.isRecording) {
|
|
@@ -62,20 +66,24 @@ class CuoralRecorder {
|
|
|
62
66
|
if (result.success) {
|
|
63
67
|
this.isRecording = true;
|
|
64
68
|
this.recordingStartTime = Date.now();
|
|
65
|
-
// Post message to widget
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
// Post message to widget only if enabled (disabled when called from widget handler)
|
|
70
|
+
if (sendMessages) {
|
|
71
|
+
this.postMessage({
|
|
72
|
+
type: CuoralMessageType.RECORDING_STARTED,
|
|
73
|
+
payload: { timestamp: this.recordingStartTime },
|
|
74
|
+
});
|
|
75
|
+
}
|
|
70
76
|
}
|
|
71
77
|
else {
|
|
72
78
|
// Recording failed - reset state and notify widget
|
|
73
79
|
this.isRecording = false;
|
|
74
80
|
this.recordingStartTime = undefined;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
if (sendMessages) {
|
|
82
|
+
this.postMessage({
|
|
83
|
+
type: CuoralMessageType.RECORDING_ERROR,
|
|
84
|
+
payload: { error: 'Failed to start recording' },
|
|
85
|
+
});
|
|
86
|
+
}
|
|
79
87
|
}
|
|
80
88
|
return result.success;
|
|
81
89
|
}
|
|
@@ -83,58 +91,71 @@ class CuoralRecorder {
|
|
|
83
91
|
// Exception occurred - reset state and notify widget
|
|
84
92
|
this.isRecording = false;
|
|
85
93
|
this.recordingStartTime = undefined;
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
94
|
+
if (sendMessages) {
|
|
95
|
+
this.postMessage({
|
|
96
|
+
type: CuoralMessageType.RECORDING_ERROR,
|
|
97
|
+
payload: { error: error.message },
|
|
98
|
+
});
|
|
99
|
+
}
|
|
90
100
|
return false;
|
|
91
101
|
}
|
|
92
102
|
}
|
|
93
103
|
/**
|
|
94
104
|
* Stop recording
|
|
95
105
|
*/
|
|
96
|
-
async stopRecording() {
|
|
106
|
+
async stopRecording(options, sendMessages = true) {
|
|
97
107
|
try {
|
|
98
108
|
if (!this.isRecording) {
|
|
99
109
|
// Send error message to widget so it can exit "stopping" state
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
110
|
+
if (sendMessages) {
|
|
111
|
+
this.postMessage({
|
|
112
|
+
type: CuoralMessageType.RECORDING_ERROR,
|
|
113
|
+
payload: { error: 'Not recording' },
|
|
114
|
+
});
|
|
115
|
+
}
|
|
104
116
|
return null;
|
|
105
117
|
}
|
|
106
|
-
const result = await CuoralPlugin$1.stopRecording();
|
|
118
|
+
const result = await CuoralPlugin$1.stopRecording(options);
|
|
107
119
|
if (result.success) {
|
|
108
120
|
this.isRecording = false;
|
|
109
121
|
const duration = this.recordingStartTime
|
|
110
122
|
? Math.floor((Date.now() - this.recordingStartTime) / 1000)
|
|
111
123
|
: 0;
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
124
|
+
// Send RECORDING_STOPPED message only if enabled
|
|
125
|
+
if (sendMessages) {
|
|
126
|
+
this.postMessage({
|
|
127
|
+
type: CuoralMessageType.RECORDING_STOPPED,
|
|
128
|
+
payload: {
|
|
129
|
+
filePath: result.filePath,
|
|
130
|
+
duration: result.duration || duration,
|
|
131
|
+
uploaded: result.uploaded,
|
|
132
|
+
uploadedToBackend: result.uploaded,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
120
136
|
return {
|
|
121
137
|
filePath: result.filePath,
|
|
122
138
|
duration: result.duration || duration,
|
|
139
|
+
uploaded: result.uploaded,
|
|
123
140
|
};
|
|
124
141
|
}
|
|
125
142
|
// If result.success is false, send error to widget
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
143
|
+
if (sendMessages) {
|
|
144
|
+
this.postMessage({
|
|
145
|
+
type: CuoralMessageType.RECORDING_ERROR,
|
|
146
|
+
payload: { error: 'Failed to stop recording' },
|
|
147
|
+
});
|
|
148
|
+
}
|
|
130
149
|
return null;
|
|
131
150
|
}
|
|
132
151
|
catch (error) {
|
|
133
152
|
console.error('[Cuoral] Failed to stop recording:', error);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
153
|
+
if (sendMessages) {
|
|
154
|
+
this.postMessage({
|
|
155
|
+
type: CuoralMessageType.RECORDING_ERROR,
|
|
156
|
+
payload: { error: error.message },
|
|
157
|
+
});
|
|
158
|
+
}
|
|
138
159
|
return null;
|
|
139
160
|
}
|
|
140
161
|
}
|
|
@@ -636,6 +657,24 @@ class CuoralIntelligence {
|
|
|
636
657
|
this.rageClickWindowMs = 2000; // Within 2 seconds
|
|
637
658
|
this.sessionId = sessionId;
|
|
638
659
|
}
|
|
660
|
+
/**
|
|
661
|
+
* Update the session ID (e.g., when user logs in)
|
|
662
|
+
*/
|
|
663
|
+
updateSessionId(newSessionId) {
|
|
664
|
+
console.log('[Cuoral Intelligence] Updating session ID from', this.sessionId, 'to', newSessionId);
|
|
665
|
+
this.sessionId = newSessionId;
|
|
666
|
+
// Flush any pending events with the old session before switching
|
|
667
|
+
this.flush();
|
|
668
|
+
// Update native error capture with new session ID
|
|
669
|
+
if (Capacitor.isNativePlatform()) {
|
|
670
|
+
CuoralPlugin.setupNativeErrorCapture({
|
|
671
|
+
backendUrl: this.config.consoleErrorBackendUrl,
|
|
672
|
+
sessionId: newSessionId
|
|
673
|
+
}).catch(error => {
|
|
674
|
+
console.warn('[Cuoral Intelligence] Failed to update native error capture:', error);
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
}
|
|
639
678
|
/**
|
|
640
679
|
* Initialize intelligence tracking
|
|
641
680
|
*/
|
|
@@ -965,6 +1004,7 @@ class CuoralIntelligence {
|
|
|
965
1004
|
url: window.location.href,
|
|
966
1005
|
data,
|
|
967
1006
|
};
|
|
1007
|
+
console.log(`[Intelligence] Enqueuing ${type} event with session:`, this.sessionId);
|
|
968
1008
|
queue.push(event);
|
|
969
1009
|
// Flush immediately for critical errors
|
|
970
1010
|
const shouldFlushImmediately = type === 'api_call' && (data.status_code === 0 || data.status_code >= 400);
|
|
@@ -1394,6 +1434,7 @@ class CuoralIntelligence {
|
|
|
1394
1434
|
*/
|
|
1395
1435
|
class Cuoral {
|
|
1396
1436
|
constructor(options) {
|
|
1437
|
+
console.log('[Cuoral] Constructor called with options:', options);
|
|
1397
1438
|
// Check if running on a mobile platform
|
|
1398
1439
|
if (!Capacitor.isNativePlatform()) {
|
|
1399
1440
|
throw new Error('Cuoral Ionic library only works on native mobile platforms (iOS/Android). Web is not supported.');
|
|
@@ -1403,6 +1444,7 @@ class Cuoral {
|
|
|
1403
1444
|
useModal: true,
|
|
1404
1445
|
...options
|
|
1405
1446
|
};
|
|
1447
|
+
console.log('[Cuoral] Merged options:', this.options);
|
|
1406
1448
|
// Determine widget base URL
|
|
1407
1449
|
const baseUrl = options.widgetBaseUrl || Cuoral.PRODUCTION_WIDGET_URL;
|
|
1408
1450
|
const params = new URLSearchParams({
|
|
@@ -1410,6 +1452,11 @@ class Cuoral {
|
|
|
1410
1452
|
key: options.publicKey,
|
|
1411
1453
|
_t: Date.now().toString(),
|
|
1412
1454
|
});
|
|
1455
|
+
// Add session_id if available (for widget to use existing session)
|
|
1456
|
+
const existingSessionId = localStorage.getItem('__x_loadID');
|
|
1457
|
+
if (existingSessionId) {
|
|
1458
|
+
params.set('cuoral_mobile_session_id', existingSessionId);
|
|
1459
|
+
}
|
|
1413
1460
|
if (options.email)
|
|
1414
1461
|
params.set('email', options.email);
|
|
1415
1462
|
if (options.firstName)
|
|
@@ -1427,25 +1474,36 @@ class Cuoral {
|
|
|
1427
1474
|
debug: options.debug || false
|
|
1428
1475
|
});
|
|
1429
1476
|
this.recorder = new CuoralRecorder();
|
|
1477
|
+
// Expose bridge on window for widget detection and debugging
|
|
1478
|
+
window.CuoralCapacitorBridge = true;
|
|
1479
|
+
window.__cuoralBridge = this.bridge; // For debugging
|
|
1480
|
+
window.__cuoralRecorder = this.recorder; // For debugging
|
|
1481
|
+
console.log('[Cuoral] Bridge and recorder created, exposed on window');
|
|
1430
1482
|
// Setup automatic message handlers
|
|
1431
1483
|
this.setupMessageHandlers();
|
|
1484
|
+
console.log('[Cuoral] Constructor complete');
|
|
1432
1485
|
}
|
|
1433
1486
|
/**
|
|
1434
1487
|
* Initialize Cuoral
|
|
1435
1488
|
*/
|
|
1436
1489
|
async initialize() {
|
|
1490
|
+
// Fetch session configuration and initialize intelligence if enabled by backend
|
|
1491
|
+
await this.initializeIntelligence();
|
|
1492
|
+
console.log('[Cuoral] Initialize - Session ID:', localStorage.getItem('__x_loadID'));
|
|
1493
|
+
// Setup localStorage listener to detect when widget changes session
|
|
1494
|
+
this.setupStorageListener();
|
|
1437
1495
|
this.bridge.initialize();
|
|
1438
1496
|
// Recreate modal if it was destroyed (e.g., after clearSession)
|
|
1439
1497
|
if (this.options.useModal && !this.modal) {
|
|
1440
1498
|
const widgetUrl = this.getWidgetUrl();
|
|
1441
1499
|
this.modal = new CuoralModal(widgetUrl, this.options.showFloatingButton);
|
|
1442
1500
|
}
|
|
1443
|
-
//
|
|
1501
|
+
// Update modal URL with session ID
|
|
1444
1502
|
if (this.modal) {
|
|
1503
|
+
const widgetUrl = this.getWidgetUrl();
|
|
1504
|
+
this.modal.updateWidgetUrl(widgetUrl);
|
|
1445
1505
|
this.modal.initialize();
|
|
1446
1506
|
}
|
|
1447
|
-
// Fetch session configuration and initialize intelligence if enabled by backend
|
|
1448
|
-
await this.initializeIntelligence();
|
|
1449
1507
|
}
|
|
1450
1508
|
/**
|
|
1451
1509
|
* Initialize intelligence based on backend configuration
|
|
@@ -1477,9 +1535,13 @@ class Cuoral {
|
|
|
1477
1535
|
}
|
|
1478
1536
|
// Only initialize intelligence if customer_intelligence is enabled in backend
|
|
1479
1537
|
if (config && config.customer_intelligence === true) {
|
|
1538
|
+
console.log('[Cuoral] Initializing intelligence with session:', sessionId);
|
|
1480
1539
|
this.intelligence = new CuoralIntelligence(sessionId);
|
|
1481
1540
|
this.intelligence.init();
|
|
1482
1541
|
}
|
|
1542
|
+
else {
|
|
1543
|
+
console.log('[Cuoral] Intelligence not enabled or no config for session:', sessionId);
|
|
1544
|
+
}
|
|
1483
1545
|
}
|
|
1484
1546
|
catch (error) {
|
|
1485
1547
|
console.warn('[Cuoral] Failed to initialize intelligence:', error);
|
|
@@ -1536,6 +1598,48 @@ class Cuoral {
|
|
|
1536
1598
|
this.intelligence.trackError(message, stackTrace, metadata);
|
|
1537
1599
|
}
|
|
1538
1600
|
}
|
|
1601
|
+
/**
|
|
1602
|
+
* Update user profile for the current session
|
|
1603
|
+
* Call this after user logs in to update the intelligence session with their profile
|
|
1604
|
+
* @param email - User's email address
|
|
1605
|
+
* @param name - User's full name
|
|
1606
|
+
*/
|
|
1607
|
+
async updateUserProfile(email, name) {
|
|
1608
|
+
try {
|
|
1609
|
+
const sessionId = localStorage.getItem('__x_loadID');
|
|
1610
|
+
if (!sessionId) {
|
|
1611
|
+
console.warn('[Cuoral] No session ID found, cannot update profile');
|
|
1612
|
+
return false;
|
|
1613
|
+
}
|
|
1614
|
+
console.log('[Cuoral] Updating user profile for session:', sessionId);
|
|
1615
|
+
const response = await fetch('https://api.cuoral.com/conversation/set-profile', {
|
|
1616
|
+
method: 'POST',
|
|
1617
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1618
|
+
body: JSON.stringify({
|
|
1619
|
+
session_id: sessionId,
|
|
1620
|
+
email: email,
|
|
1621
|
+
name: name,
|
|
1622
|
+
}),
|
|
1623
|
+
});
|
|
1624
|
+
if (!response.ok) {
|
|
1625
|
+
console.error('[Cuoral] Failed to update profile:', response.statusText);
|
|
1626
|
+
return false;
|
|
1627
|
+
}
|
|
1628
|
+
console.log('[Cuoral] ✓ User profile updated successfully for session:', sessionId);
|
|
1629
|
+
// Store user info locally
|
|
1630
|
+
this.options.email = email;
|
|
1631
|
+
const nameParts = name.split(' ');
|
|
1632
|
+
if (nameParts.length > 0) {
|
|
1633
|
+
this.options.firstName = nameParts[0];
|
|
1634
|
+
this.options.lastName = nameParts.slice(1).join(' ');
|
|
1635
|
+
}
|
|
1636
|
+
return true;
|
|
1637
|
+
}
|
|
1638
|
+
catch (error) {
|
|
1639
|
+
console.error('[Cuoral] Error updating user profile:', error);
|
|
1640
|
+
return false;
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1539
1643
|
/**
|
|
1540
1644
|
* Start native screen recording programmatically
|
|
1541
1645
|
* @returns Promise<boolean> - true if recording started successfully
|
|
@@ -1551,11 +1655,19 @@ class Cuoral {
|
|
|
1551
1655
|
}
|
|
1552
1656
|
/**
|
|
1553
1657
|
* Stop native screen recording programmatically
|
|
1554
|
-
*
|
|
1658
|
+
* Recording will be automatically uploaded to the portal
|
|
1659
|
+
* @returns Promise<{filePath?: string; duration?: number; uploaded?: boolean} | null> - Recording result or null if failed
|
|
1555
1660
|
*/
|
|
1556
1661
|
async stopRecording() {
|
|
1557
1662
|
try {
|
|
1558
|
-
|
|
1663
|
+
const sessionId = localStorage.getItem('__x_loadID');
|
|
1664
|
+
const customerId = localStorage.getItem('cuoralCustomerId');
|
|
1665
|
+
return await this.recorder.stopRecording({
|
|
1666
|
+
autoUpload: true,
|
|
1667
|
+
sessionId: sessionId || undefined,
|
|
1668
|
+
publicKey: this.options.publicKey,
|
|
1669
|
+
customerId: customerId || undefined,
|
|
1670
|
+
});
|
|
1559
1671
|
}
|
|
1560
1672
|
catch (error) {
|
|
1561
1673
|
console.error('[Cuoral] Failed to stop recording:', error);
|
|
@@ -1601,7 +1713,7 @@ class Cuoral {
|
|
|
1601
1713
|
// Add session_id if available
|
|
1602
1714
|
const sessionId = localStorage.getItem('__x_loadID');
|
|
1603
1715
|
if (sessionId) {
|
|
1604
|
-
params.set('
|
|
1716
|
+
params.set('cuoral_mobile_session_id', sessionId);
|
|
1605
1717
|
}
|
|
1606
1718
|
if (this.options.email)
|
|
1607
1719
|
params.set('email', this.options.email);
|
|
@@ -1655,6 +1767,28 @@ class Cuoral {
|
|
|
1655
1767
|
}
|
|
1656
1768
|
}
|
|
1657
1769
|
}
|
|
1770
|
+
/**
|
|
1771
|
+
* Setup localStorage listener for session changes
|
|
1772
|
+
* Widget updates localStorage when creating new session, SDK detects and syncs
|
|
1773
|
+
*/
|
|
1774
|
+
setupStorageListener() {
|
|
1775
|
+
// Poll localStorage every 2 seconds to detect session changes
|
|
1776
|
+
// (storage event doesn't fire for same-window changes)
|
|
1777
|
+
let lastKnownSession = localStorage.getItem('__x_loadID');
|
|
1778
|
+
setInterval(() => {
|
|
1779
|
+
const currentSession = localStorage.getItem('__x_loadID');
|
|
1780
|
+
if (currentSession && currentSession !== lastKnownSession) {
|
|
1781
|
+
console.log('[Cuoral] 🔄 Session changed in localStorage');
|
|
1782
|
+
console.log('[Cuoral] Old session:', lastKnownSession);
|
|
1783
|
+
console.log('[Cuoral] New session:', currentSession);
|
|
1784
|
+
lastKnownSession = currentSession;
|
|
1785
|
+
// Update intelligence to use new session
|
|
1786
|
+
if (this.intelligence) {
|
|
1787
|
+
this.intelligence.updateSessionId(currentSession);
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
}, 2000);
|
|
1791
|
+
}
|
|
1658
1792
|
/**
|
|
1659
1793
|
* Clean up resources
|
|
1660
1794
|
*/
|
|
@@ -1742,13 +1876,51 @@ class Cuoral {
|
|
|
1742
1876
|
setupMessageHandlers() {
|
|
1743
1877
|
// Handle start recording requests from widget
|
|
1744
1878
|
this.bridge.on(CuoralMessageType.START_RECORDING, async () => {
|
|
1745
|
-
|
|
1879
|
+
// Pass sendMessages: false to prevent duplicate messages
|
|
1880
|
+
const success = await this.recorder.startRecording(undefined, false);
|
|
1881
|
+
if (success) {
|
|
1882
|
+
// Send RECORDING_STARTED back to widget
|
|
1883
|
+
this.bridge.sendToWidget({
|
|
1884
|
+
type: CuoralMessageType.RECORDING_STARTED,
|
|
1885
|
+
payload: {
|
|
1886
|
+
timestamp: Date.now()
|
|
1887
|
+
}
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
else {
|
|
1891
|
+
this.bridge.sendToWidget({
|
|
1892
|
+
type: CuoralMessageType.RECORDING_ERROR,
|
|
1893
|
+
payload: {
|
|
1894
|
+
error: 'Failed to start recording'
|
|
1895
|
+
}
|
|
1896
|
+
});
|
|
1897
|
+
}
|
|
1746
1898
|
});
|
|
1747
1899
|
// Handle stop recording requests from widget
|
|
1748
1900
|
this.bridge.on(CuoralMessageType.STOP_RECORDING, async () => {
|
|
1749
|
-
const
|
|
1750
|
-
|
|
1751
|
-
|
|
1901
|
+
const sessionId = localStorage.getItem('__x_loadID');
|
|
1902
|
+
const customerId = localStorage.getItem('cuoralCustomerId');
|
|
1903
|
+
// Pass sendMessages: false to prevent duplicate messages
|
|
1904
|
+
const result = await this.recorder.stopRecording({
|
|
1905
|
+
autoUpload: true,
|
|
1906
|
+
sessionId: sessionId || undefined,
|
|
1907
|
+
publicKey: this.options.publicKey,
|
|
1908
|
+
customerId: customerId || undefined,
|
|
1909
|
+
}, false);
|
|
1910
|
+
if (result && result.uploaded) {
|
|
1911
|
+
// Video was automatically uploaded, notify widget with RECORDING_STOPPED
|
|
1912
|
+
this.bridge.sendToWidget({
|
|
1913
|
+
type: CuoralMessageType.RECORDING_STOPPED,
|
|
1914
|
+
payload: {
|
|
1915
|
+
duration: result.duration,
|
|
1916
|
+
uploaded: true,
|
|
1917
|
+
uploadedToBackend: true,
|
|
1918
|
+
timestamp: Date.now()
|
|
1919
|
+
}
|
|
1920
|
+
});
|
|
1921
|
+
}
|
|
1922
|
+
else if (result) {
|
|
1923
|
+
// Upload failed or was disabled, send video data to widget (old behavior)
|
|
1752
1924
|
const capacitorUrl = result.filePath ? Capacitor.convertFileSrc(result.filePath) : '';
|
|
1753
1925
|
try {
|
|
1754
1926
|
// Fetch the video blob from the capacitor URL
|