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/bridge.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { CuoralMessageType } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Bridge for bidirectional communication between WebView and Native code
|
|
4
|
+
*/
|
|
5
|
+
export class CuoralBridge {
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.messageHandlers = new Map();
|
|
8
|
+
this.isInitialized = false;
|
|
9
|
+
this.widgetIframe = null;
|
|
10
|
+
this.config = config;
|
|
11
|
+
this.setupMessageListener();
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Initialize the bridge
|
|
15
|
+
*/
|
|
16
|
+
initialize() {
|
|
17
|
+
if (this.isInitialized) {
|
|
18
|
+
this.log('Bridge already initialized');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
this.isInitialized = true;
|
|
22
|
+
this.log('Bridge initialized');
|
|
23
|
+
// Notify widget that bridge is ready
|
|
24
|
+
this.sendToWidget({
|
|
25
|
+
type: CuoralMessageType.WIDGET_READY,
|
|
26
|
+
timestamp: Date.now(),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Send message to native code
|
|
31
|
+
*/
|
|
32
|
+
sendToNative(message) {
|
|
33
|
+
if (!message.timestamp) {
|
|
34
|
+
message.timestamp = Date.now();
|
|
35
|
+
}
|
|
36
|
+
this.log('Sending to native:', message);
|
|
37
|
+
// For Capacitor - use window.postMessage
|
|
38
|
+
if (window.webkit?.messageHandlers?.cuoral) {
|
|
39
|
+
// iOS WKWebView
|
|
40
|
+
window.webkit.messageHandlers.cuoral.postMessage(message);
|
|
41
|
+
}
|
|
42
|
+
else if (window.CuoralAndroid) {
|
|
43
|
+
// Android JavascriptInterface
|
|
44
|
+
window.CuoralAndroid.postMessage(JSON.stringify(message));
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// Fallback - use postMessage for any other WebView
|
|
48
|
+
window.postMessage(message, '*');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Send message to widget iframe
|
|
53
|
+
*/
|
|
54
|
+
sendToWidget(message) {
|
|
55
|
+
if (!message.timestamp) {
|
|
56
|
+
message.timestamp = Date.now();
|
|
57
|
+
}
|
|
58
|
+
this.log('Sending to widget:', message);
|
|
59
|
+
// Find the iframe element
|
|
60
|
+
const iframe = document.getElementById('cuoral-widget-iframe');
|
|
61
|
+
if (iframe?.contentWindow) {
|
|
62
|
+
try {
|
|
63
|
+
// Use postMessage for cross-origin communication
|
|
64
|
+
iframe.contentWindow.postMessage(message, '*');
|
|
65
|
+
this.log('Message sent via postMessage');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
this.log('Error using postMessage:', error);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
this.log('Iframe not found or not ready');
|
|
74
|
+
}
|
|
75
|
+
// Fallback: Use localStorage for same-origin
|
|
76
|
+
try {
|
|
77
|
+
const storageKey = `cuoral_message_${Date.now()}`;
|
|
78
|
+
localStorage.setItem(storageKey, JSON.stringify(message));
|
|
79
|
+
// Clean up after 5 seconds
|
|
80
|
+
setTimeout(() => {
|
|
81
|
+
localStorage.removeItem(storageKey);
|
|
82
|
+
}, 5000);
|
|
83
|
+
// Trigger a storage event by updating a counter
|
|
84
|
+
const counter = parseInt(localStorage.getItem('cuoral_message_counter') || '0');
|
|
85
|
+
localStorage.setItem('cuoral_message_counter', (counter + 1).toString());
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
this.log('Error using localStorage:', error);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Register message handler
|
|
93
|
+
*/
|
|
94
|
+
on(type, handler) {
|
|
95
|
+
if (!this.messageHandlers.has(type)) {
|
|
96
|
+
this.messageHandlers.set(type, []);
|
|
97
|
+
}
|
|
98
|
+
const handlers = this.messageHandlers.get(type);
|
|
99
|
+
handlers.push(handler);
|
|
100
|
+
this.log(`Registered handler for ${type}`);
|
|
101
|
+
// Return unsubscribe function
|
|
102
|
+
return () => {
|
|
103
|
+
const index = handlers.indexOf(handler);
|
|
104
|
+
if (index > -1) {
|
|
105
|
+
handlers.splice(index, 1);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Setup window message listener
|
|
111
|
+
*/
|
|
112
|
+
setupMessageListener() {
|
|
113
|
+
window.addEventListener('message', (event) => {
|
|
114
|
+
// Only process messages FROM the iframe
|
|
115
|
+
const widgetFrame = document.querySelector('iframe[src*="mobile.html"]');
|
|
116
|
+
if (widgetFrame && event.source === widgetFrame.contentWindow) {
|
|
117
|
+
this.widgetIframe = widgetFrame;
|
|
118
|
+
}
|
|
119
|
+
else if (widgetFrame) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Validate message structure
|
|
123
|
+
if (!event.data || !event.data.type) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const message = event.data;
|
|
127
|
+
// Check if it's a Cuoral message
|
|
128
|
+
if (!Object.values(CuoralMessageType).includes(message.type)) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
this.log('Cuoral message received:', message);
|
|
132
|
+
// Call custom handler if provided
|
|
133
|
+
if (this.config.onMessage) {
|
|
134
|
+
this.config.onMessage(message);
|
|
135
|
+
}
|
|
136
|
+
// Call registered handlers
|
|
137
|
+
const handlers = this.messageHandlers.get(message.type);
|
|
138
|
+
if (handlers) {
|
|
139
|
+
handlers.forEach(handler => handler(message.payload));
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Debug logging
|
|
145
|
+
*/
|
|
146
|
+
log(...args) {
|
|
147
|
+
if (this.config.debug) {
|
|
148
|
+
console.log('[CuoralBridge]', ...args);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Destroy the bridge
|
|
153
|
+
*/
|
|
154
|
+
destroy() {
|
|
155
|
+
this.messageHandlers.clear();
|
|
156
|
+
this.isInitialized = false;
|
|
157
|
+
this.log('Bridge destroyed');
|
|
158
|
+
}
|
|
159
|
+
}
|
package/dist/cuoral.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export interface CuoralOptions {
|
|
2
|
+
publicKey: string;
|
|
3
|
+
email?: string;
|
|
4
|
+
firstName?: string;
|
|
5
|
+
lastName?: string;
|
|
6
|
+
debug?: boolean;
|
|
7
|
+
widgetBaseUrl?: string;
|
|
8
|
+
showFloatingButton?: boolean;
|
|
9
|
+
useModal?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Main Cuoral class - simple API for users
|
|
13
|
+
*/
|
|
14
|
+
export declare class Cuoral {
|
|
15
|
+
private bridge;
|
|
16
|
+
private recorder;
|
|
17
|
+
private modal?;
|
|
18
|
+
private options;
|
|
19
|
+
private static readonly PRODUCTION_WIDGET_URL;
|
|
20
|
+
private static readonly DEV_WIDGET_URL;
|
|
21
|
+
constructor(options: CuoralOptions);
|
|
22
|
+
/**
|
|
23
|
+
* Initialize Cuoral
|
|
24
|
+
*/
|
|
25
|
+
initialize(): void;
|
|
26
|
+
/**
|
|
27
|
+
* Open the widget modal
|
|
28
|
+
*/
|
|
29
|
+
openModal(): void;
|
|
30
|
+
/**
|
|
31
|
+
* Close the widget modal
|
|
32
|
+
*/
|
|
33
|
+
closeModal(): void;
|
|
34
|
+
/**
|
|
35
|
+
* Check if modal is open
|
|
36
|
+
*/
|
|
37
|
+
isModalOpen(): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Get the widget URL for iframe embedding
|
|
40
|
+
*/
|
|
41
|
+
getWidgetUrl(): string;
|
|
42
|
+
/**
|
|
43
|
+
* Clean up resources
|
|
44
|
+
*/
|
|
45
|
+
destroy(): void;
|
|
46
|
+
/**
|
|
47
|
+
* Setup automatic message handlers
|
|
48
|
+
*/
|
|
49
|
+
private setupMessageHandlers;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=cuoral.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cuoral.d.ts","sourceRoot":"","sources":["../src/cuoral.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,qBAAa,MAAM;IACjB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,KAAK,CAAC,CAAc;IAC5B,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAuC;IACpF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAwB;gBAElD,OAAO,EAAE,aAAa;IAsClC;;OAEG;IACI,UAAU,IAAI,IAAI;IASzB;;OAEG;IACI,SAAS,IAAI,IAAI;IAMxB;;OAEG;IACI,UAAU,IAAI,IAAI;IAMzB;;OAEG;IACI,WAAW,IAAI,OAAO;IAI7B;;OAEG;IACI,YAAY,IAAI,MAAM;IAe7B;;OAEG;IACI,OAAO,IAAI,IAAI;IAStB;;OAEG;IACH,OAAO,CAAC,oBAAoB;CAuE7B"}
|
package/dist/cuoral.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { CuoralBridge } from './bridge';
|
|
2
|
+
import { CuoralRecorder } from './plugin';
|
|
3
|
+
import { CuoralMessageType } from './types';
|
|
4
|
+
import { CuoralModal } from './modal';
|
|
5
|
+
import { Capacitor } from '@capacitor/core';
|
|
6
|
+
/**
|
|
7
|
+
* Main Cuoral class - simple API for users
|
|
8
|
+
*/
|
|
9
|
+
export class Cuoral {
|
|
10
|
+
constructor(options) {
|
|
11
|
+
this.options = {
|
|
12
|
+
showFloatingButton: true,
|
|
13
|
+
useModal: true,
|
|
14
|
+
...options
|
|
15
|
+
};
|
|
16
|
+
// Determine widget base URL
|
|
17
|
+
const baseUrl = options.widgetBaseUrl || Cuoral.PRODUCTION_WIDGET_URL;
|
|
18
|
+
const params = new URLSearchParams({
|
|
19
|
+
auto_start: 'true',
|
|
20
|
+
key: options.publicKey,
|
|
21
|
+
_t: Date.now().toString(),
|
|
22
|
+
});
|
|
23
|
+
if (options.email)
|
|
24
|
+
params.set('email', options.email);
|
|
25
|
+
if (options.firstName)
|
|
26
|
+
params.set('first_name', options.firstName);
|
|
27
|
+
if (options.lastName)
|
|
28
|
+
params.set('last_name', options.lastName);
|
|
29
|
+
const widgetUrl = `${baseUrl}?${params.toString()}`;
|
|
30
|
+
// Initialize modal if enabled
|
|
31
|
+
if (this.options.useModal) {
|
|
32
|
+
this.modal = new CuoralModal(widgetUrl, this.options.showFloatingButton);
|
|
33
|
+
}
|
|
34
|
+
// Initialize bridge and recorder
|
|
35
|
+
this.bridge = new CuoralBridge({
|
|
36
|
+
widgetUrl,
|
|
37
|
+
debug: options.debug || false
|
|
38
|
+
});
|
|
39
|
+
this.recorder = new CuoralRecorder();
|
|
40
|
+
// Setup automatic message handlers
|
|
41
|
+
this.setupMessageHandlers();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Initialize Cuoral
|
|
45
|
+
*/
|
|
46
|
+
initialize() {
|
|
47
|
+
this.bridge.initialize();
|
|
48
|
+
// Initialize modal if enabled
|
|
49
|
+
if (this.modal) {
|
|
50
|
+
this.modal.initialize();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Open the widget modal
|
|
55
|
+
*/
|
|
56
|
+
openModal() {
|
|
57
|
+
if (this.modal) {
|
|
58
|
+
this.modal.open();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Close the widget modal
|
|
63
|
+
*/
|
|
64
|
+
closeModal() {
|
|
65
|
+
if (this.modal) {
|
|
66
|
+
this.modal.close();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Check if modal is open
|
|
71
|
+
*/
|
|
72
|
+
isModalOpen() {
|
|
73
|
+
return this.modal?.isModalOpen() || false;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Get the widget URL for iframe embedding
|
|
77
|
+
*/
|
|
78
|
+
getWidgetUrl() {
|
|
79
|
+
const baseUrl = this.options.widgetBaseUrl || Cuoral.PRODUCTION_WIDGET_URL;
|
|
80
|
+
const params = new URLSearchParams({
|
|
81
|
+
auto_start: 'true',
|
|
82
|
+
key: this.options.publicKey,
|
|
83
|
+
_t: Date.now().toString(),
|
|
84
|
+
});
|
|
85
|
+
if (this.options.email)
|
|
86
|
+
params.set('email', this.options.email);
|
|
87
|
+
if (this.options.firstName)
|
|
88
|
+
params.set('first_name', this.options.firstName);
|
|
89
|
+
if (this.options.lastName)
|
|
90
|
+
params.set('last_name', this.options.lastName);
|
|
91
|
+
return `${baseUrl}?${params.toString()}`;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Clean up resources
|
|
95
|
+
*/
|
|
96
|
+
destroy() {
|
|
97
|
+
this.bridge.destroy();
|
|
98
|
+
// Destroy modal if exists
|
|
99
|
+
if (this.modal) {
|
|
100
|
+
this.modal.destroy();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Setup automatic message handlers
|
|
105
|
+
*/
|
|
106
|
+
setupMessageHandlers() {
|
|
107
|
+
// Handle start recording requests from widget
|
|
108
|
+
this.bridge.on(CuoralMessageType.START_RECORDING, async () => {
|
|
109
|
+
const success = await this.recorder.startRecording();
|
|
110
|
+
if (!success) {
|
|
111
|
+
// Error already handled by recorder
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
// Handle stop recording requests from widget
|
|
115
|
+
this.bridge.on(CuoralMessageType.STOP_RECORDING, async () => {
|
|
116
|
+
const result = await this.recorder.stopRecording();
|
|
117
|
+
if (result) {
|
|
118
|
+
// Convert file path to web-accessible URL
|
|
119
|
+
const capacitorUrl = result.filePath ? Capacitor.convertFileSrc(result.filePath) : '';
|
|
120
|
+
try {
|
|
121
|
+
// Fetch the video blob from the capacitor URL
|
|
122
|
+
const response = await fetch(capacitorUrl);
|
|
123
|
+
const videoBlob = await response.blob();
|
|
124
|
+
// Convert blob to base64 data URL so it can be serialized and transferred cross-origin
|
|
125
|
+
const reader = new FileReader();
|
|
126
|
+
reader.onloadend = () => {
|
|
127
|
+
const base64data = reader.result;
|
|
128
|
+
// Send the base64 data via bridge to CDN widget
|
|
129
|
+
this.bridge.sendToWidget({
|
|
130
|
+
type: CuoralMessageType.RECORDING_STOPPED,
|
|
131
|
+
payload: {
|
|
132
|
+
videoData: base64data,
|
|
133
|
+
videoType: 'video/mp4',
|
|
134
|
+
originalPath: result.filePath,
|
|
135
|
+
duration: result.duration,
|
|
136
|
+
timestamp: Date.now()
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
reader.onerror = (error) => {
|
|
141
|
+
console.error('[Cuoral] Error reading blob:', error);
|
|
142
|
+
this.bridge.sendToWidget({
|
|
143
|
+
type: CuoralMessageType.RECORDING_STOPPED,
|
|
144
|
+
payload: {
|
|
145
|
+
error: 'Failed to process recording',
|
|
146
|
+
duration: result.duration,
|
|
147
|
+
timestamp: Date.now()
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
// Start reading the blob as data URL
|
|
152
|
+
reader.readAsDataURL(videoBlob);
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
console.error('[Cuoral] Error processing video file:', error);
|
|
156
|
+
this.bridge.sendToWidget({
|
|
157
|
+
type: CuoralMessageType.RECORDING_STOPPED,
|
|
158
|
+
payload: {
|
|
159
|
+
error: 'Failed to process recording',
|
|
160
|
+
duration: result.duration,
|
|
161
|
+
timestamp: Date.now()
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
console.error('[Cuoral] No result from stopRecording');
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
Cuoral.PRODUCTION_WIDGET_URL = 'https://js.cuoral.com/mobile.html';
|
|
173
|
+
Cuoral.DEV_WIDGET_URL = 'assets/mobile.html';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC"}
|