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.
Files changed (68) hide show
  1. package/android/build/.transforms/bb54161301273cf9b5b94a21c0fb3f23/transformed/classes/classes_dex/classes.dex +0 -0
  2. package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$1.dex +0 -0
  3. package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$2.dex +0 -0
  4. package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$3.dex +0 -0
  5. package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$4.dex +0 -0
  6. package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$5.dex +0 -0
  7. package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$6.dex +0 -0
  8. package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$InitiateCallback.dex +0 -0
  9. package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin$UploadCallback.dex +0 -0
  10. package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/CuoralPlugin.dex +0 -0
  11. package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/desugar_graph.bin +0 -0
  12. package/android/build/intermediates/compile_library_classes_jar/debug/classes.jar +0 -0
  13. package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$1.class +0 -0
  14. package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$2.class +0 -0
  15. package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$3.class +0 -0
  16. package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$4.class +0 -0
  17. package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$5.class +0 -0
  18. package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$6.class +0 -0
  19. package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$InitiateCallback.class +0 -0
  20. package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin$UploadCallback.class +0 -0
  21. package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/CuoralPlugin.class +0 -0
  22. package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$1.class +0 -0
  23. package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$2.class +0 -0
  24. package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$3.class +0 -0
  25. package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$4.class +0 -0
  26. package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$5.class +0 -0
  27. package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$6.class +0 -0
  28. package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$InitiateCallback.class +0 -0
  29. package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin$UploadCallback.class +0 -0
  30. package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/CuoralPlugin.class +0 -0
  31. package/android/build/intermediates/runtime_library_classes_jar/debug/classes.jar +0 -0
  32. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/{CuoralPlugin$1.class.uniqueId1 → CuoralPlugin$1.class.uniqueId2} +0 -0
  33. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/{CuoralPlugin$2.class.uniqueId2 → CuoralPlugin$2.class.uniqueId5} +0 -0
  34. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$3.class.uniqueId3 +0 -0
  35. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$4.class.uniqueId6 +0 -0
  36. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$5.class.uniqueId4 +0 -0
  37. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$6.class.uniqueId8 +0 -0
  38. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$InitiateCallback.class.uniqueId0 +0 -0
  39. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$UploadCallback.class.uniqueId7 +0 -0
  40. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin.class.uniqueId1 +0 -0
  41. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  42. package/android/build.gradle +1 -0
  43. package/android/src/main/java/com/cuoral/ionic/CuoralPlugin.java +209 -6
  44. package/dist/bridge.d.ts.map +1 -1
  45. package/dist/cuoral.d.ts +15 -1
  46. package/dist/cuoral.d.ts.map +1 -1
  47. package/dist/cuoral.js +141 -12
  48. package/dist/index.esm.js +218 -46
  49. package/dist/index.esm.js.map +1 -1
  50. package/dist/index.js +218 -46
  51. package/dist/index.js.map +1 -1
  52. package/dist/intelligence.d.ts +4 -0
  53. package/dist/intelligence.d.ts.map +1 -1
  54. package/dist/intelligence.js +19 -0
  55. package/dist/plugin.d.ts +16 -3
  56. package/dist/plugin.d.ts.map +1 -1
  57. package/dist/plugin.js +53 -36
  58. package/dist/types.d.ts +4 -0
  59. package/dist/types.d.ts.map +1 -1
  60. package/dist/types.js +4 -0
  61. package/ios/Plugin/CuoralPlugin.swift +249 -13
  62. package/package.json +1 -1
  63. package/src/bridge.ts +1 -0
  64. package/src/cuoral.ts +158 -14
  65. package/src/intelligence.ts +23 -0
  66. package/src/plugin.ts +70 -37
  67. package/src/types.ts +4 -0
  68. 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
- this.postMessage({
42
- type: CuoralMessageType.RECORDING_STARTED,
43
- payload: { timestamp: this.recordingStartTime },
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
- this.postMessage({
51
- type: CuoralMessageType.RECORDING_ERROR,
52
- payload: { error: 'Failed to start recording' },
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
- this.postMessage({
62
- type: CuoralMessageType.RECORDING_ERROR,
63
- payload: { error: error.message },
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
- this.postMessage({
76
- type: CuoralMessageType.RECORDING_ERROR,
77
- payload: { error: 'Not recording' },
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
- // Post message to widget
88
- this.postMessage({
89
- type: CuoralMessageType.RECORDING_STOPPED,
90
- payload: {
91
- filePath: result.filePath,
92
- duration: result.duration || duration,
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
- this.postMessage({
102
- type: CuoralMessageType.RECORDING_ERROR,
103
- payload: { error: 'Failed to stop recording' },
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
- this.postMessage({
110
- type: CuoralMessageType.RECORDING_ERROR,
111
- payload: { error: error.message },
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",
@@ -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;IAGtC,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"}
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
- call.resolve([
212
- "success": true,
213
- "filePath": filePath,
214
- "duration": Int(duration)
215
- ])
216
-
217
- // Clean up
218
- self.assetWriter = nil
219
- self.videoInput = nil
220
- self.videoOutputURL = nil
221
- self.recordingStartTime = nil
222
- self.firstFrameTime = nil
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.6",
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) {