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