cuoral-ionic 0.0.1 → 0.0.3
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 +81 -4
- package/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.9/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/build/.transforms/2c48e1f34ca03014b78fcb3e0ab7197b/results.bin +1 -0
- package/android/build/.transforms/2c48e1f34ca03014b78fcb3e0ab7197b/transformed/classes/classes_dex/classes.dex +0 -0
- package/android/build/.transforms/980c51bd075f726f311ad662d5d20ba0/results.bin +1 -0
- package/android/build/.transforms/980c51bd075f726f311ad662d5d20ba0/transformed/classes/classes_dex/classes.dex +0 -0
- package/android/build/.transforms/bb54161301273cf9b5b94a21c0fb3f23/results.bin +1 -0
- package/android/build/.transforms/bb54161301273cf9b5b94a21c0fb3f23/transformed/classes/classes_dex/classes.dex +0 -0
- package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/results.bin +1 -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.dex +0 -0
- package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/ScreenRecordService.dex +0 -0
- package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/desugar_graph.bin +0 -0
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/AndroidManifest.xml +22 -0
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/output-metadata.json +18 -0
- package/android/build/intermediates/aar_main_jar/debug/classes.jar +0 -0
- package/android/build/intermediates/aar_metadata/debug/aar-metadata.properties +6 -0
- package/android/build/intermediates/annotation_processor_list/debug/annotationProcessors.json +1 -0
- package/android/build/intermediates/annotations_typedef_file/debug/typedefs.txt +0 -0
- package/android/build/intermediates/compile_library_classes_jar/debug/classes.jar +0 -0
- package/android/build/intermediates/compile_r_class_jar/debug/R.jar +0 -0
- package/android/build/intermediates/compile_symbol_list/debug/R.txt +0 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +2 -0
- package/android/build/intermediates/incremental/debug-mergeJavaRes/merge-state +0 -0
- package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +2 -0
- package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +2 -0
- package/android/build/intermediates/incremental/packageDebugAssets/merger.xml +2 -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.class +0 -0
- package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/ScreenRecordService.class +0 -0
- package/android/build/intermediates/local_only_symbol_list/debug/R-def.txt +2 -0
- package/android/build/intermediates/manifest_merge_blame_file/debug/manifest-merger-blame-debug-report.txt +38 -0
- package/android/build/intermediates/merged_java_res/debug/feature-cuoral-ionic.jar +0 -0
- package/android/build/intermediates/merged_manifest/debug/AndroidManifest.xml +22 -0
- package/android/build/intermediates/navigation_json/debug/navigation.json +1 -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.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/ScreenRecordService.class +0 -0
- package/android/build/intermediates/runtime_library_classes_jar/debug/classes.jar +0 -0
- package/android/build/intermediates/symbol_list_with_package_name/debug/package-aware-r.txt +1 -0
- package/android/build/outputs/aar/cuoral-ionic-debug.aar +0 -0
- package/android/build/outputs/logs/manifest-merger-debug-report.txt +49 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$1.class.uniqueId1 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$2.class.uniqueId2 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin.class.uniqueId0 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
- package/android/build.gradle +61 -0
- package/android/src/main/AndroidManifest.xml +9 -0
- package/android/src/main/java/com/cuoral/ionic/CuoralPlugin.java +290 -33
- package/android/src/main/java/com/cuoral/ionic/ScreenRecordService.java +59 -0
- package/dist/cuoral.d.ts +30 -1
- package/dist/cuoral.d.ts.map +1 -1
- package/dist/cuoral.js +168 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +706 -127
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +706 -126
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +66 -0
- package/dist/intelligence.d.ts.map +1 -0
- package/dist/intelligence.js +508 -0
- package/dist/plugin.d.ts +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +24 -6
- package/dist/web.d.ts.map +1 -1
- package/dist/web.js +0 -3
- package/ios/Plugin/CuoralPlugin.swift +78 -1
- package/package.json +1 -1
- package/src/cuoral.ts +205 -1
- package/src/index.ts +1 -0
- package/src/intelligence.ts +609 -0
- package/src/plugin.ts +26 -6
- package/src/web.ts +0 -6
package/dist/index.esm.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { registerPlugin, Capacitor
|
|
1
|
+
import { registerPlugin, Capacitor } from '@capacitor/core';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Message types for communication between WebView and Native code
|
|
@@ -26,11 +26,9 @@ var CuoralMessageType;
|
|
|
26
26
|
})(CuoralMessageType || (CuoralMessageType = {}));
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Register the Capacitor plugin
|
|
29
|
+
* Register the Capacitor plugin (mobile only - iOS and Android)
|
|
30
30
|
*/
|
|
31
|
-
const CuoralPlugin = registerPlugin('CuoralPlugin'
|
|
32
|
-
web: () => Promise.resolve().then(function () { return web; }).then(m => new m.CuoralPluginWeb()),
|
|
33
|
-
});
|
|
31
|
+
const CuoralPlugin$1 = registerPlugin('CuoralPlugin');
|
|
34
32
|
/**
|
|
35
33
|
* High-level API for easier integration
|
|
36
34
|
*/
|
|
@@ -49,17 +47,17 @@ class CuoralRecorder {
|
|
|
49
47
|
return false;
|
|
50
48
|
}
|
|
51
49
|
// Check support
|
|
52
|
-
const { supported } = await CuoralPlugin.isRecordingSupported();
|
|
50
|
+
const { supported } = await CuoralPlugin$1.isRecordingSupported();
|
|
53
51
|
if (!supported) {
|
|
54
52
|
throw new Error('Screen recording is not supported on this device');
|
|
55
53
|
}
|
|
56
54
|
// Request permissions
|
|
57
|
-
const { granted } = await CuoralPlugin.requestPermissions();
|
|
55
|
+
const { granted } = await CuoralPlugin$1.requestPermissions();
|
|
58
56
|
if (!granted) {
|
|
59
57
|
throw new Error('Recording permissions not granted');
|
|
60
58
|
}
|
|
61
59
|
// Start recording
|
|
62
|
-
const result = await CuoralPlugin.startRecording(options);
|
|
60
|
+
const result = await CuoralPlugin$1.startRecording(options);
|
|
63
61
|
if (result.success) {
|
|
64
62
|
this.isRecording = true;
|
|
65
63
|
this.recordingStartTime = Date.now();
|
|
@@ -69,10 +67,21 @@ class CuoralRecorder {
|
|
|
69
67
|
payload: { timestamp: this.recordingStartTime },
|
|
70
68
|
});
|
|
71
69
|
}
|
|
70
|
+
else {
|
|
71
|
+
// Recording failed - reset state and notify widget
|
|
72
|
+
this.isRecording = false;
|
|
73
|
+
this.recordingStartTime = undefined;
|
|
74
|
+
this.postMessage({
|
|
75
|
+
type: CuoralMessageType.RECORDING_ERROR,
|
|
76
|
+
payload: { error: 'Failed to start recording' },
|
|
77
|
+
});
|
|
78
|
+
}
|
|
72
79
|
return result.success;
|
|
73
80
|
}
|
|
74
81
|
catch (error) {
|
|
75
|
-
|
|
82
|
+
// Exception occurred - reset state and notify widget
|
|
83
|
+
this.isRecording = false;
|
|
84
|
+
this.recordingStartTime = undefined;
|
|
76
85
|
this.postMessage({
|
|
77
86
|
type: CuoralMessageType.RECORDING_ERROR,
|
|
78
87
|
payload: { error: error.message },
|
|
@@ -86,10 +95,14 @@ class CuoralRecorder {
|
|
|
86
95
|
async stopRecording() {
|
|
87
96
|
try {
|
|
88
97
|
if (!this.isRecording) {
|
|
89
|
-
|
|
98
|
+
// Send error message to widget so it can exit "stopping" state
|
|
99
|
+
this.postMessage({
|
|
100
|
+
type: CuoralMessageType.RECORDING_ERROR,
|
|
101
|
+
payload: { error: 'Not recording' },
|
|
102
|
+
});
|
|
90
103
|
return null;
|
|
91
104
|
}
|
|
92
|
-
const result = await CuoralPlugin.stopRecording();
|
|
105
|
+
const result = await CuoralPlugin$1.stopRecording();
|
|
93
106
|
if (result.success) {
|
|
94
107
|
this.isRecording = false;
|
|
95
108
|
const duration = this.recordingStartTime
|
|
@@ -108,6 +121,11 @@ class CuoralRecorder {
|
|
|
108
121
|
duration: result.duration || duration,
|
|
109
122
|
};
|
|
110
123
|
}
|
|
124
|
+
// If result.success is false, send error to widget
|
|
125
|
+
this.postMessage({
|
|
126
|
+
type: CuoralMessageType.RECORDING_ERROR,
|
|
127
|
+
payload: { error: 'Failed to stop recording' },
|
|
128
|
+
});
|
|
111
129
|
return null;
|
|
112
130
|
}
|
|
113
131
|
catch (error) {
|
|
@@ -124,7 +142,7 @@ class CuoralRecorder {
|
|
|
124
142
|
*/
|
|
125
143
|
async takeScreenshot(options) {
|
|
126
144
|
try {
|
|
127
|
-
const screenshot = await CuoralPlugin.takeScreenshot(options);
|
|
145
|
+
const screenshot = await CuoralPlugin$1.takeScreenshot(options);
|
|
128
146
|
// Post message to widget
|
|
129
147
|
this.postMessage({
|
|
130
148
|
type: CuoralMessageType.SCREENSHOT_TAKEN,
|
|
@@ -145,7 +163,7 @@ class CuoralRecorder {
|
|
|
145
163
|
* Get current recording state
|
|
146
164
|
*/
|
|
147
165
|
async getState() {
|
|
148
|
-
return await CuoralPlugin.getRecordingState();
|
|
166
|
+
return await CuoralPlugin$1.getRecordingState();
|
|
149
167
|
}
|
|
150
168
|
/**
|
|
151
169
|
* Post message to WebView
|
|
@@ -537,11 +555,522 @@ class CuoralModal {
|
|
|
537
555
|
}
|
|
538
556
|
}
|
|
539
557
|
|
|
558
|
+
// Register the Cuoral plugin for native error capture
|
|
559
|
+
const CuoralPlugin = registerPlugin('CuoralPlugin');
|
|
560
|
+
class CuoralIntelligence {
|
|
561
|
+
constructor(sessionId) {
|
|
562
|
+
this.config = {
|
|
563
|
+
consoleErrorBackendUrl: 'https://api.cuoral.com/customer-intelligence/console-error',
|
|
564
|
+
pageViewBackendUrl: 'https://api.cuoral.com/customer-intelligence/page-view',
|
|
565
|
+
apiResponseBackendUrl: 'https://api.cuoral.com/customer-intelligence/api-response',
|
|
566
|
+
batchSize: 10,
|
|
567
|
+
batchInterval: 2000,
|
|
568
|
+
maxQueueSize: 30,
|
|
569
|
+
retryAttempts: 1,
|
|
570
|
+
retryDelay: 2000,
|
|
571
|
+
};
|
|
572
|
+
this.queues = {
|
|
573
|
+
console_error: [],
|
|
574
|
+
unhandled_error: [],
|
|
575
|
+
page_view: [],
|
|
576
|
+
api_call: [],
|
|
577
|
+
};
|
|
578
|
+
this.batchTimers = {};
|
|
579
|
+
this.sessionId = null;
|
|
580
|
+
this.isInitialized = false;
|
|
581
|
+
this.lastPageViewTimestamp = Date.now();
|
|
582
|
+
this.lastPageViewScreen = '';
|
|
583
|
+
this.pendingEvents = [];
|
|
584
|
+
// Network monitoring state
|
|
585
|
+
this.originalFetch = null;
|
|
586
|
+
this.originalXMLHttpRequest = null;
|
|
587
|
+
this.sessionId = sessionId;
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Initialize intelligence tracking
|
|
591
|
+
*/
|
|
592
|
+
init() {
|
|
593
|
+
if (this.isInitialized) {
|
|
594
|
+
console.warn('[Cuoral Intelligence] Already initialized');
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
this.setupConsoleErrorListener();
|
|
598
|
+
this.setupNetworkMonitoring();
|
|
599
|
+
this.setupAppStateListener();
|
|
600
|
+
this.setupNativeErrorCapture();
|
|
601
|
+
this.isInitialized = true;
|
|
602
|
+
this.flushPendingEvents();
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Track a page/screen view
|
|
606
|
+
*/
|
|
607
|
+
trackPageView(screen, metadata) {
|
|
608
|
+
const duration = Date.now() - this.lastPageViewTimestamp;
|
|
609
|
+
this.enqueueEvent('page_view', {
|
|
610
|
+
screen,
|
|
611
|
+
referrer: this.lastPageViewScreen,
|
|
612
|
+
duration,
|
|
613
|
+
metadata: metadata || {},
|
|
614
|
+
});
|
|
615
|
+
this.lastPageViewTimestamp = Date.now();
|
|
616
|
+
this.lastPageViewScreen = screen;
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Track a console error manually
|
|
620
|
+
*/
|
|
621
|
+
trackError(message, stackTrace, metadata) {
|
|
622
|
+
this.enqueueEvent('console_error', {
|
|
623
|
+
message,
|
|
624
|
+
stack_trace: stackTrace || 'No stack trace available',
|
|
625
|
+
log_level: 'error',
|
|
626
|
+
metadata: metadata || {},
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Flush all queued events immediately
|
|
631
|
+
*/
|
|
632
|
+
flush() {
|
|
633
|
+
Object.keys(this.queues).forEach((type) => {
|
|
634
|
+
this.flushQueue(type);
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Destroy and cleanup
|
|
639
|
+
*/
|
|
640
|
+
destroy() {
|
|
641
|
+
// Clear all timers
|
|
642
|
+
Object.keys(this.batchTimers).forEach((type) => {
|
|
643
|
+
if (this.batchTimers[type]) {
|
|
644
|
+
clearTimeout(this.batchTimers[type]);
|
|
645
|
+
delete this.batchTimers[type];
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
// Restore original functions
|
|
649
|
+
if (this.originalFetch) {
|
|
650
|
+
window.fetch = this.originalFetch;
|
|
651
|
+
this.originalFetch = null;
|
|
652
|
+
}
|
|
653
|
+
if (this.originalXMLHttpRequest) {
|
|
654
|
+
window.XMLHttpRequest = this.originalXMLHttpRequest;
|
|
655
|
+
this.originalXMLHttpRequest = null;
|
|
656
|
+
}
|
|
657
|
+
// Clear queues
|
|
658
|
+
Object.keys(this.queues).forEach((type) => {
|
|
659
|
+
this.queues[type] = [];
|
|
660
|
+
});
|
|
661
|
+
this.isInitialized = false;
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Setup console error listener
|
|
665
|
+
*/
|
|
666
|
+
setupConsoleErrorListener() {
|
|
667
|
+
// Override console.error
|
|
668
|
+
const originalConsoleError = console.error;
|
|
669
|
+
console.error = (...args) => {
|
|
670
|
+
originalConsoleError.apply(console, args);
|
|
671
|
+
try {
|
|
672
|
+
// Try to find an Error object in arguments to get real stack trace
|
|
673
|
+
let stack = '';
|
|
674
|
+
let message = '';
|
|
675
|
+
// Check each argument for error information
|
|
676
|
+
for (const arg of args) {
|
|
677
|
+
if (arg instanceof Error) {
|
|
678
|
+
stack = arg.stack || '';
|
|
679
|
+
message = arg.message;
|
|
680
|
+
break;
|
|
681
|
+
}
|
|
682
|
+
else if (arg && typeof arg === 'object') {
|
|
683
|
+
if (arg.stack && typeof arg.stack === 'string') {
|
|
684
|
+
stack = arg.stack;
|
|
685
|
+
message = arg.message || arg.toString();
|
|
686
|
+
break;
|
|
687
|
+
}
|
|
688
|
+
if (arg.rejection && arg.rejection instanceof Error) {
|
|
689
|
+
stack = arg.rejection.stack || '';
|
|
690
|
+
message = arg.rejection.message;
|
|
691
|
+
break;
|
|
692
|
+
}
|
|
693
|
+
if (arg.error instanceof Error) {
|
|
694
|
+
stack = arg.error.stack || '';
|
|
695
|
+
message = arg.error.message;
|
|
696
|
+
break;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
// If no stack found, generate one from current context
|
|
701
|
+
// Note: Angular's ErrorHandler doesn't pass Error objects, so this will
|
|
702
|
+
// capture the console.error call stack, not the original error stack.
|
|
703
|
+
// For better error tracking, use source maps or test with unminified builds.
|
|
704
|
+
if (!stack) {
|
|
705
|
+
const error = new Error();
|
|
706
|
+
stack = error.stack || 'No stack trace available';
|
|
707
|
+
message = args.map((a) => (typeof a === 'object' ? JSON.stringify(a) : String(a))).join(' ');
|
|
708
|
+
}
|
|
709
|
+
this.enqueueEvent('console_error', {
|
|
710
|
+
message: message || 'Console error',
|
|
711
|
+
stack_trace: stack,
|
|
712
|
+
log_level: 'error',
|
|
713
|
+
metadata: {},
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
catch (err) {
|
|
717
|
+
// Silently fail
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
// Global error handler
|
|
721
|
+
window.onerror = (message, source, lineno, colno, error) => {
|
|
722
|
+
try {
|
|
723
|
+
this.enqueueEvent('unhandled_error', {
|
|
724
|
+
message: String(message),
|
|
725
|
+
source: source || 'unknown',
|
|
726
|
+
lineno: lineno || 0,
|
|
727
|
+
colno: colno || 0,
|
|
728
|
+
stack_trace: error?.stack || 'No stack trace available',
|
|
729
|
+
log_level: 'error',
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
catch (err) {
|
|
733
|
+
// Silently fail
|
|
734
|
+
}
|
|
735
|
+
return false;
|
|
736
|
+
};
|
|
737
|
+
// Unhandled promise rejection
|
|
738
|
+
window.addEventListener('unhandledrejection', (event) => {
|
|
739
|
+
this.enqueueEvent('unhandled_error', {
|
|
740
|
+
message: event.reason instanceof Error ? event.reason.message : String(event.reason),
|
|
741
|
+
stack_trace: event.reason?.stack || 'No stack trace available',
|
|
742
|
+
log_level: 'error',
|
|
743
|
+
});
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Setup network monitoring
|
|
748
|
+
*/
|
|
749
|
+
setupNetworkMonitoring() {
|
|
750
|
+
// Store original fetch
|
|
751
|
+
if (!this.originalFetch) {
|
|
752
|
+
this.originalFetch = window.fetch;
|
|
753
|
+
}
|
|
754
|
+
// Override fetch
|
|
755
|
+
window.fetch = async (...args) => {
|
|
756
|
+
const url = args[0] instanceof Request ? args[0].url : args[0];
|
|
757
|
+
const method = args[0] instanceof Request && args[0].method
|
|
758
|
+
? args[0].method
|
|
759
|
+
: args[1]?.method || 'GET';
|
|
760
|
+
const startTime = Date.now();
|
|
761
|
+
try {
|
|
762
|
+
const response = await this.originalFetch.apply(window, args);
|
|
763
|
+
const duration = Date.now() - startTime;
|
|
764
|
+
const status = response.status;
|
|
765
|
+
// Only track errors (4xx, 5xx)
|
|
766
|
+
if (status >= 400) {
|
|
767
|
+
const responseClone = response.clone();
|
|
768
|
+
try {
|
|
769
|
+
const text = await responseClone.text();
|
|
770
|
+
let responseData = null;
|
|
771
|
+
try {
|
|
772
|
+
const truncated = text.length > 5000 ? text.substring(0, 5000) + '...' : text;
|
|
773
|
+
responseData = JSON.parse(truncated);
|
|
774
|
+
}
|
|
775
|
+
catch (e) {
|
|
776
|
+
responseData = text.length > 5000 ? text.substring(0, 5000) + '...' : text;
|
|
777
|
+
}
|
|
778
|
+
this.enqueueEvent('api_call', {
|
|
779
|
+
method,
|
|
780
|
+
url,
|
|
781
|
+
status_code: status,
|
|
782
|
+
response_data: responseData,
|
|
783
|
+
duration,
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
catch (err) {
|
|
787
|
+
this.enqueueEvent('api_call', {
|
|
788
|
+
method,
|
|
789
|
+
url,
|
|
790
|
+
status_code: status,
|
|
791
|
+
duration,
|
|
792
|
+
error: 'Failed to read response body',
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return response;
|
|
797
|
+
}
|
|
798
|
+
catch (error) {
|
|
799
|
+
const duration = Date.now() - startTime;
|
|
800
|
+
this.enqueueEvent('api_call', {
|
|
801
|
+
method,
|
|
802
|
+
url,
|
|
803
|
+
status_code: 0,
|
|
804
|
+
response_data: error.message || 'Network Error',
|
|
805
|
+
duration,
|
|
806
|
+
error: error.message || 'Network Error',
|
|
807
|
+
});
|
|
808
|
+
throw error;
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
// Store original XMLHttpRequest
|
|
812
|
+
if (!this.originalXMLHttpRequest && window.XMLHttpRequest) {
|
|
813
|
+
this.originalXMLHttpRequest = window.XMLHttpRequest;
|
|
814
|
+
}
|
|
815
|
+
// Override XMLHttpRequest
|
|
816
|
+
const originalOpen = XMLHttpRequest.prototype.open;
|
|
817
|
+
const originalSend = XMLHttpRequest.prototype.send;
|
|
818
|
+
XMLHttpRequest.prototype.open = function (method, url) {
|
|
819
|
+
this._cuoralMethod = method;
|
|
820
|
+
this._cuoralUrl = url;
|
|
821
|
+
return originalOpen.apply(this, arguments);
|
|
822
|
+
};
|
|
823
|
+
XMLHttpRequest.prototype.send = function () {
|
|
824
|
+
const startTime = Date.now();
|
|
825
|
+
const xhr = this;
|
|
826
|
+
const loadendHandler = () => {
|
|
827
|
+
try {
|
|
828
|
+
const status = xhr.status;
|
|
829
|
+
const duration = Date.now() - startTime;
|
|
830
|
+
// Only track errors (4xx, 5xx)
|
|
831
|
+
if (status >= 400) {
|
|
832
|
+
let responseData = null;
|
|
833
|
+
try {
|
|
834
|
+
const responseText = xhr.responseText || '';
|
|
835
|
+
const truncated = responseText.length > 5000
|
|
836
|
+
? responseText.substring(0, 5000) + '...'
|
|
837
|
+
: responseText;
|
|
838
|
+
responseData = truncated ? JSON.parse(truncated) : truncated;
|
|
839
|
+
}
|
|
840
|
+
catch (e) {
|
|
841
|
+
const responseText = xhr.responseText || '';
|
|
842
|
+
responseData = responseText.length > 5000
|
|
843
|
+
? responseText.substring(0, 5000) + '...'
|
|
844
|
+
: responseText;
|
|
845
|
+
}
|
|
846
|
+
window._cuoralIntelligence?.enqueueEvent('api_call', {
|
|
847
|
+
method: xhr._cuoralMethod,
|
|
848
|
+
url: xhr._cuoralUrl,
|
|
849
|
+
status_code: status,
|
|
850
|
+
response_data: responseData,
|
|
851
|
+
duration,
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
catch (err) {
|
|
856
|
+
// Silently fail
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
this.addEventListener('loadend', loadendHandler, { once: true });
|
|
860
|
+
return originalSend.apply(this, arguments);
|
|
861
|
+
};
|
|
862
|
+
// Store reference for XMLHttpRequest monitoring
|
|
863
|
+
window._cuoralIntelligence = this;
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Setup app state listener
|
|
867
|
+
*/
|
|
868
|
+
setupAppStateListener() {
|
|
869
|
+
// Listen for app going to background
|
|
870
|
+
window.addEventListener('visibilitychange', () => {
|
|
871
|
+
if (document.visibilityState === 'hidden') {
|
|
872
|
+
this.flush();
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
// Listen for app pause (Capacitor)
|
|
876
|
+
if (Capacitor.isNativePlatform()) {
|
|
877
|
+
window.addEventListener('pause', () => {
|
|
878
|
+
this.flush();
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Enqueue an event
|
|
884
|
+
*/
|
|
885
|
+
enqueueEvent(type, data) {
|
|
886
|
+
const queue = this.queues[type];
|
|
887
|
+
if (!queue) {
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
// Don't track successful API calls (2xx, 3xx)
|
|
891
|
+
if (type === 'api_call' && data.status_code >= 200 && data.status_code < 400) {
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
// Limit queue size
|
|
895
|
+
if (queue.length >= this.config.maxQueueSize) {
|
|
896
|
+
queue.shift();
|
|
897
|
+
}
|
|
898
|
+
const event = {
|
|
899
|
+
event_type: type,
|
|
900
|
+
timestamp: Date.now(),
|
|
901
|
+
session_id: this.sessionId || '',
|
|
902
|
+
user_agent: navigator.userAgent,
|
|
903
|
+
url: window.location.href,
|
|
904
|
+
data,
|
|
905
|
+
};
|
|
906
|
+
queue.push(event);
|
|
907
|
+
// Flush immediately for critical errors
|
|
908
|
+
const shouldFlushImmediately = type === 'api_call' && (data.status_code === 0 || data.status_code >= 400);
|
|
909
|
+
if (shouldFlushImmediately || queue.length >= this.config.batchSize) {
|
|
910
|
+
this.flushQueue(type);
|
|
911
|
+
}
|
|
912
|
+
else if (!this.batchTimers[type]) {
|
|
913
|
+
this.batchTimers[type] = setTimeout(() => this.flushQueue(type), this.config.batchInterval);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Flush a specific queue
|
|
918
|
+
*/
|
|
919
|
+
flushQueue(eventType, useBeacon = false) {
|
|
920
|
+
const queue = this.queues[eventType];
|
|
921
|
+
if (!queue || queue.length === 0) {
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
// Clear timer
|
|
925
|
+
if (this.batchTimers[eventType]) {
|
|
926
|
+
clearTimeout(this.batchTimers[eventType]);
|
|
927
|
+
delete this.batchTimers[eventType];
|
|
928
|
+
}
|
|
929
|
+
const eventsToProcess = [...queue];
|
|
930
|
+
this.queues[eventType] = [];
|
|
931
|
+
let backendUrl;
|
|
932
|
+
let transformedPayload;
|
|
933
|
+
if (eventType === 'console_error' || eventType === 'unhandled_error') {
|
|
934
|
+
backendUrl = this.config.consoleErrorBackendUrl;
|
|
935
|
+
transformedPayload = eventsToProcess.map((event) => ({
|
|
936
|
+
message: event.data.message,
|
|
937
|
+
stack_trace: event.data.stack_trace,
|
|
938
|
+
log_level: 'error',
|
|
939
|
+
url: event.url,
|
|
940
|
+
session_id: event.session_id,
|
|
941
|
+
source: 'mobile',
|
|
942
|
+
console_metadata: event.data.metadata || {},
|
|
943
|
+
}));
|
|
944
|
+
}
|
|
945
|
+
else if (eventType === 'page_view') {
|
|
946
|
+
backendUrl = this.config.pageViewBackendUrl;
|
|
947
|
+
transformedPayload = eventsToProcess.map((event) => ({
|
|
948
|
+
session_id: event.session_id,
|
|
949
|
+
url: event.data.screen,
|
|
950
|
+
referrer: event.data.referrer,
|
|
951
|
+
user_agent: event.user_agent,
|
|
952
|
+
source: 'mobile',
|
|
953
|
+
page_metadata: {
|
|
954
|
+
duration: event.data.duration,
|
|
955
|
+
screen: event.data.screen,
|
|
956
|
+
...event.data.metadata,
|
|
957
|
+
},
|
|
958
|
+
timestamp: new Date(event.timestamp).toISOString(),
|
|
959
|
+
}));
|
|
960
|
+
}
|
|
961
|
+
else if (eventType === 'api_call') {
|
|
962
|
+
backendUrl = this.config.apiResponseBackendUrl;
|
|
963
|
+
transformedPayload = eventsToProcess
|
|
964
|
+
.map((event) => {
|
|
965
|
+
let responseBody = event.data.response_data;
|
|
966
|
+
if (typeof responseBody === 'string') {
|
|
967
|
+
try {
|
|
968
|
+
responseBody = JSON.parse(responseBody);
|
|
969
|
+
}
|
|
970
|
+
catch (e) {
|
|
971
|
+
responseBody = { raw_text: responseBody };
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
else if (responseBody === null || typeof responseBody === 'undefined') {
|
|
975
|
+
responseBody = {};
|
|
976
|
+
}
|
|
977
|
+
return {
|
|
978
|
+
url: event.data.url,
|
|
979
|
+
method: event.data.method,
|
|
980
|
+
status_code: event.data.status_code,
|
|
981
|
+
response_body: responseBody,
|
|
982
|
+
session_id: event.session_id,
|
|
983
|
+
source: 'mobile',
|
|
984
|
+
api_metadata: {
|
|
985
|
+
duration: event.data.duration,
|
|
986
|
+
error: event.data.error || null,
|
|
987
|
+
},
|
|
988
|
+
};
|
|
989
|
+
})
|
|
990
|
+
.filter((item) => item !== null && item !== undefined);
|
|
991
|
+
}
|
|
992
|
+
else {
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
const filteredPayload = transformedPayload.filter((item) => item !== null && item !== undefined);
|
|
996
|
+
if (backendUrl && filteredPayload.length > 0) {
|
|
997
|
+
this.sendToBackend(backendUrl, filteredPayload, useBeacon);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Send data to backend
|
|
1002
|
+
*/
|
|
1003
|
+
async sendToBackend(url, payload, useBeacon = false, retryCount = 0) {
|
|
1004
|
+
if (!this.sessionId) {
|
|
1005
|
+
this.pendingEvents.push({ url, payload, useBeacon });
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
if (!navigator.onLine) {
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
const data = JSON.stringify(payload);
|
|
1012
|
+
const headers = {
|
|
1013
|
+
'Content-Type': 'application/json',
|
|
1014
|
+
};
|
|
1015
|
+
try {
|
|
1016
|
+
const response = await fetch(url, {
|
|
1017
|
+
method: 'POST',
|
|
1018
|
+
headers,
|
|
1019
|
+
body: data,
|
|
1020
|
+
keepalive: useBeacon,
|
|
1021
|
+
});
|
|
1022
|
+
if (!response.ok && retryCount < this.config.retryAttempts) {
|
|
1023
|
+
setTimeout(() => {
|
|
1024
|
+
this.sendToBackend(url, payload, false, retryCount + 1);
|
|
1025
|
+
}, this.config.retryDelay);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
catch (error) {
|
|
1029
|
+
// Silently fail
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Flush pending events
|
|
1034
|
+
*/
|
|
1035
|
+
flushPendingEvents() {
|
|
1036
|
+
if (this.pendingEvents.length > 0) {
|
|
1037
|
+
const pending = [...this.pendingEvents];
|
|
1038
|
+
this.pendingEvents = [];
|
|
1039
|
+
pending.forEach(({ url, payload, useBeacon }) => {
|
|
1040
|
+
this.sendToBackend(url, payload, useBeacon);
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Setup native error capture for Android/iOS crashes
|
|
1046
|
+
*/
|
|
1047
|
+
setupNativeErrorCapture() {
|
|
1048
|
+
try {
|
|
1049
|
+
// Only run on native platforms and if we have a session ID
|
|
1050
|
+
if (Capacitor.isNativePlatform() && this.sessionId) {
|
|
1051
|
+
CuoralPlugin.setupNativeErrorCapture({
|
|
1052
|
+
backendUrl: this.config.consoleErrorBackendUrl,
|
|
1053
|
+
sessionId: this.sessionId,
|
|
1054
|
+
}).catch(() => {
|
|
1055
|
+
// Silently fail - native error capture optional
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
catch (error) {
|
|
1060
|
+
// Silently fail if plugin is not available
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
|
|
540
1065
|
/**
|
|
541
1066
|
* Main Cuoral class - simple API for users
|
|
542
1067
|
*/
|
|
543
1068
|
class Cuoral {
|
|
544
1069
|
constructor(options) {
|
|
1070
|
+
// Check if running on a mobile platform
|
|
1071
|
+
if (!Capacitor.isNativePlatform()) {
|
|
1072
|
+
throw new Error('Cuoral Ionic library only works on native mobile platforms (iOS/Android). Web is not supported.');
|
|
1073
|
+
}
|
|
545
1074
|
this.options = {
|
|
546
1075
|
showFloatingButton: true,
|
|
547
1076
|
useModal: true,
|
|
@@ -577,12 +1106,103 @@ class Cuoral {
|
|
|
577
1106
|
/**
|
|
578
1107
|
* Initialize Cuoral
|
|
579
1108
|
*/
|
|
580
|
-
initialize() {
|
|
1109
|
+
async initialize() {
|
|
581
1110
|
this.bridge.initialize();
|
|
582
1111
|
// Initialize modal if enabled
|
|
583
1112
|
if (this.modal) {
|
|
584
1113
|
this.modal.initialize();
|
|
585
1114
|
}
|
|
1115
|
+
// Fetch session configuration and initialize intelligence if enabled by backend
|
|
1116
|
+
await this.initializeIntelligence();
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1119
|
+
* Initialize intelligence based on backend configuration
|
|
1120
|
+
*/
|
|
1121
|
+
async initializeIntelligence() {
|
|
1122
|
+
try {
|
|
1123
|
+
let sessionId = localStorage.getItem('__x_loadID');
|
|
1124
|
+
// If no session exists, initiate a new one
|
|
1125
|
+
if (!sessionId) {
|
|
1126
|
+
sessionId = await this.initiateSession();
|
|
1127
|
+
if (!sessionId) {
|
|
1128
|
+
console.warn('[Cuoral] Failed to create session, intelligence not initialized');
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
// Fetch session configuration
|
|
1133
|
+
const config = await this.fetchSessionConfiguration(sessionId);
|
|
1134
|
+
// If session was invalid/expired, try to initiate a new one
|
|
1135
|
+
if (!config && !localStorage.getItem('__x_loadID')) {
|
|
1136
|
+
sessionId = await this.initiateSession();
|
|
1137
|
+
if (sessionId) {
|
|
1138
|
+
const newConfig = await this.fetchSessionConfiguration(sessionId);
|
|
1139
|
+
if (newConfig && newConfig.customer_intelligence === true) {
|
|
1140
|
+
this.intelligence = new CuoralIntelligence(sessionId);
|
|
1141
|
+
this.intelligence.init();
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
// Only initialize intelligence if customer_intelligence is enabled in backend
|
|
1147
|
+
if (config && config.customer_intelligence === true) {
|
|
1148
|
+
this.intelligence = new CuoralIntelligence(sessionId);
|
|
1149
|
+
this.intelligence.init();
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
catch (error) {
|
|
1153
|
+
console.warn('[Cuoral] Failed to initialize intelligence:', error);
|
|
1154
|
+
// Silently fail - don't break the app if intelligence fails to initialize
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Fetch session configuration from backend
|
|
1159
|
+
*/
|
|
1160
|
+
async fetchSessionConfiguration(sessionId) {
|
|
1161
|
+
try {
|
|
1162
|
+
const response = await fetch('https://api.cuoral.com/conversation/session/get', {
|
|
1163
|
+
method: 'POST',
|
|
1164
|
+
headers: {
|
|
1165
|
+
'Content-Type': 'application/json',
|
|
1166
|
+
'x-org-id': this.options.publicKey,
|
|
1167
|
+
},
|
|
1168
|
+
body: JSON.stringify({ session_id: sessionId }),
|
|
1169
|
+
});
|
|
1170
|
+
if (response.status === 404 || response.status === 400) {
|
|
1171
|
+
// Session not found, clear and return null
|
|
1172
|
+
localStorage.removeItem('__x_loadID');
|
|
1173
|
+
return null;
|
|
1174
|
+
}
|
|
1175
|
+
if (!response.ok) {
|
|
1176
|
+
return null;
|
|
1177
|
+
}
|
|
1178
|
+
const data = await response.json();
|
|
1179
|
+
// If session is expired, clear it
|
|
1180
|
+
if (data.is_expired) {
|
|
1181
|
+
localStorage.removeItem('__x_loadID');
|
|
1182
|
+
return null;
|
|
1183
|
+
}
|
|
1184
|
+
return data.configuration || null;
|
|
1185
|
+
}
|
|
1186
|
+
catch (error) {
|
|
1187
|
+
console.warn('[Cuoral] Failed to fetch session configuration:', error);
|
|
1188
|
+
return null;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
/**
|
|
1192
|
+
* Track a page/screen view
|
|
1193
|
+
*/
|
|
1194
|
+
trackPageView(screen, metadata) {
|
|
1195
|
+
if (this.intelligence) {
|
|
1196
|
+
this.intelligence.trackPageView(screen, metadata);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
/**
|
|
1200
|
+
* Track an error manually
|
|
1201
|
+
*/
|
|
1202
|
+
trackError(message, stackTrace, metadata) {
|
|
1203
|
+
if (this.intelligence) {
|
|
1204
|
+
this.intelligence.trackError(message, stackTrace, metadata);
|
|
1205
|
+
}
|
|
586
1206
|
}
|
|
587
1207
|
/**
|
|
588
1208
|
* Open the widget modal
|
|
@@ -633,6 +1253,77 @@ class Cuoral {
|
|
|
633
1253
|
if (this.modal) {
|
|
634
1254
|
this.modal.destroy();
|
|
635
1255
|
}
|
|
1256
|
+
// Destroy intelligence if exists
|
|
1257
|
+
if (this.intelligence) {
|
|
1258
|
+
this.intelligence.destroy();
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Get or create session ID
|
|
1263
|
+
*/
|
|
1264
|
+
getOrCreateSessionId() {
|
|
1265
|
+
const storageKey = '__x_loadID';
|
|
1266
|
+
let sessionId = localStorage.getItem(storageKey);
|
|
1267
|
+
if (!sessionId) {
|
|
1268
|
+
// Session will be created by initiate-session API call
|
|
1269
|
+
// For now, return a temporary ID that will be replaced
|
|
1270
|
+
sessionId = `temp-${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
|
|
1271
|
+
}
|
|
1272
|
+
return sessionId;
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
1275
|
+
* Initiate a new session with backend (like inline.js does)
|
|
1276
|
+
*/
|
|
1277
|
+
async initiateSession() {
|
|
1278
|
+
try {
|
|
1279
|
+
const response = await fetch('https://api.cuoral.com/conversation/initiate-session', {
|
|
1280
|
+
method: 'POST',
|
|
1281
|
+
headers: {
|
|
1282
|
+
'Content-Type': 'application/json',
|
|
1283
|
+
'x-org-id': this.options.publicKey,
|
|
1284
|
+
},
|
|
1285
|
+
body: JSON.stringify({ public_key: this.options.publicKey }),
|
|
1286
|
+
});
|
|
1287
|
+
if (!response.ok) {
|
|
1288
|
+
return null;
|
|
1289
|
+
}
|
|
1290
|
+
const data = await response.json();
|
|
1291
|
+
if (data.status && data.session_id) {
|
|
1292
|
+
localStorage.setItem('__x_loadID', data.session_id);
|
|
1293
|
+
// Set profile if email, firstName, lastName are provided
|
|
1294
|
+
if (this.options.email && this.options.firstName && this.options.lastName) {
|
|
1295
|
+
await this.setProfile(data.session_id);
|
|
1296
|
+
}
|
|
1297
|
+
return data.session_id;
|
|
1298
|
+
}
|
|
1299
|
+
return null;
|
|
1300
|
+
}
|
|
1301
|
+
catch (error) {
|
|
1302
|
+
console.warn('[Cuoral] Failed to initiate session:', error);
|
|
1303
|
+
return null;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
1307
|
+
* Set user profile for the session
|
|
1308
|
+
*/
|
|
1309
|
+
async setProfile(sessionId) {
|
|
1310
|
+
try {
|
|
1311
|
+
await fetch('https://api.cuoral.com/conversation/set-profile', {
|
|
1312
|
+
method: 'POST',
|
|
1313
|
+
headers: {
|
|
1314
|
+
'Content-Type': 'application/json',
|
|
1315
|
+
'x-org-id': this.options.publicKey,
|
|
1316
|
+
},
|
|
1317
|
+
body: JSON.stringify({
|
|
1318
|
+
session_id: sessionId,
|
|
1319
|
+
email: this.options.email,
|
|
1320
|
+
name: `${this.options.firstName} ${this.options.lastName}`,
|
|
1321
|
+
}),
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
catch (error) {
|
|
1325
|
+
console.warn('[Cuoral] Failed to set profile:', error);
|
|
1326
|
+
}
|
|
636
1327
|
}
|
|
637
1328
|
/**
|
|
638
1329
|
* Setup automatic message handlers
|
|
@@ -703,117 +1394,5 @@ class Cuoral {
|
|
|
703
1394
|
Cuoral.PRODUCTION_WIDGET_URL = 'https://js.cuoral.com/mobile.html';
|
|
704
1395
|
Cuoral.DEV_WIDGET_URL = 'assets/mobile.html';
|
|
705
1396
|
|
|
706
|
-
|
|
707
|
-
* Web implementation (for testing in browser)
|
|
708
|
-
*/
|
|
709
|
-
class CuoralPluginWeb extends WebPlugin {
|
|
710
|
-
constructor() {
|
|
711
|
-
super(...arguments);
|
|
712
|
-
this.isRecording = false;
|
|
713
|
-
}
|
|
714
|
-
async startRecording(options) {
|
|
715
|
-
console.log('[Cuoral Web] Start recording', options);
|
|
716
|
-
// Check if Screen Capture API is available
|
|
717
|
-
if (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia) {
|
|
718
|
-
throw new Error('Screen recording is not supported in this browser');
|
|
719
|
-
}
|
|
720
|
-
try {
|
|
721
|
-
// Request screen capture
|
|
722
|
-
const stream = await navigator.mediaDevices.getDisplayMedia({
|
|
723
|
-
video: true,
|
|
724
|
-
audio: options?.includeAudio || false,
|
|
725
|
-
});
|
|
726
|
-
this.isRecording = true;
|
|
727
|
-
this.recordingStartTime = Date.now();
|
|
728
|
-
// Store stream for later stopping
|
|
729
|
-
window.__cuoralRecordingStream = stream;
|
|
730
|
-
return { success: true };
|
|
731
|
-
}
|
|
732
|
-
catch (error) {
|
|
733
|
-
console.error('[Cuoral Web] Failed to start recording:', error);
|
|
734
|
-
return { success: false };
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
async stopRecording() {
|
|
738
|
-
console.log('[Cuoral Web] Stop recording');
|
|
739
|
-
if (!this.isRecording) {
|
|
740
|
-
return { success: false };
|
|
741
|
-
}
|
|
742
|
-
const stream = window.__cuoralRecordingStream;
|
|
743
|
-
if (stream) {
|
|
744
|
-
stream.getTracks().forEach(track => track.stop());
|
|
745
|
-
delete window.__cuoralRecordingStream;
|
|
746
|
-
}
|
|
747
|
-
const duration = this.recordingStartTime
|
|
748
|
-
? Math.floor((Date.now() - this.recordingStartTime) / 1000)
|
|
749
|
-
: 0;
|
|
750
|
-
this.isRecording = false;
|
|
751
|
-
this.recordingStartTime = undefined;
|
|
752
|
-
return {
|
|
753
|
-
success: true,
|
|
754
|
-
duration,
|
|
755
|
-
filePath: 'web-recording-not-saved',
|
|
756
|
-
};
|
|
757
|
-
}
|
|
758
|
-
async getRecordingState() {
|
|
759
|
-
return {
|
|
760
|
-
isRecording: this.isRecording,
|
|
761
|
-
duration: this.recordingStartTime
|
|
762
|
-
? Math.floor((Date.now() - this.recordingStartTime) / 1000)
|
|
763
|
-
: undefined,
|
|
764
|
-
};
|
|
765
|
-
}
|
|
766
|
-
async takeScreenshot(options) {
|
|
767
|
-
console.log('[Cuoral Web] Take screenshot', options);
|
|
768
|
-
try {
|
|
769
|
-
// Use Screen Capture API
|
|
770
|
-
const stream = await navigator.mediaDevices.getDisplayMedia({
|
|
771
|
-
video: { width: 1920, height: 1080 },
|
|
772
|
-
});
|
|
773
|
-
// Capture frame from video stream
|
|
774
|
-
const video = document.createElement('video');
|
|
775
|
-
video.srcObject = stream;
|
|
776
|
-
video.play();
|
|
777
|
-
await new Promise(resolve => {
|
|
778
|
-
video.onloadedmetadata = resolve;
|
|
779
|
-
});
|
|
780
|
-
const canvas = document.createElement('canvas');
|
|
781
|
-
canvas.width = video.videoWidth;
|
|
782
|
-
canvas.height = video.videoHeight;
|
|
783
|
-
const ctx = canvas.getContext('2d');
|
|
784
|
-
ctx?.drawImage(video, 0, 0);
|
|
785
|
-
// Stop stream
|
|
786
|
-
stream.getTracks().forEach(track => track.stop());
|
|
787
|
-
// Convert to base64
|
|
788
|
-
const format = options?.format || 'png';
|
|
789
|
-
const quality = options?.quality || 0.92;
|
|
790
|
-
const base64 = canvas.toDataURL(`image/${format}`, quality);
|
|
791
|
-
return {
|
|
792
|
-
base64: base64.split(',')[1],
|
|
793
|
-
mimeType: `image/${format}`,
|
|
794
|
-
width: canvas.width,
|
|
795
|
-
height: canvas.height,
|
|
796
|
-
};
|
|
797
|
-
}
|
|
798
|
-
catch (error) {
|
|
799
|
-
console.error('[Cuoral Web] Failed to take screenshot:', error);
|
|
800
|
-
throw error;
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
async isRecordingSupported() {
|
|
804
|
-
const supported = !!(navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia);
|
|
805
|
-
return { supported };
|
|
806
|
-
}
|
|
807
|
-
async requestPermissions() {
|
|
808
|
-
// Web doesn't require explicit permissions - they're requested on demand
|
|
809
|
-
return { granted: true };
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
var web = /*#__PURE__*/Object.freeze({
|
|
814
|
-
__proto__: null,
|
|
815
|
-
CuoralPluginWeb: CuoralPluginWeb
|
|
816
|
-
});
|
|
817
|
-
|
|
818
|
-
export { Cuoral, CuoralBridge, CuoralMessageType, CuoralModal, CuoralPlugin, CuoralRecorder };
|
|
1397
|
+
export { Cuoral, CuoralBridge, CuoralIntelligence, CuoralMessageType, CuoralModal, CuoralPlugin$1 as CuoralPlugin, CuoralRecorder };
|
|
819
1398
|
//# sourceMappingURL=index.esm.js.map
|