cuoral-ionic 0.0.6 → 0.0.8
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/android/build/.transforms/bb54161301273cf9b5b94a21c0fb3f23/transformed/classes/classes_dex/classes.dex +0 -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$3.dex +0 -0
- package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$4.dex +0 -0
- package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$5.dex +0 -0
- package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$6.dex +0 -0
- package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$InitiateCallback.dex +0 -0
- package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$UploadCallback.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/desugar_graph.bin +0 -0
- package/android/build/intermediates/compile_library_classes_jar/debug/classes.jar +0 -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$3.class +0 -0
- package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$4.class +0 -0
- package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$5.class +0 -0
- package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$6.class +0 -0
- package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$InitiateCallback.class +0 -0
- package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$UploadCallback.class +0 -0
- package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin.class +0 -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$3.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$4.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$5.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$6.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$InitiateCallback.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$UploadCallback.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_jar/debug/classes.jar +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/{CuoralPlugin$1.class.uniqueId1 → CuoralPlugin$1.class.uniqueId2} +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/{CuoralPlugin$2.class.uniqueId2 → CuoralPlugin$2.class.uniqueId5} +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$3.class.uniqueId3 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$4.class.uniqueId6 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$5.class.uniqueId4 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$6.class.uniqueId8 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$InitiateCallback.class.uniqueId0 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$UploadCallback.class.uniqueId7 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin.class.uniqueId1 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
- package/android/build.gradle +1 -0
- package/android/src/main/java/com/cuoral/ionic/CuoralPlugin.java +209 -6
- package/dist/bridge.d.ts.map +1 -1
- package/dist/cuoral.d.ts +15 -1
- package/dist/cuoral.d.ts.map +1 -1
- package/dist/cuoral.js +141 -12
- package/dist/index.esm.js +218 -46
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +218 -46
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +4 -0
- package/dist/intelligence.d.ts.map +1 -1
- package/dist/intelligence.js +19 -0
- package/dist/plugin.d.ts +16 -3
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +53 -36
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -0
- package/ios/Plugin/CuoralPlugin.swift +249 -13
- package/package.json +1 -1
- package/src/bridge.ts +1 -0
- package/src/cuoral.ts +158 -14
- package/src/intelligence.ts +23 -0
- package/src/plugin.ts +70 -37
- package/src/types.ts +4 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin.class.uniqueId0 +0 -0
package/dist/plugin.js
CHANGED
|
@@ -15,7 +15,7 @@ export class CuoralRecorder {
|
|
|
15
15
|
/**
|
|
16
16
|
* Start recording with automatic permission handling
|
|
17
17
|
*/
|
|
18
|
-
async startRecording(options) {
|
|
18
|
+
async startRecording(options, sendMessages = true) {
|
|
19
19
|
try {
|
|
20
20
|
// Check if already recording
|
|
21
21
|
if (this.isRecording) {
|
|
@@ -37,20 +37,24 @@ export class CuoralRecorder {
|
|
|
37
37
|
if (result.success) {
|
|
38
38
|
this.isRecording = true;
|
|
39
39
|
this.recordingStartTime = Date.now();
|
|
40
|
-
// Post message to widget
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
// Post message to widget only if enabled (disabled when called from widget handler)
|
|
41
|
+
if (sendMessages) {
|
|
42
|
+
this.postMessage({
|
|
43
|
+
type: CuoralMessageType.RECORDING_STARTED,
|
|
44
|
+
payload: { timestamp: this.recordingStartTime },
|
|
45
|
+
});
|
|
46
|
+
}
|
|
45
47
|
}
|
|
46
48
|
else {
|
|
47
49
|
// Recording failed - reset state and notify widget
|
|
48
50
|
this.isRecording = false;
|
|
49
51
|
this.recordingStartTime = undefined;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
if (sendMessages) {
|
|
53
|
+
this.postMessage({
|
|
54
|
+
type: CuoralMessageType.RECORDING_ERROR,
|
|
55
|
+
payload: { error: 'Failed to start recording' },
|
|
56
|
+
});
|
|
57
|
+
}
|
|
54
58
|
}
|
|
55
59
|
return result.success;
|
|
56
60
|
}
|
|
@@ -58,58 +62,71 @@ export class CuoralRecorder {
|
|
|
58
62
|
// Exception occurred - reset state and notify widget
|
|
59
63
|
this.isRecording = false;
|
|
60
64
|
this.recordingStartTime = undefined;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
if (sendMessages) {
|
|
66
|
+
this.postMessage({
|
|
67
|
+
type: CuoralMessageType.RECORDING_ERROR,
|
|
68
|
+
payload: { error: error.message },
|
|
69
|
+
});
|
|
70
|
+
}
|
|
65
71
|
return false;
|
|
66
72
|
}
|
|
67
73
|
}
|
|
68
74
|
/**
|
|
69
75
|
* Stop recording
|
|
70
76
|
*/
|
|
71
|
-
async stopRecording() {
|
|
77
|
+
async stopRecording(options, sendMessages = true) {
|
|
72
78
|
try {
|
|
73
79
|
if (!this.isRecording) {
|
|
74
80
|
// Send error message to widget so it can exit "stopping" state
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
if (sendMessages) {
|
|
82
|
+
this.postMessage({
|
|
83
|
+
type: CuoralMessageType.RECORDING_ERROR,
|
|
84
|
+
payload: { error: 'Not recording' },
|
|
85
|
+
});
|
|
86
|
+
}
|
|
79
87
|
return null;
|
|
80
88
|
}
|
|
81
|
-
const result = await CuoralPlugin.stopRecording();
|
|
89
|
+
const result = await CuoralPlugin.stopRecording(options);
|
|
82
90
|
if (result.success) {
|
|
83
91
|
this.isRecording = false;
|
|
84
92
|
const duration = this.recordingStartTime
|
|
85
93
|
? Math.floor((Date.now() - this.recordingStartTime) / 1000)
|
|
86
94
|
: 0;
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
// Send RECORDING_STOPPED message only if enabled
|
|
96
|
+
if (sendMessages) {
|
|
97
|
+
this.postMessage({
|
|
98
|
+
type: CuoralMessageType.RECORDING_STOPPED,
|
|
99
|
+
payload: {
|
|
100
|
+
filePath: result.filePath,
|
|
101
|
+
duration: result.duration || duration,
|
|
102
|
+
uploaded: result.uploaded,
|
|
103
|
+
uploadedToBackend: result.uploaded,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
95
107
|
return {
|
|
96
108
|
filePath: result.filePath,
|
|
97
109
|
duration: result.duration || duration,
|
|
110
|
+
uploaded: result.uploaded,
|
|
98
111
|
};
|
|
99
112
|
}
|
|
100
113
|
// If result.success is false, send error to widget
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
114
|
+
if (sendMessages) {
|
|
115
|
+
this.postMessage({
|
|
116
|
+
type: CuoralMessageType.RECORDING_ERROR,
|
|
117
|
+
payload: { error: 'Failed to stop recording' },
|
|
118
|
+
});
|
|
119
|
+
}
|
|
105
120
|
return null;
|
|
106
121
|
}
|
|
107
122
|
catch (error) {
|
|
108
123
|
console.error('[Cuoral] Failed to stop recording:', error);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
124
|
+
if (sendMessages) {
|
|
125
|
+
this.postMessage({
|
|
126
|
+
type: CuoralMessageType.RECORDING_ERROR,
|
|
127
|
+
payload: { error: error.message },
|
|
128
|
+
});
|
|
129
|
+
}
|
|
113
130
|
return null;
|
|
114
131
|
}
|
|
115
132
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -6,12 +6,16 @@ export declare enum CuoralMessageType {
|
|
|
6
6
|
STOP_RECORDING = "CUORAL_STOP_RECORDING",
|
|
7
7
|
RECORDING_STARTED = "CUORAL_RECORDING_STARTED",
|
|
8
8
|
RECORDING_STOPPED = "CUORAL_RECORDING_STOPPED",
|
|
9
|
+
RECORDING_UPLOADED = "CUORAL_RECORDING_UPLOADED",
|
|
9
10
|
RECORDING_ERROR = "CUORAL_RECORDING_ERROR",
|
|
10
11
|
TAKE_SCREENSHOT = "CUORAL_TAKE_SCREENSHOT",
|
|
11
12
|
SCREENSHOT_TAKEN = "CUORAL_SCREENSHOT_TAKEN",
|
|
12
13
|
SCREENSHOT_ERROR = "CUORAL_SCREENSHOT_ERROR",
|
|
13
14
|
WIDGET_READY = "CUORAL_WIDGET_READY",
|
|
14
15
|
WIDGET_CLOSED = "CUORAL_WIDGET_CLOSED",
|
|
16
|
+
SESSION_UPDATED = "CUORAL_SESSION_UPDATED",
|
|
17
|
+
REQUEST_SESSION_ID = "CUORAL_REQUEST_SESSION_ID",
|
|
18
|
+
SESSION_ID_RESPONSE = "CUORAL_SESSION_ID_RESPONSE",
|
|
15
19
|
UPLOAD_FILE = "CUORAL_UPLOAD_FILE",
|
|
16
20
|
UPLOAD_PROGRESS = "CUORAL_UPLOAD_PROGRESS",
|
|
17
21
|
UPLOAD_COMPLETE = "CUORAL_UPLOAD_COMPLETE",
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,oBAAY,iBAAiB;IAE3B,eAAe,2BAA2B;IAC1C,cAAc,0BAA0B;IACxC,iBAAiB,6BAA6B;IAC9C,iBAAiB,6BAA6B;IAC9C,eAAe,2BAA2B;IAG1C,eAAe,2BAA2B;IAC1C,gBAAgB,4BAA4B;IAC5C,gBAAgB,4BAA4B;IAG5C,YAAY,wBAAwB;IACpC,aAAa,yBAAyB;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,oBAAY,iBAAiB;IAE3B,eAAe,2BAA2B;IAC1C,cAAc,0BAA0B;IACxC,iBAAiB,6BAA6B;IAC9C,iBAAiB,6BAA6B;IAC9C,kBAAkB,8BAA8B;IAChD,eAAe,2BAA2B;IAG1C,eAAe,2BAA2B;IAC1C,gBAAgB,4BAA4B;IAC5C,gBAAgB,4BAA4B;IAG5C,YAAY,wBAAwB;IACpC,aAAa,yBAAyB;IACtC,eAAe,2BAA2B;IAC1C,kBAAkB,8BAA8B;IAChD,mBAAmB,+BAA+B;IAGlD,WAAW,uBAAuB;IAClC,eAAe,2BAA2B;IAC1C,eAAe,2BAA2B;IAC1C,YAAY,wBAAwB;CACrC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,iBAAiB,CAAC;IACxB,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAE7C;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;CACzB"}
|
package/dist/types.js
CHANGED
|
@@ -8,6 +8,7 @@ export var CuoralMessageType;
|
|
|
8
8
|
CuoralMessageType["STOP_RECORDING"] = "CUORAL_STOP_RECORDING";
|
|
9
9
|
CuoralMessageType["RECORDING_STARTED"] = "CUORAL_RECORDING_STARTED";
|
|
10
10
|
CuoralMessageType["RECORDING_STOPPED"] = "CUORAL_RECORDING_STOPPED";
|
|
11
|
+
CuoralMessageType["RECORDING_UPLOADED"] = "CUORAL_RECORDING_UPLOADED";
|
|
11
12
|
CuoralMessageType["RECORDING_ERROR"] = "CUORAL_RECORDING_ERROR";
|
|
12
13
|
// Screenshot
|
|
13
14
|
CuoralMessageType["TAKE_SCREENSHOT"] = "CUORAL_TAKE_SCREENSHOT";
|
|
@@ -16,6 +17,9 @@ export var CuoralMessageType;
|
|
|
16
17
|
// Widget Communication
|
|
17
18
|
CuoralMessageType["WIDGET_READY"] = "CUORAL_WIDGET_READY";
|
|
18
19
|
CuoralMessageType["WIDGET_CLOSED"] = "CUORAL_WIDGET_CLOSED";
|
|
20
|
+
CuoralMessageType["SESSION_UPDATED"] = "CUORAL_SESSION_UPDATED";
|
|
21
|
+
CuoralMessageType["REQUEST_SESSION_ID"] = "CUORAL_REQUEST_SESSION_ID";
|
|
22
|
+
CuoralMessageType["SESSION_ID_RESPONSE"] = "CUORAL_SESSION_ID_RESPONSE";
|
|
19
23
|
// File Upload
|
|
20
24
|
CuoralMessageType["UPLOAD_FILE"] = "CUORAL_UPLOAD_FILE";
|
|
21
25
|
CuoralMessageType["UPLOAD_PROGRESS"] = "CUORAL_UPLOAD_PROGRESS";
|
|
@@ -110,10 +110,19 @@ public class CuoralPlugin: CAPPlugin {
|
|
|
110
110
|
do {
|
|
111
111
|
assetWriter = try AVAssetWriter(url: outputURL, fileType: .mp4)
|
|
112
112
|
|
|
113
|
+
// Optimized quality settings for screen recordings
|
|
114
|
+
// 1.5 Mbps bitrate = ~11MB per minute (~54MB for 5 min)
|
|
115
|
+
let compressionProperties: [String: Any] = [
|
|
116
|
+
AVVideoAverageBitRateKey: 1_500_000, // 1.5 Mbps
|
|
117
|
+
AVVideoMaxKeyFrameIntervalKey: 30,
|
|
118
|
+
AVVideoProfileLevelKey: AVVideoProfileLevelH264BaselineAutoLevel
|
|
119
|
+
]
|
|
120
|
+
|
|
113
121
|
let videoSettings: [String: Any] = [
|
|
114
122
|
AVVideoCodecKey: AVVideoCodecType.h264,
|
|
115
123
|
AVVideoWidthKey: UIScreen.main.bounds.width * UIScreen.main.scale,
|
|
116
|
-
AVVideoHeightKey: UIScreen.main.bounds.height * UIScreen.main.scale
|
|
124
|
+
AVVideoHeightKey: UIScreen.main.bounds.height * UIScreen.main.scale,
|
|
125
|
+
AVVideoCompressionPropertiesKey: compressionProperties
|
|
117
126
|
]
|
|
118
127
|
|
|
119
128
|
videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoSettings)
|
|
@@ -184,6 +193,12 @@ public class CuoralPlugin: CAPPlugin {
|
|
|
184
193
|
|
|
185
194
|
isRecording = false
|
|
186
195
|
|
|
196
|
+
// Get upload parameters
|
|
197
|
+
let shouldUpload = call.getBool("autoUpload") ?? true
|
|
198
|
+
let sessionId = call.getString("sessionId") ?? ""
|
|
199
|
+
let publicKey = call.getString("publicKey") ?? ""
|
|
200
|
+
let customerId = call.getString("customerId") ?? ""
|
|
201
|
+
|
|
187
202
|
recorder.stopCapture { [weak self] error in
|
|
188
203
|
guard let self = self else { return }
|
|
189
204
|
|
|
@@ -208,20 +223,241 @@ public class CuoralPlugin: CAPPlugin {
|
|
|
208
223
|
let duration = self.recordingStartTime.map { Date().timeIntervalSince($0) } ?? 0
|
|
209
224
|
let filePath = self.videoOutputURL?.path ?? ""
|
|
210
225
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
226
|
+
// If autoUpload is enabled, upload the video
|
|
227
|
+
if shouldUpload && !sessionId.isEmpty && !publicKey.isEmpty {
|
|
228
|
+
self.uploadRecording(
|
|
229
|
+
filePath: filePath,
|
|
230
|
+
sessionId: sessionId,
|
|
231
|
+
publicKey: publicKey,
|
|
232
|
+
customerId: customerId,
|
|
233
|
+
duration: Int(duration)
|
|
234
|
+
) { success, error in
|
|
235
|
+
if success {
|
|
236
|
+
call.resolve([
|
|
237
|
+
"success": true,
|
|
238
|
+
"filePath": filePath,
|
|
239
|
+
"duration": Int(duration),
|
|
240
|
+
"uploaded": true
|
|
241
|
+
])
|
|
242
|
+
} else {
|
|
243
|
+
call.resolve([
|
|
244
|
+
"success": true,
|
|
245
|
+
"filePath": filePath,
|
|
246
|
+
"duration": Int(duration),
|
|
247
|
+
"uploaded": false,
|
|
248
|
+
"uploadError": error ?? "Unknown error"
|
|
249
|
+
])
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Clean up
|
|
253
|
+
self.assetWriter = nil
|
|
254
|
+
self.videoInput = nil
|
|
255
|
+
self.videoOutputURL = nil
|
|
256
|
+
self.recordingStartTime = nil
|
|
257
|
+
self.firstFrameTime = nil
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
// No upload, just return file path
|
|
261
|
+
call.resolve([
|
|
262
|
+
"success": true,
|
|
263
|
+
"filePath": filePath,
|
|
264
|
+
"duration": Int(duration),
|
|
265
|
+
"uploaded": false
|
|
266
|
+
])
|
|
267
|
+
|
|
268
|
+
// Clean up
|
|
269
|
+
self.assetWriter = nil
|
|
270
|
+
self.videoInput = nil
|
|
271
|
+
self.videoOutputURL = nil
|
|
272
|
+
self.recordingStartTime = nil
|
|
273
|
+
self.firstFrameTime = nil
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Upload recording to Cuoral backend
|
|
281
|
+
*/
|
|
282
|
+
private func uploadRecording(
|
|
283
|
+
filePath: String,
|
|
284
|
+
sessionId: String,
|
|
285
|
+
publicKey: String,
|
|
286
|
+
customerId: String,
|
|
287
|
+
duration: Int,
|
|
288
|
+
completion: @escaping (Bool, String?) -> Void
|
|
289
|
+
) {
|
|
290
|
+
guard let fileURL = URL(string: "file://\(filePath)"),
|
|
291
|
+
FileManager.default.fileExists(atPath: filePath) else {
|
|
292
|
+
completion(false, "File not found")
|
|
293
|
+
return
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Read video data
|
|
297
|
+
guard let videoData = try? Data(contentsOf: fileURL) else {
|
|
298
|
+
completion(false, "Failed to read video file")
|
|
299
|
+
return
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Step 1: Initiate recording to get record_id
|
|
303
|
+
initiateRecording(sessionId: sessionId, customerId: customerId, publicKey: publicKey) { recordId, error in
|
|
304
|
+
if let error = error {
|
|
305
|
+
completion(false, "Failed to initiate recording: \(error)")
|
|
306
|
+
return
|
|
223
307
|
}
|
|
308
|
+
|
|
309
|
+
guard let recordId = recordId else {
|
|
310
|
+
completion(false, "No record ID returned")
|
|
311
|
+
return
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Step 2: Upload video with the record_id
|
|
315
|
+
self.uploadVideo(
|
|
316
|
+
videoData: videoData,
|
|
317
|
+
recordId: recordId,
|
|
318
|
+
publicKey: publicKey,
|
|
319
|
+
customerId: customerId,
|
|
320
|
+
duration: duration,
|
|
321
|
+
completion: completion
|
|
322
|
+
)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Step 1: Initiate recording session
|
|
328
|
+
*/
|
|
329
|
+
private func initiateRecording(
|
|
330
|
+
sessionId: String,
|
|
331
|
+
customerId: String,
|
|
332
|
+
publicKey: String,
|
|
333
|
+
completion: @escaping (String?, String?) -> Void
|
|
334
|
+
) {
|
|
335
|
+
let url = URL(string: "https://api.cuoral.com/customer-intelligence/initiate/record")!
|
|
336
|
+
var request = URLRequest(url: url)
|
|
337
|
+
request.httpMethod = "POST"
|
|
338
|
+
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
339
|
+
request.setValue("*/*", forHTTPHeaderField: "Accept")
|
|
340
|
+
request.setValue(publicKey, forHTTPHeaderField: "x-org-id")
|
|
341
|
+
|
|
342
|
+
// API needs both session_id and customer_id (matching widget.js)
|
|
343
|
+
let body: [String: String] = [
|
|
344
|
+
"session_id": sessionId,
|
|
345
|
+
"customer_id": customerId
|
|
346
|
+
]
|
|
347
|
+
|
|
348
|
+
guard let jsonData = try? JSONSerialization.data(withJSONObject: body) else {
|
|
349
|
+
completion(nil, "Failed to serialize JSON")
|
|
350
|
+
return
|
|
224
351
|
}
|
|
352
|
+
|
|
353
|
+
request.httpBody = jsonData
|
|
354
|
+
request.timeoutInterval = 30.0
|
|
355
|
+
|
|
356
|
+
URLSession.shared.dataTask(with: request) { data, response, error in
|
|
357
|
+
if let error = error {
|
|
358
|
+
completion(nil, error.localizedDescription)
|
|
359
|
+
return
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
guard let httpResponse = response as? HTTPURLResponse else {
|
|
363
|
+
completion(nil, "Invalid response")
|
|
364
|
+
return
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// API returns JSON: { status: 'success', record_id: '...' }
|
|
368
|
+
guard httpResponse.statusCode == 200, let data = data else {
|
|
369
|
+
completion(nil, "Failed to initiate recording (status \(httpResponse.statusCode))")
|
|
370
|
+
return
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Parse JSON response
|
|
374
|
+
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
375
|
+
let status = json["status"] as? String,
|
|
376
|
+
status == "success",
|
|
377
|
+
let recordId = json["record_id"] as? String else {
|
|
378
|
+
completion(nil, "Invalid response format")
|
|
379
|
+
return
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
completion(recordId, nil)
|
|
383
|
+
}.resume()
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Step 2: Upload video to the initiated recording
|
|
388
|
+
*/
|
|
389
|
+
private func uploadVideo(
|
|
390
|
+
videoData: Data,
|
|
391
|
+
recordId: String,
|
|
392
|
+
publicKey: String,
|
|
393
|
+
customerId: String,
|
|
394
|
+
duration: Int,
|
|
395
|
+
completion: @escaping (Bool, String?) -> Void
|
|
396
|
+
) {
|
|
397
|
+
// Create multipart form data
|
|
398
|
+
let boundary = "Boundary-\(UUID().uuidString)"
|
|
399
|
+
var body = Data()
|
|
400
|
+
|
|
401
|
+
// Add form fields (API spec: only record_id is required)
|
|
402
|
+
let parameters: [String: String] = [
|
|
403
|
+
"record_id": recordId,
|
|
404
|
+
"console_error": "[]",
|
|
405
|
+
"api_response_log": "[]",
|
|
406
|
+
"page_view": "[]"
|
|
407
|
+
]
|
|
408
|
+
|
|
409
|
+
for (key, value) in parameters {
|
|
410
|
+
body.append("--\(boundary)\r\n".data(using: .utf8)!)
|
|
411
|
+
body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8)!)
|
|
412
|
+
body.append("\(value)\r\n".data(using: .utf8)!)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Add video file
|
|
416
|
+
body.append("--\(boundary)\r\n".data(using: .utf8)!)
|
|
417
|
+
body.append("Content-Disposition: form-data; name=\"record_media\"; filename=\"recording.mp4\"\r\n".data(using: .utf8)!)
|
|
418
|
+
body.append("Content-Type: video/mp4\r\n\r\n".data(using: .utf8)!)
|
|
419
|
+
body.append(videoData)
|
|
420
|
+
body.append("\r\n".data(using: .utf8)!)
|
|
421
|
+
body.append("--\(boundary)--\r\n".data(using: .utf8)!)
|
|
422
|
+
|
|
423
|
+
// Create request
|
|
424
|
+
let url = URL(string: "https://api.cuoral.com/customer-intelligence/update/record")!
|
|
425
|
+
var request = URLRequest(url: url)
|
|
426
|
+
request.httpMethod = "POST"
|
|
427
|
+
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
|
|
428
|
+
request.setValue("*/*", forHTTPHeaderField: "Accept")
|
|
429
|
+
// Note: widget.js doesn't send x-org-id for update endpoint
|
|
430
|
+
request.httpBody = body
|
|
431
|
+
request.timeoutInterval = 60.0 // 60 seconds for upload
|
|
432
|
+
|
|
433
|
+
// Send request
|
|
434
|
+
URLSession.shared.dataTask(with: request) { data, response, error in
|
|
435
|
+
if let error = error {
|
|
436
|
+
completion(false, error.localizedDescription)
|
|
437
|
+
return
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
guard let httpResponse = response as? HTTPURLResponse else {
|
|
441
|
+
completion(false, "Invalid response")
|
|
442
|
+
return
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// API returns JSON: { status: 'success' }
|
|
446
|
+
guard httpResponse.statusCode == 200, let data = data else {
|
|
447
|
+
completion(false, "Upload failed with status \(httpResponse.statusCode)")
|
|
448
|
+
return
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Parse JSON response
|
|
452
|
+
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
453
|
+
let status = json["status"] as? String,
|
|
454
|
+
status == "success" else {
|
|
455
|
+
completion(false, "Upload failed")
|
|
456
|
+
return
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
completion(true, nil)
|
|
460
|
+
}.resume()
|
|
225
461
|
}
|
|
226
462
|
|
|
227
463
|
@objc func getRecordingState(_ call: CAPPluginCall) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cuoral-ionic",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "Cuoral Ionic Framework Library - Proactive customer success platform with support ticketing, customer intelligence, screen recording, and engagement tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
package/src/bridge.ts
CHANGED
|
@@ -129,6 +129,7 @@ export class CuoralBridge {
|
|
|
129
129
|
window.addEventListener('message', (event: MessageEvent) => {
|
|
130
130
|
// Only process messages FROM the iframe
|
|
131
131
|
const widgetFrame = document.querySelector('iframe[src*="mobile.html"]') as HTMLIFrameElement;
|
|
132
|
+
|
|
132
133
|
if (widgetFrame && event.source === widgetFrame.contentWindow) {
|
|
133
134
|
this.widgetIframe = widgetFrame;
|
|
134
135
|
} else if (widgetFrame) {
|