cuoral-ionic 0.0.1

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/dist/index.js ADDED
@@ -0,0 +1,825 @@
1
+ 'use strict';
2
+
3
+ var core = require('@capacitor/core');
4
+
5
+ /**
6
+ * Message types for communication between WebView and Native code
7
+ */
8
+ exports.CuoralMessageType = void 0;
9
+ (function (CuoralMessageType) {
10
+ // Screen Recording
11
+ CuoralMessageType["START_RECORDING"] = "CUORAL_START_RECORDING";
12
+ CuoralMessageType["STOP_RECORDING"] = "CUORAL_STOP_RECORDING";
13
+ CuoralMessageType["RECORDING_STARTED"] = "CUORAL_RECORDING_STARTED";
14
+ CuoralMessageType["RECORDING_STOPPED"] = "CUORAL_RECORDING_STOPPED";
15
+ CuoralMessageType["RECORDING_ERROR"] = "CUORAL_RECORDING_ERROR";
16
+ // Screenshot
17
+ CuoralMessageType["TAKE_SCREENSHOT"] = "CUORAL_TAKE_SCREENSHOT";
18
+ CuoralMessageType["SCREENSHOT_TAKEN"] = "CUORAL_SCREENSHOT_TAKEN";
19
+ CuoralMessageType["SCREENSHOT_ERROR"] = "CUORAL_SCREENSHOT_ERROR";
20
+ // Widget Communication
21
+ CuoralMessageType["WIDGET_READY"] = "CUORAL_WIDGET_READY";
22
+ CuoralMessageType["WIDGET_CLOSED"] = "CUORAL_WIDGET_CLOSED";
23
+ // File Upload
24
+ CuoralMessageType["UPLOAD_FILE"] = "CUORAL_UPLOAD_FILE";
25
+ CuoralMessageType["UPLOAD_PROGRESS"] = "CUORAL_UPLOAD_PROGRESS";
26
+ CuoralMessageType["UPLOAD_COMPLETE"] = "CUORAL_UPLOAD_COMPLETE";
27
+ CuoralMessageType["UPLOAD_ERROR"] = "CUORAL_UPLOAD_ERROR";
28
+ })(exports.CuoralMessageType || (exports.CuoralMessageType = {}));
29
+
30
+ /**
31
+ * Register the Capacitor plugin
32
+ */
33
+ const CuoralPlugin = core.registerPlugin('CuoralPlugin', {
34
+ web: () => Promise.resolve().then(function () { return web; }).then(m => new m.CuoralPluginWeb()),
35
+ });
36
+ /**
37
+ * High-level API for easier integration
38
+ */
39
+ class CuoralRecorder {
40
+ constructor() {
41
+ this.isRecording = false;
42
+ }
43
+ /**
44
+ * Start recording with automatic permission handling
45
+ */
46
+ async startRecording(options) {
47
+ try {
48
+ // Check if already recording
49
+ if (this.isRecording) {
50
+ console.warn('[Cuoral] Already recording');
51
+ return false;
52
+ }
53
+ // Check support
54
+ const { supported } = await CuoralPlugin.isRecordingSupported();
55
+ if (!supported) {
56
+ throw new Error('Screen recording is not supported on this device');
57
+ }
58
+ // Request permissions
59
+ const { granted } = await CuoralPlugin.requestPermissions();
60
+ if (!granted) {
61
+ throw new Error('Recording permissions not granted');
62
+ }
63
+ // Start recording
64
+ const result = await CuoralPlugin.startRecording(options);
65
+ if (result.success) {
66
+ this.isRecording = true;
67
+ this.recordingStartTime = Date.now();
68
+ // Post message to widget
69
+ this.postMessage({
70
+ type: exports.CuoralMessageType.RECORDING_STARTED,
71
+ payload: { timestamp: this.recordingStartTime },
72
+ });
73
+ }
74
+ return result.success;
75
+ }
76
+ catch (error) {
77
+ console.error('[Cuoral] Failed to start recording:', error);
78
+ this.postMessage({
79
+ type: exports.CuoralMessageType.RECORDING_ERROR,
80
+ payload: { error: error.message },
81
+ });
82
+ return false;
83
+ }
84
+ }
85
+ /**
86
+ * Stop recording
87
+ */
88
+ async stopRecording() {
89
+ try {
90
+ if (!this.isRecording) {
91
+ console.warn('[Cuoral] Not recording');
92
+ return null;
93
+ }
94
+ const result = await CuoralPlugin.stopRecording();
95
+ if (result.success) {
96
+ this.isRecording = false;
97
+ const duration = this.recordingStartTime
98
+ ? Math.floor((Date.now() - this.recordingStartTime) / 1000)
99
+ : 0;
100
+ // Post message to widget
101
+ this.postMessage({
102
+ type: exports.CuoralMessageType.RECORDING_STOPPED,
103
+ payload: {
104
+ filePath: result.filePath,
105
+ duration: result.duration || duration,
106
+ },
107
+ });
108
+ return {
109
+ filePath: result.filePath,
110
+ duration: result.duration || duration,
111
+ };
112
+ }
113
+ return null;
114
+ }
115
+ catch (error) {
116
+ console.error('[Cuoral] Failed to stop recording:', error);
117
+ this.postMessage({
118
+ type: exports.CuoralMessageType.RECORDING_ERROR,
119
+ payload: { error: error.message },
120
+ });
121
+ return null;
122
+ }
123
+ }
124
+ /**
125
+ * Take screenshot
126
+ */
127
+ async takeScreenshot(options) {
128
+ try {
129
+ const screenshot = await CuoralPlugin.takeScreenshot(options);
130
+ // Post message to widget
131
+ this.postMessage({
132
+ type: exports.CuoralMessageType.SCREENSHOT_TAKEN,
133
+ payload: screenshot,
134
+ });
135
+ return screenshot;
136
+ }
137
+ catch (error) {
138
+ console.error('[Cuoral] Failed to take screenshot:', error);
139
+ this.postMessage({
140
+ type: exports.CuoralMessageType.SCREENSHOT_ERROR,
141
+ payload: { error: error.message },
142
+ });
143
+ return null;
144
+ }
145
+ }
146
+ /**
147
+ * Get current recording state
148
+ */
149
+ async getState() {
150
+ return await CuoralPlugin.getRecordingState();
151
+ }
152
+ /**
153
+ * Post message to WebView
154
+ */
155
+ postMessage(message) {
156
+ if (typeof window !== 'undefined' && window.postMessage) {
157
+ window.postMessage(message, '*');
158
+ }
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Bridge for bidirectional communication between WebView and Native code
164
+ */
165
+ class CuoralBridge {
166
+ constructor(config) {
167
+ this.messageHandlers = new Map();
168
+ this.isInitialized = false;
169
+ this.widgetIframe = null;
170
+ this.config = config;
171
+ this.setupMessageListener();
172
+ }
173
+ /**
174
+ * Initialize the bridge
175
+ */
176
+ initialize() {
177
+ if (this.isInitialized) {
178
+ this.log('Bridge already initialized');
179
+ return;
180
+ }
181
+ this.isInitialized = true;
182
+ this.log('Bridge initialized');
183
+ // Notify widget that bridge is ready
184
+ this.sendToWidget({
185
+ type: exports.CuoralMessageType.WIDGET_READY,
186
+ timestamp: Date.now(),
187
+ });
188
+ }
189
+ /**
190
+ * Send message to native code
191
+ */
192
+ sendToNative(message) {
193
+ if (!message.timestamp) {
194
+ message.timestamp = Date.now();
195
+ }
196
+ this.log('Sending to native:', message);
197
+ // For Capacitor - use window.postMessage
198
+ if (window.webkit?.messageHandlers?.cuoral) {
199
+ // iOS WKWebView
200
+ window.webkit.messageHandlers.cuoral.postMessage(message);
201
+ }
202
+ else if (window.CuoralAndroid) {
203
+ // Android JavascriptInterface
204
+ window.CuoralAndroid.postMessage(JSON.stringify(message));
205
+ }
206
+ else {
207
+ // Fallback - use postMessage for any other WebView
208
+ window.postMessage(message, '*');
209
+ }
210
+ }
211
+ /**
212
+ * Send message to widget iframe
213
+ */
214
+ sendToWidget(message) {
215
+ if (!message.timestamp) {
216
+ message.timestamp = Date.now();
217
+ }
218
+ this.log('Sending to widget:', message);
219
+ // Find the iframe element
220
+ const iframe = document.getElementById('cuoral-widget-iframe');
221
+ if (iframe?.contentWindow) {
222
+ try {
223
+ // Use postMessage for cross-origin communication
224
+ iframe.contentWindow.postMessage(message, '*');
225
+ this.log('Message sent via postMessage');
226
+ return;
227
+ }
228
+ catch (error) {
229
+ this.log('Error using postMessage:', error);
230
+ }
231
+ }
232
+ else {
233
+ this.log('Iframe not found or not ready');
234
+ }
235
+ // Fallback: Use localStorage for same-origin
236
+ try {
237
+ const storageKey = `cuoral_message_${Date.now()}`;
238
+ localStorage.setItem(storageKey, JSON.stringify(message));
239
+ // Clean up after 5 seconds
240
+ setTimeout(() => {
241
+ localStorage.removeItem(storageKey);
242
+ }, 5000);
243
+ // Trigger a storage event by updating a counter
244
+ const counter = parseInt(localStorage.getItem('cuoral_message_counter') || '0');
245
+ localStorage.setItem('cuoral_message_counter', (counter + 1).toString());
246
+ }
247
+ catch (error) {
248
+ this.log('Error using localStorage:', error);
249
+ }
250
+ }
251
+ /**
252
+ * Register message handler
253
+ */
254
+ on(type, handler) {
255
+ if (!this.messageHandlers.has(type)) {
256
+ this.messageHandlers.set(type, []);
257
+ }
258
+ const handlers = this.messageHandlers.get(type);
259
+ handlers.push(handler);
260
+ this.log(`Registered handler for ${type}`);
261
+ // Return unsubscribe function
262
+ return () => {
263
+ const index = handlers.indexOf(handler);
264
+ if (index > -1) {
265
+ handlers.splice(index, 1);
266
+ }
267
+ };
268
+ }
269
+ /**
270
+ * Setup window message listener
271
+ */
272
+ setupMessageListener() {
273
+ window.addEventListener('message', (event) => {
274
+ // Only process messages FROM the iframe
275
+ const widgetFrame = document.querySelector('iframe[src*="mobile.html"]');
276
+ if (widgetFrame && event.source === widgetFrame.contentWindow) {
277
+ this.widgetIframe = widgetFrame;
278
+ }
279
+ else if (widgetFrame) {
280
+ return;
281
+ }
282
+ // Validate message structure
283
+ if (!event.data || !event.data.type) {
284
+ return;
285
+ }
286
+ const message = event.data;
287
+ // Check if it's a Cuoral message
288
+ if (!Object.values(exports.CuoralMessageType).includes(message.type)) {
289
+ return;
290
+ }
291
+ this.log('Cuoral message received:', message);
292
+ // Call custom handler if provided
293
+ if (this.config.onMessage) {
294
+ this.config.onMessage(message);
295
+ }
296
+ // Call registered handlers
297
+ const handlers = this.messageHandlers.get(message.type);
298
+ if (handlers) {
299
+ handlers.forEach(handler => handler(message.payload));
300
+ }
301
+ });
302
+ }
303
+ /**
304
+ * Debug logging
305
+ */
306
+ log(...args) {
307
+ if (this.config.debug) {
308
+ console.log('[CuoralBridge]', ...args);
309
+ }
310
+ }
311
+ /**
312
+ * Destroy the bridge
313
+ */
314
+ destroy() {
315
+ this.messageHandlers.clear();
316
+ this.isInitialized = false;
317
+ this.log('Bridge destroyed');
318
+ }
319
+ }
320
+
321
+ /**
322
+ * Cuoral Modal Manager
323
+ * Handles full-screen modal display with floating button
324
+ */
325
+ class CuoralModal {
326
+ constructor(widgetUrl, showFloatingButton = true) {
327
+ this.isOpen = false;
328
+ this.widgetUrl = widgetUrl;
329
+ this.showFloatingButton = showFloatingButton;
330
+ }
331
+ /**
332
+ * Initialize the modal and floating button
333
+ */
334
+ initialize() {
335
+ if (this.showFloatingButton) {
336
+ this.createFloatingButton();
337
+ }
338
+ this.createModal();
339
+ }
340
+ /**
341
+ * Create floating chat button
342
+ */
343
+ createFloatingButton() {
344
+ this.floatingButton = document.createElement('div');
345
+ this.floatingButton.id = 'cuoral-floating-button';
346
+ this.floatingButton.innerHTML = `
347
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
348
+ <path d="M20 2H4C2.9 2 2 2.9 2 4V22L6 18H20C21.1 18 22 17.1 22 16V4C22 2.9 21.1 2 20 2ZM20 16H6L4 18V4H20V16Z" fill="white"/>
349
+ </svg>
350
+ `;
351
+ // Styles
352
+ Object.assign(this.floatingButton.style, {
353
+ position: 'fixed',
354
+ bottom: '20px',
355
+ right: '20px',
356
+ width: '56px',
357
+ height: '56px',
358
+ borderRadius: '50%',
359
+ backgroundColor: '#007AFF',
360
+ display: 'flex',
361
+ alignItems: 'center',
362
+ justifyContent: 'center',
363
+ cursor: 'pointer',
364
+ boxShadow: '0 4px 12px rgba(0, 122, 255, 0.4)',
365
+ zIndex: '999999',
366
+ transition: 'transform 0.2s, box-shadow 0.2s'
367
+ });
368
+ // Hover effect
369
+ this.floatingButton.addEventListener('mouseenter', () => {
370
+ if (this.floatingButton) {
371
+ this.floatingButton.style.transform = 'scale(1.1)';
372
+ this.floatingButton.style.boxShadow = '0 6px 16px rgba(0, 122, 255, 0.5)';
373
+ }
374
+ });
375
+ this.floatingButton.addEventListener('mouseleave', () => {
376
+ if (this.floatingButton) {
377
+ this.floatingButton.style.transform = 'scale(1)';
378
+ this.floatingButton.style.boxShadow = '0 4px 12px rgba(0, 122, 255, 0.4)';
379
+ }
380
+ });
381
+ // Click to open modal
382
+ this.floatingButton.addEventListener('click', () => {
383
+ this.open();
384
+ });
385
+ document.body.appendChild(this.floatingButton);
386
+ }
387
+ /**
388
+ * Create modal structure
389
+ */
390
+ createModal() {
391
+ // Modal container
392
+ this.modalElement = document.createElement('div');
393
+ this.modalElement.id = 'cuoral-modal';
394
+ Object.assign(this.modalElement.style, {
395
+ position: 'fixed',
396
+ top: '0',
397
+ left: '0',
398
+ width: '100%',
399
+ height: '100%',
400
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
401
+ display: 'none',
402
+ alignItems: 'center',
403
+ justifyContent: 'center',
404
+ zIndex: '1000000',
405
+ opacity: '0',
406
+ transition: 'opacity 0.3s ease',
407
+ padding: '60px 0'
408
+ });
409
+ // Modal content (with margin)
410
+ const modalContent = document.createElement('div');
411
+ Object.assign(modalContent.style, {
412
+ width: '100%',
413
+ height: '100%',
414
+ maxWidth: '100%',
415
+ maxHeight: '100%',
416
+ position: 'relative',
417
+ backgroundColor: 'transparent',
418
+ display: 'flex',
419
+ flexDirection: 'column',
420
+ borderRadius: '12px',
421
+ overflow: 'hidden'
422
+ });
423
+ // Close button
424
+ const closeButton = document.createElement('div');
425
+ closeButton.innerHTML = `
426
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
427
+ <path d="M18 6L6 18M6 6L18 18" stroke="white" stroke-width="2" stroke-linecap="round"/>
428
+ </svg>
429
+ `;
430
+ Object.assign(closeButton.style, {
431
+ position: 'absolute',
432
+ top: '20px',
433
+ right: '20px',
434
+ width: '40px',
435
+ height: '40px',
436
+ borderRadius: '50%',
437
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
438
+ display: 'flex',
439
+ alignItems: 'center',
440
+ justifyContent: 'center',
441
+ cursor: 'pointer',
442
+ zIndex: '1000001',
443
+ transition: 'background-color 0.2s'
444
+ });
445
+ closeButton.addEventListener('mouseenter', () => {
446
+ closeButton.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
447
+ });
448
+ closeButton.addEventListener('mouseleave', () => {
449
+ closeButton.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
450
+ });
451
+ closeButton.addEventListener('click', () => {
452
+ this.close();
453
+ });
454
+ // Iframe
455
+ this.iframeElement = document.createElement('iframe');
456
+ this.iframeElement.id = 'cuoral-widget-iframe';
457
+ this.iframeElement.src = this.widgetUrl;
458
+ Object.assign(this.iframeElement.style, {
459
+ width: '100%',
460
+ height: '100%',
461
+ border: 'none',
462
+ backgroundColor: 'white'
463
+ });
464
+ modalContent.appendChild(closeButton);
465
+ modalContent.appendChild(this.iframeElement);
466
+ this.modalElement.appendChild(modalContent);
467
+ // Close on backdrop click
468
+ this.modalElement.addEventListener('click', (e) => {
469
+ if (e.target === this.modalElement) {
470
+ this.close();
471
+ }
472
+ });
473
+ document.body.appendChild(this.modalElement);
474
+ }
475
+ /**
476
+ * Open the modal
477
+ */
478
+ open() {
479
+ if (this.isOpen || !this.modalElement)
480
+ return;
481
+ this.isOpen = true;
482
+ this.modalElement.style.display = 'flex';
483
+ // Trigger animation
484
+ setTimeout(() => {
485
+ if (this.modalElement) {
486
+ this.modalElement.style.opacity = '1';
487
+ }
488
+ }, 10);
489
+ // Hide floating button when modal is open
490
+ if (this.floatingButton) {
491
+ this.floatingButton.style.display = 'none';
492
+ }
493
+ // Prevent body scroll
494
+ document.body.style.overflow = 'hidden';
495
+ }
496
+ /**
497
+ * Close the modal
498
+ */
499
+ close() {
500
+ if (!this.isOpen || !this.modalElement)
501
+ return;
502
+ this.isOpen = false;
503
+ this.modalElement.style.opacity = '0';
504
+ setTimeout(() => {
505
+ if (this.modalElement) {
506
+ this.modalElement.style.display = 'none';
507
+ }
508
+ // Show floating button again
509
+ if (this.floatingButton) {
510
+ this.floatingButton.style.display = 'flex';
511
+ }
512
+ // Restore body scroll
513
+ document.body.style.overflow = '';
514
+ }, 300);
515
+ }
516
+ /**
517
+ * Check if modal is open
518
+ */
519
+ isModalOpen() {
520
+ return this.isOpen;
521
+ }
522
+ /**
523
+ * Destroy modal and floating button
524
+ */
525
+ destroy() {
526
+ if (this.modalElement) {
527
+ this.modalElement.remove();
528
+ }
529
+ if (this.floatingButton) {
530
+ this.floatingButton.remove();
531
+ }
532
+ document.body.style.overflow = '';
533
+ }
534
+ /**
535
+ * Get iframe element for communication
536
+ */
537
+ getIframe() {
538
+ return this.iframeElement;
539
+ }
540
+ }
541
+
542
+ /**
543
+ * Main Cuoral class - simple API for users
544
+ */
545
+ class Cuoral {
546
+ constructor(options) {
547
+ this.options = {
548
+ showFloatingButton: true,
549
+ useModal: true,
550
+ ...options
551
+ };
552
+ // Determine widget base URL
553
+ const baseUrl = options.widgetBaseUrl || Cuoral.PRODUCTION_WIDGET_URL;
554
+ const params = new URLSearchParams({
555
+ auto_start: 'true',
556
+ key: options.publicKey,
557
+ _t: Date.now().toString(),
558
+ });
559
+ if (options.email)
560
+ params.set('email', options.email);
561
+ if (options.firstName)
562
+ params.set('first_name', options.firstName);
563
+ if (options.lastName)
564
+ params.set('last_name', options.lastName);
565
+ const widgetUrl = `${baseUrl}?${params.toString()}`;
566
+ // Initialize modal if enabled
567
+ if (this.options.useModal) {
568
+ this.modal = new CuoralModal(widgetUrl, this.options.showFloatingButton);
569
+ }
570
+ // Initialize bridge and recorder
571
+ this.bridge = new CuoralBridge({
572
+ widgetUrl,
573
+ debug: options.debug || false
574
+ });
575
+ this.recorder = new CuoralRecorder();
576
+ // Setup automatic message handlers
577
+ this.setupMessageHandlers();
578
+ }
579
+ /**
580
+ * Initialize Cuoral
581
+ */
582
+ initialize() {
583
+ this.bridge.initialize();
584
+ // Initialize modal if enabled
585
+ if (this.modal) {
586
+ this.modal.initialize();
587
+ }
588
+ }
589
+ /**
590
+ * Open the widget modal
591
+ */
592
+ openModal() {
593
+ if (this.modal) {
594
+ this.modal.open();
595
+ }
596
+ }
597
+ /**
598
+ * Close the widget modal
599
+ */
600
+ closeModal() {
601
+ if (this.modal) {
602
+ this.modal.close();
603
+ }
604
+ }
605
+ /**
606
+ * Check if modal is open
607
+ */
608
+ isModalOpen() {
609
+ return this.modal?.isModalOpen() || false;
610
+ }
611
+ /**
612
+ * Get the widget URL for iframe embedding
613
+ */
614
+ getWidgetUrl() {
615
+ const baseUrl = this.options.widgetBaseUrl || Cuoral.PRODUCTION_WIDGET_URL;
616
+ const params = new URLSearchParams({
617
+ auto_start: 'true',
618
+ key: this.options.publicKey,
619
+ _t: Date.now().toString(),
620
+ });
621
+ if (this.options.email)
622
+ params.set('email', this.options.email);
623
+ if (this.options.firstName)
624
+ params.set('first_name', this.options.firstName);
625
+ if (this.options.lastName)
626
+ params.set('last_name', this.options.lastName);
627
+ return `${baseUrl}?${params.toString()}`;
628
+ }
629
+ /**
630
+ * Clean up resources
631
+ */
632
+ destroy() {
633
+ this.bridge.destroy();
634
+ // Destroy modal if exists
635
+ if (this.modal) {
636
+ this.modal.destroy();
637
+ }
638
+ }
639
+ /**
640
+ * Setup automatic message handlers
641
+ */
642
+ setupMessageHandlers() {
643
+ // Handle start recording requests from widget
644
+ this.bridge.on(exports.CuoralMessageType.START_RECORDING, async () => {
645
+ await this.recorder.startRecording();
646
+ });
647
+ // Handle stop recording requests from widget
648
+ this.bridge.on(exports.CuoralMessageType.STOP_RECORDING, async () => {
649
+ const result = await this.recorder.stopRecording();
650
+ if (result) {
651
+ // Convert file path to web-accessible URL
652
+ const capacitorUrl = result.filePath ? core.Capacitor.convertFileSrc(result.filePath) : '';
653
+ try {
654
+ // Fetch the video blob from the capacitor URL
655
+ const response = await fetch(capacitorUrl);
656
+ const videoBlob = await response.blob();
657
+ // Convert blob to base64 data URL so it can be serialized and transferred cross-origin
658
+ const reader = new FileReader();
659
+ reader.onloadend = () => {
660
+ const base64data = reader.result;
661
+ // Send the base64 data via bridge to CDN widget
662
+ this.bridge.sendToWidget({
663
+ type: exports.CuoralMessageType.RECORDING_STOPPED,
664
+ payload: {
665
+ videoData: base64data,
666
+ videoType: 'video/mp4',
667
+ originalPath: result.filePath,
668
+ duration: result.duration,
669
+ timestamp: Date.now()
670
+ }
671
+ });
672
+ };
673
+ reader.onerror = (error) => {
674
+ console.error('[Cuoral] Error reading blob:', error);
675
+ this.bridge.sendToWidget({
676
+ type: exports.CuoralMessageType.RECORDING_STOPPED,
677
+ payload: {
678
+ error: 'Failed to process recording',
679
+ duration: result.duration,
680
+ timestamp: Date.now()
681
+ }
682
+ });
683
+ };
684
+ // Start reading the blob as data URL
685
+ reader.readAsDataURL(videoBlob);
686
+ }
687
+ catch (error) {
688
+ console.error('[Cuoral] Error processing video file:', error);
689
+ this.bridge.sendToWidget({
690
+ type: exports.CuoralMessageType.RECORDING_STOPPED,
691
+ payload: {
692
+ error: 'Failed to process recording',
693
+ duration: result.duration,
694
+ timestamp: Date.now()
695
+ }
696
+ });
697
+ }
698
+ }
699
+ else {
700
+ console.error('[Cuoral] No result from stopRecording');
701
+ }
702
+ });
703
+ }
704
+ }
705
+ Cuoral.PRODUCTION_WIDGET_URL = 'https://js.cuoral.com/mobile.html';
706
+ Cuoral.DEV_WIDGET_URL = 'assets/mobile.html';
707
+
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
+ exports.Cuoral = Cuoral;
821
+ exports.CuoralBridge = CuoralBridge;
822
+ exports.CuoralModal = CuoralModal;
823
+ exports.CuoralPlugin = CuoralPlugin;
824
+ exports.CuoralRecorder = CuoralRecorder;
825
+ //# sourceMappingURL=index.js.map