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/LICENSE +21 -0
- package/README.md +273 -0
- package/android/src/main/AndroidManifest.xml +10 -0
- package/android/src/main/java/com/cuoral/ionic/CuoralPlugin.java +291 -0
- package/assets/icon/favicon.png +0 -0
- package/dist/bridge.d.ts +51 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/bridge.js +159 -0
- package/dist/cuoral.d.ts +51 -0
- package/dist/cuoral.d.ts.map +1 -0
- package/dist/cuoral.js +173 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +819 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +825 -0
- package/dist/index.js.map +1 -0
- package/dist/modal.d.ts +46 -0
- package/dist/modal.d.ts.map +1 -0
- package/dist/modal.js +220 -0
- package/dist/plugin.d.ts +76 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +134 -0
- package/dist/types.d.ts +109 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +24 -0
- package/dist/web.d.ts +27 -0
- package/dist/web.d.ts.map +1 -0
- package/dist/web.js +107 -0
- package/ios/Plugin/CuoralPlugin.m +11 -0
- package/ios/Plugin/CuoralPlugin.swift +246 -0
- package/package.json +59 -0
- package/src/bridge.ts +195 -0
- package/src/cuoral.ts +208 -0
- package/src/index.ts +5 -0
- package/src/modal.ts +257 -0
- package/src/plugin.ts +190 -0
- package/src/types.ts +129 -0
- package/src/web.ts +135 -0
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
|