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.
Files changed (68) hide show
  1. package/android/build/.transforms/bb54161301273cf9b5b94a21c0fb3f23/transformed/classes/classes_dex/classes.dex +0 -0
  2. package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$1.dex +0 -0
  3. package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$2.dex +0 -0
  4. package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$3.dex +0 -0
  5. package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$4.dex +0 -0
  6. package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$5.dex +0 -0
  7. package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$6.dex +0 -0
  8. package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$InitiateCallback.dex +0 -0
  9. package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$UploadCallback.dex +0 -0
  10. package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin.dex +0 -0
  11. package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/desugar_graph.bin +0 -0
  12. package/android/build/intermediates/compile_library_classes_jar/debug/classes.jar +0 -0
  13. package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$1.class +0 -0
  14. package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$2.class +0 -0
  15. package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$3.class +0 -0
  16. package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$4.class +0 -0
  17. package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$5.class +0 -0
  18. package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$6.class +0 -0
  19. package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$InitiateCallback.class +0 -0
  20. package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$UploadCallback.class +0 -0
  21. package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin.class +0 -0
  22. package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$1.class +0 -0
  23. package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$2.class +0 -0
  24. package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$3.class +0 -0
  25. package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$4.class +0 -0
  26. package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$5.class +0 -0
  27. package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$6.class +0 -0
  28. package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$InitiateCallback.class +0 -0
  29. package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$UploadCallback.class +0 -0
  30. package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin.class +0 -0
  31. package/android/build/intermediates/runtime_library_classes_jar/debug/classes.jar +0 -0
  32. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/{CuoralPlugin$1.class.uniqueId1 → CuoralPlugin$1.class.uniqueId2} +0 -0
  33. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/{CuoralPlugin$2.class.uniqueId2 → CuoralPlugin$2.class.uniqueId5} +0 -0
  34. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$3.class.uniqueId3 +0 -0
  35. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$4.class.uniqueId6 +0 -0
  36. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$5.class.uniqueId4 +0 -0
  37. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$6.class.uniqueId8 +0 -0
  38. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$InitiateCallback.class.uniqueId0 +0 -0
  39. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$UploadCallback.class.uniqueId7 +0 -0
  40. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin.class.uniqueId1 +0 -0
  41. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  42. package/android/build.gradle +1 -0
  43. package/android/src/main/java/com/cuoral/ionic/CuoralPlugin.java +209 -6
  44. package/dist/bridge.d.ts.map +1 -1
  45. package/dist/cuoral.d.ts +15 -1
  46. package/dist/cuoral.d.ts.map +1 -1
  47. package/dist/cuoral.js +141 -12
  48. package/dist/index.esm.js +218 -46
  49. package/dist/index.esm.js.map +1 -1
  50. package/dist/index.js +218 -46
  51. package/dist/index.js.map +1 -1
  52. package/dist/intelligence.d.ts +4 -0
  53. package/dist/intelligence.d.ts.map +1 -1
  54. package/dist/intelligence.js +19 -0
  55. package/dist/plugin.d.ts +16 -3
  56. package/dist/plugin.d.ts.map +1 -1
  57. package/dist/plugin.js +53 -36
  58. package/dist/types.d.ts +4 -0
  59. package/dist/types.d.ts.map +1 -1
  60. package/dist/types.js +4 -0
  61. package/ios/Plugin/CuoralPlugin.swift +249 -13
  62. package/package.json +1 -1
  63. package/src/bridge.ts +1 -0
  64. package/src/cuoral.ts +158 -14
  65. package/src/intelligence.ts +23 -0
  66. package/src/plugin.ts +70 -37
  67. package/src/types.ts +4 -0
  68. 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
- // Initialize modal if enabled
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
- * @returns Promise<{filePath?: string; duration?: number} | null> - Recording result or null if failed
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
- return await this.recorder.stopRecording();
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('session_id', sessionId);
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
- const success = await this.recorder.startRecording();
361
- if (!success) {
362
- // Error already handled by recorder
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 result = await this.recorder.stopRecording();
368
- if (result) {
369
- // Convert file path to web-accessible URL
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
- this.postMessage({
67
- type: CuoralMessageType.RECORDING_STARTED,
68
- payload: { timestamp: this.recordingStartTime },
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
- this.postMessage({
76
- type: CuoralMessageType.RECORDING_ERROR,
77
- payload: { error: 'Failed to start recording' },
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
- this.postMessage({
87
- type: CuoralMessageType.RECORDING_ERROR,
88
- payload: { error: error.message },
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
- this.postMessage({
101
- type: CuoralMessageType.RECORDING_ERROR,
102
- payload: { error: 'Not recording' },
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
- // Post message to widget
113
- this.postMessage({
114
- type: CuoralMessageType.RECORDING_STOPPED,
115
- payload: {
116
- filePath: result.filePath,
117
- duration: result.duration || duration,
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
- this.postMessage({
127
- type: CuoralMessageType.RECORDING_ERROR,
128
- payload: { error: 'Failed to stop recording' },
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
- this.postMessage({
135
- type: CuoralMessageType.RECORDING_ERROR,
136
- payload: { error: error.message },
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
- // Initialize modal if enabled
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
- * @returns Promise<{filePath?: string; duration?: number} | null> - Recording result or null if failed
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
- return await this.recorder.stopRecording();
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('session_id', sessionId);
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
- await this.recorder.startRecording();
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 result = await this.recorder.stopRecording();
1750
- if (result) {
1751
- // Convert file path to web-accessible URL
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