cuoral-ionic 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -343,7 +343,19 @@ new Cuoral({
343
343
 
344
344
  ```typescript
345
345
  // Initialize Cuoral
346
- cuoral.initialize(): void
346
+ cuoral.initialize(): Promise<void>
347
+
348
+ // Track page/screen view (for intelligence)
349
+ cuoral.trackPageView(screen: string, metadata?: any): void
350
+
351
+ // Track error manually (for intelligence)
352
+ cuoral.trackError(message: string, stackTrace?: string, metadata?: any): void
353
+
354
+ // Start native screen recording programmatically
355
+ cuoral.startRecording(): Promise<boolean>
356
+
357
+ // Stop native screen recording programmatically
358
+ cuoral.stopRecording(): Promise<{filePath?: string; duration?: number} | null>
347
359
 
348
360
  // Get widget URL for iframe embedding
349
361
  cuoral.getWidgetUrl(): string
@@ -357,10 +369,69 @@ cuoral.closeModal(): void
357
369
  // Check if modal is open
358
370
  cuoral.isModalOpen(): boolean
359
371
 
372
+ // Clear and end current session (call before logout)
373
+ cuoral.clearSession(): Promise<void>
374
+
360
375
  // Clean up resources
361
376
  cuoral.destroy(): void
362
377
  ```
363
378
 
379
+ ### Programmatic Screen Recording
380
+
381
+ You can trigger native screen recording programmatically from your app code:
382
+
383
+ ```typescript
384
+ async startUserRecording() {
385
+ const started = await this.cuoral.startRecording();
386
+ if (started) {
387
+ console.log('Recording started successfully');
388
+ this.isRecording = true;
389
+ } else {
390
+ console.error('Failed to start recording');
391
+ }
392
+ }
393
+
394
+ async stopUserRecording() {
395
+ const result = await this.cuoral.stopRecording();
396
+ if (result) {
397
+ console.log('Recording stopped', {
398
+ filePath: result.filePath,
399
+ duration: result.duration
400
+ });
401
+ this.isRecording = false;
402
+ } else {
403
+ console.error('Failed to stop recording');
404
+ }
405
+ }
406
+ ```
407
+
408
+ **Use Cases:**
409
+ - Allow users to record their issue before contacting support
410
+ - Implement custom recording UI in your app
411
+ - Record specific user flows programmatically
412
+ - Create bug reporting features with automatic recording
413
+
414
+ **Note:** Recording still requires user permission on iOS (microphone access). The video file is automatically processed and available for playback in the support widget.
415
+
416
+ ### Manual Intelligence Tracking
417
+
418
+ Track custom events beyond automatic tracking:
419
+
420
+ ```typescript
421
+ // Track page/screen view
422
+ this.cuoral.trackPageView('/checkout', {
423
+ cart_items: 3,
424
+ total_value: 99.99
425
+ });
426
+
427
+ // Track error manually
428
+ this.cuoral.trackError(
429
+ 'Payment failed',
430
+ error.stack,
431
+ { payment_method: 'credit_card', amount: 99.99 }
432
+ );
433
+ ```
434
+
364
435
  ## What Gets Handled Automatically
365
436
 
366
437
  - ✅ Support ticket creation and management
package/dist/cuoral.d.ts CHANGED
@@ -40,6 +40,19 @@ export declare class Cuoral {
40
40
  * Track an error manually
41
41
  */
42
42
  trackError(message: string, stackTrace?: string, metadata?: any): void;
43
+ /**
44
+ * Start native screen recording programmatically
45
+ * @returns Promise<boolean> - true if recording started successfully
46
+ */
47
+ startRecording(): Promise<boolean>;
48
+ /**
49
+ * Stop native screen recording programmatically
50
+ * @returns Promise<{filePath?: string; duration?: number} | null> - Recording result or null if failed
51
+ */
52
+ stopRecording(): Promise<{
53
+ filePath?: string;
54
+ duration?: number;
55
+ } | null>;
43
56
  /**
44
57
  * Open the widget modal
45
58
  */
@@ -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;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"}
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;;;OAGG;IACU,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;IAS/C;;;OAGG;IACU,aAAa,IAAI,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAC,GAAG,IAAI,CAAC;IASpF;;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
@@ -151,6 +151,32 @@ export class Cuoral {
151
151
  this.intelligence.trackError(message, stackTrace, metadata);
152
152
  }
153
153
  }
154
+ /**
155
+ * Start native screen recording programmatically
156
+ * @returns Promise<boolean> - true if recording started successfully
157
+ */
158
+ async startRecording() {
159
+ try {
160
+ return await this.recorder.startRecording();
161
+ }
162
+ catch (error) {
163
+ console.error('[Cuoral] Failed to start recording:', error);
164
+ return false;
165
+ }
166
+ }
167
+ /**
168
+ * Stop native screen recording programmatically
169
+ * @returns Promise<{filePath?: string; duration?: number} | null> - Recording result or null if failed
170
+ */
171
+ async stopRecording() {
172
+ try {
173
+ return await this.recorder.stopRecording();
174
+ }
175
+ catch (error) {
176
+ console.error('[Cuoral] Failed to stop recording:', error);
177
+ return null;
178
+ }
179
+ }
154
180
  /**
155
181
  * Open the widget modal
156
182
  */
package/dist/index.esm.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { registerPlugin, Capacitor } from '@capacitor/core';
2
+ import * as rrweb from 'rrweb';
2
3
 
3
4
  /**
4
5
  * Message types for communication between WebView and Native code
@@ -602,11 +603,13 @@ class CuoralIntelligence {
602
603
  consoleErrorBackendUrl: 'https://api.cuoral.com/customer-intelligence/console-error',
603
604
  pageViewBackendUrl: 'https://api.cuoral.com/customer-intelligence/page-view',
604
605
  apiResponseBackendUrl: 'https://api.cuoral.com/customer-intelligence/api-response',
606
+ sessionReplayBackendUrl: 'https://api.cuoral.com/customer-intelligence/session-recording/batch',
605
607
  batchSize: 10,
606
608
  batchInterval: 2000,
607
609
  maxQueueSize: 30,
608
610
  retryAttempts: 1,
609
611
  retryDelay: 2000,
612
+ sessionReplayBatchInterval: 10000, // 10 seconds
610
613
  };
611
614
  this.queues = {
612
615
  console_error: [],
@@ -623,6 +626,14 @@ class CuoralIntelligence {
623
626
  // Network monitoring state
624
627
  this.originalFetch = null;
625
628
  this.originalXMLHttpRequest = null;
629
+ // Session replay state
630
+ this.rrwebStopFn = null;
631
+ this.rrwebEvents = [];
632
+ this.customEvents = [];
633
+ this.sessionReplayTimer = null;
634
+ this.clickTimestamps = new Map();
635
+ this.rageClickThreshold = 5; // 5 rapid clicks
636
+ this.rageClickWindowMs = 2000; // Within 2 seconds
626
637
  this.sessionId = sessionId;
627
638
  }
628
639
  /**
@@ -637,6 +648,7 @@ class CuoralIntelligence {
637
648
  this.setupNetworkMonitoring();
638
649
  this.setupAppStateListener();
639
650
  this.setupNativeErrorCapture();
651
+ this.setupSessionReplay();
640
652
  this.isInitialized = true;
641
653
  this.flushPendingEvents();
642
654
  }
@@ -684,6 +696,17 @@ class CuoralIntelligence {
684
696
  delete this.batchTimers[type];
685
697
  }
686
698
  });
699
+ // Stop session replay
700
+ if (this.rrwebStopFn) {
701
+ this.rrwebStopFn();
702
+ this.rrwebStopFn = null;
703
+ }
704
+ if (this.sessionReplayTimer) {
705
+ clearInterval(this.sessionReplayTimer);
706
+ this.sessionReplayTimer = null;
707
+ }
708
+ // Flush remaining session replay data
709
+ this.flushSessionReplayBatch();
687
710
  // Restore original functions
688
711
  if (this.originalFetch) {
689
712
  window.fetch = this.originalFetch;
@@ -1099,6 +1122,271 @@ class CuoralIntelligence {
1099
1122
  // Silently fail if plugin is not available
1100
1123
  }
1101
1124
  }
1125
+ /**
1126
+ * Setup session replay with rrweb
1127
+ */
1128
+ setupSessionReplay() {
1129
+ if (!this.sessionId) {
1130
+ console.warn('[Cuoral Intelligence] Session replay requires a session ID');
1131
+ return;
1132
+ }
1133
+ // Start rrweb recording
1134
+ this.rrwebStopFn = rrweb.record({
1135
+ emit: (event) => {
1136
+ this.rrwebEvents.push(event);
1137
+ },
1138
+ checkoutEveryNms: 60000, // Full snapshot every minute
1139
+ sampling: {
1140
+ scroll: 150, // Throttle scroll events
1141
+ media: 800,
1142
+ input: 'last', // Only record final input value (privacy)
1143
+ },
1144
+ maskAllInputs: true, // Mask sensitive inputs (privacy)
1145
+ blockClass: 'cuoral-block',
1146
+ ignoreClass: 'cuoral-ignore',
1147
+ });
1148
+ // Setup custom event tracking
1149
+ this.setupClickTracking();
1150
+ this.setupScrollTracking();
1151
+ this.setupFormTracking();
1152
+ // Start batch timer (send every ~10 seconds)
1153
+ this.sessionReplayTimer = setInterval(() => {
1154
+ this.flushSessionReplayBatch();
1155
+ }, this.config.sessionReplayBatchInterval);
1156
+ }
1157
+ /**
1158
+ * Setup click tracking (including rage clicks)
1159
+ */
1160
+ setupClickTracking() {
1161
+ document.addEventListener('click', (event) => {
1162
+ const target = event.target;
1163
+ if (!target)
1164
+ return;
1165
+ const selector = this.getElementSelector(target);
1166
+ const elementText = target.textContent?.trim().substring(0, 100) || '';
1167
+ const url = window.location.href;
1168
+ const now = Date.now();
1169
+ // Track regular click
1170
+ this.addCustomEvent({
1171
+ name: 'click',
1172
+ category: 'interaction',
1173
+ url,
1174
+ element_selector: selector,
1175
+ element_text: elementText,
1176
+ event_timestamp: new Date(now).toISOString(),
1177
+ session_id: this.sessionId,
1178
+ properties: this.getMetadata(),
1179
+ });
1180
+ // Track rage click detection
1181
+ const clicks = this.clickTimestamps.get(selector) || [];
1182
+ clicks.push(now);
1183
+ // Remove old clicks outside the time window
1184
+ const recentClicks = clicks.filter(timestamp => now - timestamp < this.rageClickWindowMs);
1185
+ this.clickTimestamps.set(selector, recentClicks);
1186
+ // If 5+ clicks within 2 seconds = rage click
1187
+ if (recentClicks.length >= this.rageClickThreshold) {
1188
+ this.addCustomEvent({
1189
+ name: 'rage_click',
1190
+ category: 'frustration',
1191
+ url,
1192
+ element_selector: selector,
1193
+ element_text: elementText,
1194
+ event_timestamp: new Date(now).toISOString(),
1195
+ session_id: this.sessionId,
1196
+ properties: {
1197
+ ...this.getMetadata(),
1198
+ click_count: recentClicks.length,
1199
+ },
1200
+ });
1201
+ // Clear after detecting rage click
1202
+ this.clickTimestamps.delete(selector);
1203
+ }
1204
+ }, true);
1205
+ }
1206
+ /**
1207
+ * Setup scroll depth tracking
1208
+ */
1209
+ setupScrollTracking() {
1210
+ let scrollDepths = new Set();
1211
+ let ticking = false;
1212
+ const trackScroll = () => {
1213
+ const scrollPercentage = Math.round((window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100);
1214
+ // Track milestones: 25%, 50%, 75%, 100%
1215
+ const milestones = [25, 50, 75, 100];
1216
+ for (const milestone of milestones) {
1217
+ if (scrollPercentage >= milestone && !scrollDepths.has(milestone)) {
1218
+ scrollDepths.add(milestone);
1219
+ this.addCustomEvent({
1220
+ name: 'scroll_depth',
1221
+ category: 'engagement',
1222
+ url: window.location.href,
1223
+ event_timestamp: new Date().toISOString(),
1224
+ session_id: this.sessionId,
1225
+ properties: {
1226
+ ...this.getMetadata(),
1227
+ percentage: milestone,
1228
+ },
1229
+ });
1230
+ }
1231
+ }
1232
+ ticking = false;
1233
+ };
1234
+ window.addEventListener('scroll', () => {
1235
+ if (!ticking) {
1236
+ window.requestAnimationFrame(trackScroll);
1237
+ ticking = true;
1238
+ }
1239
+ });
1240
+ }
1241
+ /**
1242
+ * Setup form tracking
1243
+ */
1244
+ setupFormTracking() {
1245
+ const trackedForms = new WeakSet();
1246
+ // Track form starts
1247
+ document.addEventListener('focusin', (event) => {
1248
+ const target = event.target;
1249
+ if (!target)
1250
+ return;
1251
+ const form = target.closest('form');
1252
+ if (form && !trackedForms.has(form)) {
1253
+ trackedForms.add(form);
1254
+ this.addCustomEvent({
1255
+ name: 'form_started',
1256
+ category: 'form',
1257
+ url: window.location.href,
1258
+ element_selector: this.getElementSelector(form),
1259
+ event_timestamp: new Date().toISOString(),
1260
+ session_id: this.sessionId,
1261
+ properties: this.getMetadata(),
1262
+ });
1263
+ }
1264
+ }, true);
1265
+ // Track form submissions
1266
+ document.addEventListener('submit', (event) => {
1267
+ const form = event.target;
1268
+ if (!form)
1269
+ return;
1270
+ this.addCustomEvent({
1271
+ name: 'form_submitted',
1272
+ category: 'form',
1273
+ url: window.location.href,
1274
+ element_selector: this.getElementSelector(form),
1275
+ event_timestamp: new Date().toISOString(),
1276
+ session_id: this.sessionId,
1277
+ properties: this.getMetadata(),
1278
+ });
1279
+ }, true);
1280
+ // Track form abandonment (on page exit with incomplete forms)
1281
+ window.addEventListener('beforeunload', () => {
1282
+ document.querySelectorAll('form').forEach((form) => {
1283
+ const inputs = form.querySelectorAll('input, textarea, select');
1284
+ const hasValue = Array.from(inputs).some((input) => input.value);
1285
+ if (hasValue && !trackedForms.has(form)) {
1286
+ this.addCustomEvent({
1287
+ name: 'form_abandoned',
1288
+ category: 'form',
1289
+ url: window.location.href,
1290
+ element_selector: this.getElementSelector(form),
1291
+ event_timestamp: new Date().toISOString(),
1292
+ session_id: this.sessionId,
1293
+ properties: this.getMetadata(),
1294
+ });
1295
+ }
1296
+ });
1297
+ });
1298
+ }
1299
+ /**
1300
+ * Track custom business events (flows, features, etc.)
1301
+ */
1302
+ trackCustomEvent(name, category, properties = {}, elementSelector, elementText) {
1303
+ this.addCustomEvent({
1304
+ name,
1305
+ category,
1306
+ url: window.location.href,
1307
+ element_selector: elementSelector,
1308
+ element_text: elementText,
1309
+ event_timestamp: new Date().toISOString(),
1310
+ session_id: this.sessionId,
1311
+ properties: {
1312
+ ...this.getMetadata(),
1313
+ ...properties,
1314
+ },
1315
+ });
1316
+ }
1317
+ /**
1318
+ * Add a custom event to the buffer
1319
+ */
1320
+ addCustomEvent(event) {
1321
+ this.customEvents.push(event);
1322
+ }
1323
+ /**
1324
+ * Flush session replay batch
1325
+ */
1326
+ flushSessionReplayBatch() {
1327
+ if (!this.sessionId)
1328
+ return;
1329
+ const eventsToSend = [...this.rrwebEvents];
1330
+ const customEventsToSend = [...this.customEvents];
1331
+ // Clear buffers
1332
+ this.rrwebEvents = [];
1333
+ this.customEvents = [];
1334
+ // Only send if there's data
1335
+ if (eventsToSend.length === 0 && customEventsToSend.length === 0) {
1336
+ return;
1337
+ }
1338
+ const batch = {
1339
+ session_id: this.sessionId,
1340
+ events: eventsToSend,
1341
+ custom_events: customEventsToSend,
1342
+ };
1343
+ this.sendSessionReplayBatch(batch);
1344
+ }
1345
+ /**
1346
+ * Send session replay batch to backend
1347
+ */
1348
+ async sendSessionReplayBatch(batch) {
1349
+ try {
1350
+ const response = await fetch(this.config.sessionReplayBackendUrl, {
1351
+ method: 'POST',
1352
+ headers: {
1353
+ 'Content-Type': 'application/json',
1354
+ },
1355
+ body: JSON.stringify(batch),
1356
+ });
1357
+ if (!response.ok) {
1358
+ console.warn('[Cuoral Intelligence] Failed to send session replay batch');
1359
+ }
1360
+ }
1361
+ catch (error) {
1362
+ console.warn('[Cuoral Intelligence] Error sending session replay batch:', error);
1363
+ }
1364
+ }
1365
+ /**
1366
+ * Get element selector (CSS selector)
1367
+ */
1368
+ getElementSelector(element) {
1369
+ if (element.id) {
1370
+ return `#${element.id}`;
1371
+ }
1372
+ if (element.className && typeof element.className === 'string') {
1373
+ const classes = element.className.split(' ').filter(c => c).join('.');
1374
+ if (classes) {
1375
+ return `${element.tagName.toLowerCase()}.${classes}`;
1376
+ }
1377
+ }
1378
+ return element.tagName.toLowerCase();
1379
+ }
1380
+ /**
1381
+ * Get metadata (user agent, screen resolution, viewport)
1382
+ */
1383
+ getMetadata() {
1384
+ return {
1385
+ user_agent: navigator.userAgent,
1386
+ screen_resolution: `${screen.width}x${screen.height}`,
1387
+ viewport_size: `${window.innerWidth}x${window.innerHeight}`,
1388
+ };
1389
+ }
1102
1390
  }
1103
1391
 
1104
1392
  /**
@@ -1248,6 +1536,32 @@ class Cuoral {
1248
1536
  this.intelligence.trackError(message, stackTrace, metadata);
1249
1537
  }
1250
1538
  }
1539
+ /**
1540
+ * Start native screen recording programmatically
1541
+ * @returns Promise<boolean> - true if recording started successfully
1542
+ */
1543
+ async startRecording() {
1544
+ try {
1545
+ return await this.recorder.startRecording();
1546
+ }
1547
+ catch (error) {
1548
+ console.error('[Cuoral] Failed to start recording:', error);
1549
+ return false;
1550
+ }
1551
+ }
1552
+ /**
1553
+ * Stop native screen recording programmatically
1554
+ * @returns Promise<{filePath?: string; duration?: number} | null> - Recording result or null if failed
1555
+ */
1556
+ async stopRecording() {
1557
+ try {
1558
+ return await this.recorder.stopRecording();
1559
+ }
1560
+ catch (error) {
1561
+ console.error('[Cuoral] Failed to stop recording:', error);
1562
+ return null;
1563
+ }
1564
+ }
1251
1565
  /**
1252
1566
  * Open the widget modal
1253
1567
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}