cuoral-ionic 0.0.6 → 0.0.7
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/previous-compilation-data.bin +0 -0
- package/android/build.gradle +1 -0
- package/android/src/main/java/com/cuoral/ionic/CuoralPlugin.java +205 -5
- package/dist/cuoral.d.ts +15 -1
- package/dist/cuoral.d.ts.map +1 -1
- package/dist/cuoral.js +113 -9
- package/dist/index.esm.js +159 -19
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +159 -19
- 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 +15 -2
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +23 -10
- 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/cuoral.ts +128 -11
- package/src/intelligence.ts +23 -0
- package/src/plugin.ts +39 -11
- package/src/types.ts +4 -0
package/dist/plugin.d.ts
CHANGED
|
@@ -12,10 +12,17 @@ export interface CuoralPluginInterface {
|
|
|
12
12
|
/**
|
|
13
13
|
* Stop screen recording
|
|
14
14
|
*/
|
|
15
|
-
stopRecording(
|
|
15
|
+
stopRecording(options?: {
|
|
16
|
+
autoUpload?: boolean;
|
|
17
|
+
sessionId?: string;
|
|
18
|
+
publicKey?: string;
|
|
19
|
+
customerId?: string;
|
|
20
|
+
}): Promise<{
|
|
16
21
|
success: boolean;
|
|
17
22
|
filePath?: string;
|
|
18
23
|
duration?: number;
|
|
24
|
+
uploaded?: boolean;
|
|
25
|
+
uploadError?: string;
|
|
19
26
|
}>;
|
|
20
27
|
/**
|
|
21
28
|
* Get recording state
|
|
@@ -56,9 +63,15 @@ export declare class CuoralRecorder {
|
|
|
56
63
|
/**
|
|
57
64
|
* Stop recording
|
|
58
65
|
*/
|
|
59
|
-
stopRecording(
|
|
66
|
+
stopRecording(options?: {
|
|
67
|
+
autoUpload?: boolean;
|
|
68
|
+
sessionId?: string;
|
|
69
|
+
publicKey?: string;
|
|
70
|
+
customerId?: string;
|
|
71
|
+
}): Promise<{
|
|
60
72
|
filePath?: string;
|
|
61
73
|
duration?: number;
|
|
74
|
+
uploaded?: boolean;
|
|
62
75
|
} | null>;
|
|
63
76
|
/**
|
|
64
77
|
* Take screenshot
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,cAAc,EACf,MAAM,SAAS,CAAC;AAEjB;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,cAAc,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAE1E;;OAEG;IACH,aAAa,
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,cAAc,EACf,MAAM,SAAS,CAAC;AAEjB;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,cAAc,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAE1E;;OAEG;IACH,aAAa,CAAC,OAAO,CAAC,EAAE;QACtB,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC;QACV,OAAO,EAAE,OAAO,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC,CAAC;IAEH;;OAEG;IACH,iBAAiB,IAAI,OAAO,CAAC,cAAc,CAAC,CAAC;IAE7C;;OAEG;IACH,cAAc,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAErE;;OAEG;IACH,oBAAoB,IAAI,OAAO,CAAC;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAExD;;OAEG;IACH,kBAAkB,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACrD;AAED;;GAEG;AACH,QAAA,MAAM,YAAY,uBAAwD,CAAC;AAE3E,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB;;GAEG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,kBAAkB,CAAC,CAAS;IAEpC;;OAEG;IACG,cAAc,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IAyDlE;;OAEG;IACG,aAAa,CAAC,OAAO,CAAC,EAAE;QAC5B,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IA6DhF;;OAEG;IACG,cAAc,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAqBjF;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,cAAc,CAAC;IAIzC;;OAEG;IACH,OAAO,CAAC,WAAW;CAKpB"}
|
package/dist/plugin.js
CHANGED
|
@@ -68,7 +68,7 @@ export class CuoralRecorder {
|
|
|
68
68
|
/**
|
|
69
69
|
* Stop recording
|
|
70
70
|
*/
|
|
71
|
-
async stopRecording() {
|
|
71
|
+
async stopRecording(options) {
|
|
72
72
|
try {
|
|
73
73
|
if (!this.isRecording) {
|
|
74
74
|
// Send error message to widget so it can exit "stopping" state
|
|
@@ -78,23 +78,36 @@ export class CuoralRecorder {
|
|
|
78
78
|
});
|
|
79
79
|
return null;
|
|
80
80
|
}
|
|
81
|
-
const result = await CuoralPlugin.stopRecording();
|
|
81
|
+
const result = await CuoralPlugin.stopRecording(options);
|
|
82
82
|
if (result.success) {
|
|
83
83
|
this.isRecording = false;
|
|
84
84
|
const duration = this.recordingStartTime
|
|
85
85
|
? Math.floor((Date.now() - this.recordingStartTime) / 1000)
|
|
86
86
|
: 0;
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
87
|
+
// If uploaded, notify widget differently
|
|
88
|
+
if (result.uploaded) {
|
|
89
|
+
this.postMessage({
|
|
90
|
+
type: CuoralMessageType.RECORDING_UPLOADED,
|
|
91
|
+
payload: {
|
|
92
|
+
duration: result.duration || duration,
|
|
93
|
+
uploaded: true
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Post message to widget (old behavior)
|
|
99
|
+
this.postMessage({
|
|
100
|
+
type: CuoralMessageType.RECORDING_STOPPED,
|
|
101
|
+
payload: {
|
|
102
|
+
filePath: result.filePath,
|
|
103
|
+
duration: result.duration || duration,
|
|
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
|
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
|
+
// Reduced quality settings to prevent huge file sizes
|
|
114
|
+
// 2.5 Mbps bitrate = ~18MB per minute (down from ~600MB per minute)
|
|
115
|
+
let compressionProperties: [String: Any] = [
|
|
116
|
+
AVVideoAverageBitRateKey: 2_500_000, // 2.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.7",
|
|
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/cuoral.ts
CHANGED
|
@@ -61,6 +61,12 @@ export class Cuoral {
|
|
|
61
61
|
_t: Date.now().toString(),
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
+
// Add session_id if available (for widget to use existing session)
|
|
65
|
+
const existingSessionId = localStorage.getItem('__x_loadID');
|
|
66
|
+
if (existingSessionId) {
|
|
67
|
+
params.set('cuoral_mobile_session_id', existingSessionId);
|
|
68
|
+
}
|
|
69
|
+
|
|
64
70
|
if (options.email) params.set('email', options.email);
|
|
65
71
|
if (options.firstName) params.set('first_name', options.firstName);
|
|
66
72
|
if (options.lastName) params.set('last_name', options.lastName);
|
|
@@ -88,6 +94,14 @@ export class Cuoral {
|
|
|
88
94
|
* Initialize Cuoral
|
|
89
95
|
*/
|
|
90
96
|
public async initialize(): Promise<void> {
|
|
97
|
+
// Fetch session configuration and initialize intelligence if enabled by backend
|
|
98
|
+
await this.initializeIntelligence();
|
|
99
|
+
|
|
100
|
+
console.log('[Cuoral] Initialize - Session ID:', localStorage.getItem('__x_loadID'));
|
|
101
|
+
|
|
102
|
+
// Setup localStorage listener to detect when widget changes session
|
|
103
|
+
this.setupStorageListener();
|
|
104
|
+
|
|
91
105
|
this.bridge.initialize();
|
|
92
106
|
|
|
93
107
|
// Recreate modal if it was destroyed (e.g., after clearSession)
|
|
@@ -96,13 +110,12 @@ export class Cuoral {
|
|
|
96
110
|
this.modal = new CuoralModal(widgetUrl, this.options.showFloatingButton);
|
|
97
111
|
}
|
|
98
112
|
|
|
99
|
-
//
|
|
113
|
+
// Update modal URL with session ID
|
|
100
114
|
if (this.modal) {
|
|
115
|
+
const widgetUrl = this.getWidgetUrl();
|
|
116
|
+
this.modal.updateWidgetUrl(widgetUrl);
|
|
101
117
|
this.modal.initialize();
|
|
102
118
|
}
|
|
103
|
-
|
|
104
|
-
// Fetch session configuration and initialize intelligence if enabled by backend
|
|
105
|
-
await this.initializeIntelligence();
|
|
106
119
|
}
|
|
107
120
|
|
|
108
121
|
/**
|
|
@@ -139,8 +152,11 @@ export class Cuoral {
|
|
|
139
152
|
|
|
140
153
|
// Only initialize intelligence if customer_intelligence is enabled in backend
|
|
141
154
|
if (config && config.customer_intelligence === true) {
|
|
155
|
+
console.log('[Cuoral] Initializing intelligence with session:', sessionId);
|
|
142
156
|
this.intelligence = new CuoralIntelligence(sessionId);
|
|
143
157
|
this.intelligence.init();
|
|
158
|
+
} else {
|
|
159
|
+
console.log('[Cuoral] Intelligence not enabled or no config for session:', sessionId);
|
|
144
160
|
}
|
|
145
161
|
} catch (error) {
|
|
146
162
|
console.warn('[Cuoral] Failed to initialize intelligence:', error);
|
|
@@ -205,6 +221,54 @@ export class Cuoral {
|
|
|
205
221
|
}
|
|
206
222
|
}
|
|
207
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Update user profile for the current session
|
|
226
|
+
* Call this after user logs in to update the intelligence session with their profile
|
|
227
|
+
* @param email - User's email address
|
|
228
|
+
* @param name - User's full name
|
|
229
|
+
*/
|
|
230
|
+
public async updateUserProfile(email: string, name: string): Promise<boolean> {
|
|
231
|
+
try {
|
|
232
|
+
const sessionId = localStorage.getItem('__x_loadID');
|
|
233
|
+
if (!sessionId) {
|
|
234
|
+
console.warn('[Cuoral] No session ID found, cannot update profile');
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
console.log('[Cuoral] Updating user profile for session:', sessionId);
|
|
239
|
+
|
|
240
|
+
const response = await fetch('https://api.cuoral.com/conversation/set-profile', {
|
|
241
|
+
method: 'POST',
|
|
242
|
+
headers: { 'Content-Type': 'application/json' },
|
|
243
|
+
body: JSON.stringify({
|
|
244
|
+
session_id: sessionId,
|
|
245
|
+
email: email,
|
|
246
|
+
name: name,
|
|
247
|
+
}),
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
if (!response.ok) {
|
|
251
|
+
console.error('[Cuoral] Failed to update profile:', response.statusText);
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
console.log('[Cuoral] ✓ User profile updated successfully for session:', sessionId);
|
|
256
|
+
|
|
257
|
+
// Store user info locally
|
|
258
|
+
this.options.email = email;
|
|
259
|
+
const nameParts = name.split(' ');
|
|
260
|
+
if (nameParts.length > 0) {
|
|
261
|
+
this.options.firstName = nameParts[0];
|
|
262
|
+
this.options.lastName = nameParts.slice(1).join(' ');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return true;
|
|
266
|
+
} catch (error) {
|
|
267
|
+
console.error('[Cuoral] Error updating user profile:', error);
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
208
272
|
/**
|
|
209
273
|
* Start native screen recording programmatically
|
|
210
274
|
* @returns Promise<boolean> - true if recording started successfully
|
|
@@ -220,11 +284,20 @@ export class Cuoral {
|
|
|
220
284
|
|
|
221
285
|
/**
|
|
222
286
|
* Stop native screen recording programmatically
|
|
223
|
-
*
|
|
287
|
+
* Recording will be automatically uploaded to the portal
|
|
288
|
+
* @returns Promise<{filePath?: string; duration?: number; uploaded?: boolean} | null> - Recording result or null if failed
|
|
224
289
|
*/
|
|
225
|
-
public async stopRecording(): Promise<{filePath?: string; duration?: number} | null> {
|
|
290
|
+
public async stopRecording(): Promise<{filePath?: string; duration?: number; uploaded?: boolean} | null> {
|
|
226
291
|
try {
|
|
227
|
-
|
|
292
|
+
const sessionId = localStorage.getItem('__x_loadID');
|
|
293
|
+
const customerId = localStorage.getItem('cuoralCustomerId');
|
|
294
|
+
|
|
295
|
+
return await this.recorder.stopRecording({
|
|
296
|
+
autoUpload: true,
|
|
297
|
+
sessionId: sessionId || undefined,
|
|
298
|
+
publicKey: this.options.publicKey,
|
|
299
|
+
customerId: customerId || undefined,
|
|
300
|
+
});
|
|
228
301
|
} catch (error) {
|
|
229
302
|
console.error('[Cuoral] Failed to stop recording:', error);
|
|
230
303
|
return null;
|
|
@@ -274,7 +347,7 @@ export class Cuoral {
|
|
|
274
347
|
// Add session_id if available
|
|
275
348
|
const sessionId = localStorage.getItem('__x_loadID');
|
|
276
349
|
if (sessionId) {
|
|
277
|
-
params.set('
|
|
350
|
+
params.set('cuoral_mobile_session_id', sessionId);
|
|
278
351
|
}
|
|
279
352
|
|
|
280
353
|
if (this.options.email) params.set('email', this.options.email);
|
|
@@ -333,6 +406,32 @@ export class Cuoral {
|
|
|
333
406
|
}
|
|
334
407
|
}
|
|
335
408
|
|
|
409
|
+
/**
|
|
410
|
+
* Setup localStorage listener for session changes
|
|
411
|
+
* Widget updates localStorage when creating new session, SDK detects and syncs
|
|
412
|
+
*/
|
|
413
|
+
private setupStorageListener(): void {
|
|
414
|
+
// Poll localStorage every 2 seconds to detect session changes
|
|
415
|
+
// (storage event doesn't fire for same-window changes)
|
|
416
|
+
let lastKnownSession = localStorage.getItem('__x_loadID');
|
|
417
|
+
|
|
418
|
+
setInterval(() => {
|
|
419
|
+
const currentSession = localStorage.getItem('__x_loadID');
|
|
420
|
+
if (currentSession && currentSession !== lastKnownSession) {
|
|
421
|
+
console.log('[Cuoral] 🔄 Session changed in localStorage');
|
|
422
|
+
console.log('[Cuoral] Old session:', lastKnownSession);
|
|
423
|
+
console.log('[Cuoral] New session:', currentSession);
|
|
424
|
+
|
|
425
|
+
lastKnownSession = currentSession;
|
|
426
|
+
|
|
427
|
+
// Update intelligence to use new session
|
|
428
|
+
if (this.intelligence) {
|
|
429
|
+
this.intelligence.updateSessionId(currentSession);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}, 2000);
|
|
433
|
+
}
|
|
434
|
+
|
|
336
435
|
/**
|
|
337
436
|
* Clean up resources
|
|
338
437
|
*/
|
|
@@ -439,10 +538,28 @@ export class Cuoral {
|
|
|
439
538
|
|
|
440
539
|
// Handle stop recording requests from widget
|
|
441
540
|
this.bridge.on(CuoralMessageType.STOP_RECORDING, async () => {
|
|
442
|
-
const
|
|
541
|
+
const sessionId = localStorage.getItem('__x_loadID');
|
|
542
|
+
const customerId = localStorage.getItem('cuoralCustomerId');
|
|
543
|
+
|
|
544
|
+
const result = await this.recorder.stopRecording({
|
|
545
|
+
autoUpload: true,
|
|
546
|
+
sessionId: sessionId || undefined,
|
|
547
|
+
publicKey: this.options.publicKey,
|
|
548
|
+
customerId: customerId || undefined,
|
|
549
|
+
});
|
|
443
550
|
|
|
444
|
-
if (result) {
|
|
445
|
-
//
|
|
551
|
+
if (result && result.uploaded) {
|
|
552
|
+
// Video was automatically uploaded, just notify widget
|
|
553
|
+
this.bridge.sendToWidget({
|
|
554
|
+
type: CuoralMessageType.RECORDING_UPLOADED,
|
|
555
|
+
payload: {
|
|
556
|
+
duration: result.duration,
|
|
557
|
+
uploaded: true,
|
|
558
|
+
timestamp: Date.now()
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
} else if (result) {
|
|
562
|
+
// Upload failed or was disabled, send video data to widget (old behavior)
|
|
446
563
|
const capacitorUrl = result.filePath ? Capacitor.convertFileSrc(result.filePath) : '';
|
|
447
564
|
|
|
448
565
|
try {
|