cuoral-ionic 0.0.1 → 0.0.3
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/README.md +81 -4
- package/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.9/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/build/.transforms/2c48e1f34ca03014b78fcb3e0ab7197b/results.bin +1 -0
- package/android/build/.transforms/2c48e1f34ca03014b78fcb3e0ab7197b/transformed/classes/classes_dex/classes.dex +0 -0
- package/android/build/.transforms/980c51bd075f726f311ad662d5d20ba0/results.bin +1 -0
- package/android/build/.transforms/980c51bd075f726f311ad662d5d20ba0/transformed/classes/classes_dex/classes.dex +0 -0
- package/android/build/.transforms/bb54161301273cf9b5b94a21c0fb3f23/results.bin +1 -0
- package/android/build/.transforms/bb54161301273cf9b5b94a21c0fb3f23/transformed/classes/classes_dex/classes.dex +0 -0
- package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/results.bin +1 -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.dex +0 -0
- package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/debug_dex/com/cuoral/ionic/ScreenRecordService.dex +0 -0
- package/android/build/.transforms/f1aabffcd8b03aa664e77a79b3e1de5d/transformed/debug/desugar_graph.bin +0 -0
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/AndroidManifest.xml +22 -0
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/output-metadata.json +18 -0
- package/android/build/intermediates/aar_main_jar/debug/classes.jar +0 -0
- package/android/build/intermediates/aar_metadata/debug/aar-metadata.properties +6 -0
- package/android/build/intermediates/annotation_processor_list/debug/annotationProcessors.json +1 -0
- package/android/build/intermediates/annotations_typedef_file/debug/typedefs.txt +0 -0
- package/android/build/intermediates/compile_library_classes_jar/debug/classes.jar +0 -0
- package/android/build/intermediates/compile_r_class_jar/debug/R.jar +0 -0
- package/android/build/intermediates/compile_symbol_list/debug/R.txt +0 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +2 -0
- package/android/build/intermediates/incremental/debug-mergeJavaRes/merge-state +0 -0
- package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +2 -0
- package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +2 -0
- package/android/build/intermediates/incremental/packageDebugAssets/merger.xml +2 -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.class +0 -0
- package/android/build/intermediates/javac/debug/classes/com/cuoral/ionic/ScreenRecordService.class +0 -0
- package/android/build/intermediates/local_only_symbol_list/debug/R-def.txt +2 -0
- package/android/build/intermediates/manifest_merge_blame_file/debug/manifest-merger-blame-debug-report.txt +38 -0
- package/android/build/intermediates/merged_java_res/debug/feature-cuoral-ionic.jar +0 -0
- package/android/build/intermediates/merged_manifest/debug/AndroidManifest.xml +22 -0
- package/android/build/intermediates/navigation_json/debug/navigation.json +1 -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.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/com/cuoral/ionic/ScreenRecordService.class +0 -0
- package/android/build/intermediates/runtime_library_classes_jar/debug/classes.jar +0 -0
- package/android/build/intermediates/symbol_list_with_package_name/debug/package-aware-r.txt +1 -0
- package/android/build/outputs/aar/cuoral-ionic-debug.aar +0 -0
- package/android/build/outputs/logs/manifest-merger-debug-report.txt +49 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$1.class.uniqueId1 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin$2.class.uniqueId2 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/CuoralPlugin.class.uniqueId0 +0 -0
- package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
- package/android/build.gradle +61 -0
- package/android/src/main/AndroidManifest.xml +9 -0
- package/android/src/main/java/com/cuoral/ionic/CuoralPlugin.java +290 -33
- package/android/src/main/java/com/cuoral/ionic/ScreenRecordService.java +59 -0
- package/dist/cuoral.d.ts +30 -1
- package/dist/cuoral.d.ts.map +1 -1
- package/dist/cuoral.js +168 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +706 -127
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +706 -126
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +66 -0
- package/dist/intelligence.d.ts.map +1 -0
- package/dist/intelligence.js +508 -0
- package/dist/plugin.d.ts +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +24 -6
- package/dist/web.d.ts.map +1 -1
- package/dist/web.js +0 -3
- package/ios/Plugin/CuoralPlugin.swift +78 -1
- package/package.json +1 -1
- package/src/cuoral.ts +205 -1
- package/src/index.ts +1 -0
- package/src/intelligence.ts +609 -0
- package/src/plugin.ts +26 -6
- package/src/web.ts +0 -6
|
@@ -16,6 +16,7 @@ import android.view.View;
|
|
|
16
16
|
|
|
17
17
|
import androidx.core.app.ActivityCompat;
|
|
18
18
|
import androidx.core.content.ContextCompat;
|
|
19
|
+
import androidx.activity.result.ActivityResult;
|
|
19
20
|
|
|
20
21
|
import com.getcapacitor.JSObject;
|
|
21
22
|
import com.getcapacitor.Plugin;
|
|
@@ -24,10 +25,16 @@ import com.getcapacitor.PluginMethod;
|
|
|
24
25
|
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
25
26
|
import com.getcapacitor.annotation.Permission;
|
|
26
27
|
import com.getcapacitor.annotation.ActivityCallback;
|
|
28
|
+
import com.getcapacitor.annotation.PermissionCallback;
|
|
27
29
|
|
|
28
30
|
import java.io.ByteArrayOutputStream;
|
|
29
31
|
import java.io.File;
|
|
30
32
|
import java.io.IOException;
|
|
33
|
+
import java.io.PrintWriter;
|
|
34
|
+
import java.io.StringWriter;
|
|
35
|
+
|
|
36
|
+
import org.json.JSONObject;
|
|
37
|
+
import org.json.JSONException;
|
|
31
38
|
|
|
32
39
|
@CapacitorPlugin(name = "CuoralPlugin", permissions = {
|
|
33
40
|
@Permission(strings = { Manifest.permission.RECORD_AUDIO }, alias = "audio"),
|
|
@@ -38,13 +45,18 @@ public class CuoralPlugin extends Plugin {
|
|
|
38
45
|
private static final int SCREEN_CAPTURE_REQUEST_CODE = 1001;
|
|
39
46
|
private static final int PERMISSION_REQUEST_CODE = 1002;
|
|
40
47
|
|
|
48
|
+
private Thread.UncaughtExceptionHandler defaultExceptionHandler;
|
|
49
|
+
private String intelligenceBackendUrl;
|
|
50
|
+
|
|
41
51
|
private MediaProjectionManager projectionManager;
|
|
42
52
|
private MediaProjection mediaProjection;
|
|
43
53
|
private MediaRecorder mediaRecorder;
|
|
54
|
+
private android.hardware.display.VirtualDisplay virtualDisplay;
|
|
44
55
|
private boolean isRecording = false;
|
|
45
56
|
private long recordingStartTime = 0;
|
|
46
57
|
private String videoFilePath;
|
|
47
58
|
private PluginCall currentCall;
|
|
59
|
+
private Intent serviceIntent;
|
|
48
60
|
|
|
49
61
|
@Override
|
|
50
62
|
public void load() {
|
|
@@ -53,6 +65,139 @@ public class CuoralPlugin extends Plugin {
|
|
|
53
65
|
.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
|
|
54
66
|
}
|
|
55
67
|
|
|
68
|
+
@PluginMethod
|
|
69
|
+
public void setupNativeErrorCapture(PluginCall call) {
|
|
70
|
+
try {
|
|
71
|
+
String backendUrl = call.getString("backendUrl");
|
|
72
|
+
String sessionId = call.getString("sessionId");
|
|
73
|
+
|
|
74
|
+
if (backendUrl == null || sessionId == null) {
|
|
75
|
+
call.reject("Backend URL and Session ID are required");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.intelligenceBackendUrl = backendUrl;
|
|
80
|
+
|
|
81
|
+
// Save the default exception handler
|
|
82
|
+
defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
|
|
83
|
+
|
|
84
|
+
// Set custom exception handler with maximum safety
|
|
85
|
+
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
|
86
|
+
@Override
|
|
87
|
+
public void uncaughtException(Thread thread, Throwable throwable) {
|
|
88
|
+
try {
|
|
89
|
+
// Try to send error, but never let this fail the crash
|
|
90
|
+
handleNativeError(throwable, sessionId);
|
|
91
|
+
} catch (Throwable t) {
|
|
92
|
+
// Completely silently ignore any errors in our error handler
|
|
93
|
+
// This ensures we never interfere with the app's crash
|
|
94
|
+
} finally {
|
|
95
|
+
// ALWAYS call the default handler, no matter what
|
|
96
|
+
if (defaultExceptionHandler != null) {
|
|
97
|
+
try {
|
|
98
|
+
defaultExceptionHandler.uncaughtException(thread, throwable);
|
|
99
|
+
} catch (Throwable t) {
|
|
100
|
+
// Even if default handler fails, don't throw
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
JSObject ret = new JSObject();
|
|
108
|
+
ret.put("success", true);
|
|
109
|
+
ret.put("message", "Native error capture enabled");
|
|
110
|
+
call.resolve(ret);
|
|
111
|
+
|
|
112
|
+
} catch (Exception e) {
|
|
113
|
+
// If setup fails, fail gracefully - don't crash the app
|
|
114
|
+
call.reject("Failed to setup native error capture", e);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private void handleNativeError(Throwable throwable, String sessionId) {
|
|
119
|
+
try {
|
|
120
|
+
// Get stack trace as string
|
|
121
|
+
StringWriter sw = new StringWriter();
|
|
122
|
+
PrintWriter pw = new PrintWriter(sw);
|
|
123
|
+
throwable.printStackTrace(pw);
|
|
124
|
+
String stackTrace = sw.toString();
|
|
125
|
+
|
|
126
|
+
// Create metadata
|
|
127
|
+
JSONObject metadata = new JSONObject();
|
|
128
|
+
metadata.put("device_model", Build.MODEL);
|
|
129
|
+
metadata.put("os_version", Build.VERSION.RELEASE);
|
|
130
|
+
metadata.put("thread", Thread.currentThread().getName());
|
|
131
|
+
metadata.put("error_type", "native_crash");
|
|
132
|
+
try {
|
|
133
|
+
metadata.put("app_version", getContext().getPackageManager()
|
|
134
|
+
.getPackageInfo(getContext().getPackageName(), 0).versionName);
|
|
135
|
+
} catch (Exception e) {
|
|
136
|
+
metadata.put("app_version", "unknown");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Create error data matching backend format (console error endpoint expects
|
|
140
|
+
// array)
|
|
141
|
+
JSONObject errorData = new JSONObject();
|
|
142
|
+
errorData.put("message",
|
|
143
|
+
throwable.getMessage() != null ? throwable.getMessage() : throwable.getClass().getName());
|
|
144
|
+
errorData.put("stack_trace", stackTrace);
|
|
145
|
+
errorData.put("log_level", "error"); // Backend accepts: error, warn, info, debug
|
|
146
|
+
errorData.put("url", "native://android");
|
|
147
|
+
errorData.put("session_id", sessionId);
|
|
148
|
+
errorData.put("source", "mobile");
|
|
149
|
+
errorData.put("console_metadata", metadata);
|
|
150
|
+
|
|
151
|
+
// Backend expects an array of events
|
|
152
|
+
org.json.JSONArray payload = new org.json.JSONArray();
|
|
153
|
+
payload.put(errorData);
|
|
154
|
+
|
|
155
|
+
// Send SYNCHRONOUSLY - must complete before app terminates
|
|
156
|
+
sendNativeErrorToBackendSync(payload.toString());
|
|
157
|
+
|
|
158
|
+
} catch (Exception e) {
|
|
159
|
+
// Silently fail - don't crash the crash handler
|
|
160
|
+
e.printStackTrace();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private void sendNativeErrorToBackendSync(String jsonPayload) {
|
|
165
|
+
java.net.HttpURLConnection conn = null;
|
|
166
|
+
try {
|
|
167
|
+
java.net.URL url = new java.net.URL(intelligenceBackendUrl);
|
|
168
|
+
conn = (java.net.HttpURLConnection) url.openConnection();
|
|
169
|
+
conn.setRequestMethod("POST");
|
|
170
|
+
conn.setRequestProperty("Content-Type", "application/json");
|
|
171
|
+
conn.setDoOutput(true);
|
|
172
|
+
conn.setConnectTimeout(2000); // 2 second timeout - fast fail
|
|
173
|
+
conn.setReadTimeout(2000); // 2 second timeout - fast fail
|
|
174
|
+
|
|
175
|
+
java.io.OutputStream os = conn.getOutputStream();
|
|
176
|
+
os.write(jsonPayload.getBytes("UTF-8"));
|
|
177
|
+
os.flush();
|
|
178
|
+
os.close();
|
|
179
|
+
|
|
180
|
+
// Get response code to ensure request completed
|
|
181
|
+
int responseCode = conn.getResponseCode();
|
|
182
|
+
|
|
183
|
+
} catch (java.net.SocketTimeoutException e) {
|
|
184
|
+
// Silently fail - timeout
|
|
185
|
+
} catch (java.io.IOException e) {
|
|
186
|
+
// Silently fail - IO error
|
|
187
|
+
} catch (Exception e) {
|
|
188
|
+
// Silently fail - error
|
|
189
|
+
} finally {
|
|
190
|
+
// Always disconnect, even if error
|
|
191
|
+
if (conn != null) {
|
|
192
|
+
try {
|
|
193
|
+
conn.disconnect();
|
|
194
|
+
} catch (Exception e) {
|
|
195
|
+
// Ignore disconnect errors
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
56
201
|
@PluginMethod
|
|
57
202
|
public void startRecording(PluginCall call) {
|
|
58
203
|
if (isRecording) {
|
|
@@ -70,43 +215,98 @@ public class CuoralPlugin extends Plugin {
|
|
|
70
215
|
return;
|
|
71
216
|
}
|
|
72
217
|
|
|
73
|
-
// Request screen capture
|
|
74
|
-
Intent captureIntent
|
|
218
|
+
// Request screen capture - force entire screen mode only
|
|
219
|
+
Intent captureIntent;
|
|
220
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
221
|
+
// Android 14+ (API 34+): Use MediaProjectionConfig to force entire screen
|
|
222
|
+
// capture
|
|
223
|
+
android.media.projection.MediaProjectionConfig config = android.media.projection.MediaProjectionConfig
|
|
224
|
+
.createConfigForDefaultDisplay();
|
|
225
|
+
captureIntent = projectionManager.createScreenCaptureIntent(config);
|
|
226
|
+
} else {
|
|
227
|
+
// Older versions: Default to entire screen
|
|
228
|
+
captureIntent = projectionManager.createScreenCaptureIntent();
|
|
229
|
+
}
|
|
75
230
|
startActivityForResult(call, captureIntent, "screenCaptureCallback");
|
|
76
231
|
}
|
|
77
232
|
|
|
78
233
|
@ActivityCallback
|
|
79
|
-
private void screenCaptureCallback(PluginCall call,
|
|
234
|
+
private void screenCaptureCallback(PluginCall call, ActivityResult result) {
|
|
80
235
|
if (result.getResultCode() != Activity.RESULT_OK) {
|
|
81
236
|
call.reject("Screen capture permission denied");
|
|
82
237
|
return;
|
|
83
238
|
}
|
|
84
239
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
setupMediaRecorder(includeAudio, quality);
|
|
94
|
-
mediaRecorder.start();
|
|
95
|
-
isRecording = true;
|
|
96
|
-
recordingStartTime = System.currentTimeMillis();
|
|
97
|
-
|
|
98
|
-
JSObject ret = new JSObject();
|
|
99
|
-
ret.put("success", true);
|
|
100
|
-
call.resolve(ret);
|
|
101
|
-
|
|
102
|
-
// Notify listeners
|
|
103
|
-
JSObject eventData = new JSObject();
|
|
104
|
-
eventData.put("timestamp", recordingStartTime);
|
|
105
|
-
notifyListeners("recordingStarted", eventData);
|
|
106
|
-
|
|
107
|
-
} catch (Exception e) {
|
|
108
|
-
call.reject("Failed to start recording: " + e.getMessage());
|
|
240
|
+
// Start foreground service for media projection
|
|
241
|
+
serviceIntent = new Intent(getContext(), ScreenRecordService.class);
|
|
242
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
243
|
+
getContext().startForegroundService(serviceIntent);
|
|
244
|
+
} else {
|
|
245
|
+
getContext().startService(serviceIntent);
|
|
109
246
|
}
|
|
247
|
+
|
|
248
|
+
// Wait for service to enter foreground state before getting MediaProjection
|
|
249
|
+
// This is required on Android 14+ (API 34+)
|
|
250
|
+
getActivity().runOnUiThread(() -> {
|
|
251
|
+
android.os.Handler handler = new android.os.Handler();
|
|
252
|
+
handler.postDelayed(() -> {
|
|
253
|
+
try {
|
|
254
|
+
Intent data = result.getData();
|
|
255
|
+
mediaProjection = projectionManager.getMediaProjection(result.getResultCode(), data);
|
|
256
|
+
|
|
257
|
+
// Register callback for Android 14+ (required before creating virtual display)
|
|
258
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
259
|
+
mediaProjection.registerCallback(new android.media.projection.MediaProjection.Callback() {
|
|
260
|
+
@Override
|
|
261
|
+
public void onStop() {
|
|
262
|
+
super.onStop();
|
|
263
|
+
// Cleanup when projection stops
|
|
264
|
+
try {
|
|
265
|
+
if (isRecording) {
|
|
266
|
+
stopRecordingInternal();
|
|
267
|
+
}
|
|
268
|
+
} catch (Exception e) {
|
|
269
|
+
// Ignore cleanup errors
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}, null);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Setup media recorder
|
|
276
|
+
boolean includeAudio = call.getBoolean("includeAudio", false);
|
|
277
|
+
float quality = call.getFloat("quality", 1.0f);
|
|
278
|
+
|
|
279
|
+
setupMediaRecorder(includeAudio, quality);
|
|
280
|
+
|
|
281
|
+
// Create virtual display to capture screen
|
|
282
|
+
DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
|
|
283
|
+
virtualDisplay = mediaProjection.createVirtualDisplay(
|
|
284
|
+
"CuoralScreenCapture",
|
|
285
|
+
metrics.widthPixels,
|
|
286
|
+
metrics.heightPixels,
|
|
287
|
+
metrics.densityDpi,
|
|
288
|
+
android.view.Display.DEFAULT_DISPLAY,
|
|
289
|
+
mediaRecorder.getSurface(),
|
|
290
|
+
null,
|
|
291
|
+
null);
|
|
292
|
+
|
|
293
|
+
mediaRecorder.start();
|
|
294
|
+
isRecording = true;
|
|
295
|
+
recordingStartTime = System.currentTimeMillis();
|
|
296
|
+
|
|
297
|
+
JSObject ret = new JSObject();
|
|
298
|
+
ret.put("success", true);
|
|
299
|
+
call.resolve(ret);
|
|
300
|
+
} catch (Exception e) {
|
|
301
|
+
call.reject("Failed to start recording: " + e.getMessage());
|
|
302
|
+
// Stop service if recording failed
|
|
303
|
+
if (serviceIntent != null) {
|
|
304
|
+
getContext().stopService(serviceIntent);
|
|
305
|
+
serviceIntent = null;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}, 500); // 500ms delay to ensure service is in foreground
|
|
309
|
+
});
|
|
110
310
|
}
|
|
111
311
|
|
|
112
312
|
@ActivityCallback
|
|
@@ -119,6 +319,44 @@ public class CuoralPlugin extends Plugin {
|
|
|
119
319
|
startRecording(call);
|
|
120
320
|
}
|
|
121
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Internal method to stop recording without resolving a PluginCall
|
|
324
|
+
* Used by MediaProjection callback
|
|
325
|
+
*/
|
|
326
|
+
private void stopRecordingInternal() {
|
|
327
|
+
if (!isRecording) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
if (mediaRecorder != null) {
|
|
333
|
+
mediaRecorder.stop();
|
|
334
|
+
mediaRecorder.reset();
|
|
335
|
+
mediaRecorder.release();
|
|
336
|
+
mediaRecorder = null;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (virtualDisplay != null) {
|
|
340
|
+
virtualDisplay.release();
|
|
341
|
+
virtualDisplay = null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (mediaProjection != null) {
|
|
345
|
+
mediaProjection.stop();
|
|
346
|
+
mediaProjection = null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (serviceIntent != null) {
|
|
350
|
+
getContext().stopService(serviceIntent);
|
|
351
|
+
serviceIntent = null;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
isRecording = false;
|
|
355
|
+
} catch (Exception e) {
|
|
356
|
+
// Silently fail during cleanup
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
122
360
|
@PluginMethod
|
|
123
361
|
public void stopRecording(PluginCall call) {
|
|
124
362
|
if (!isRecording) {
|
|
@@ -132,11 +370,22 @@ public class CuoralPlugin extends Plugin {
|
|
|
132
370
|
mediaRecorder.release();
|
|
133
371
|
mediaRecorder = null;
|
|
134
372
|
|
|
373
|
+
if (virtualDisplay != null) {
|
|
374
|
+
virtualDisplay.release();
|
|
375
|
+
virtualDisplay = null;
|
|
376
|
+
}
|
|
377
|
+
|
|
135
378
|
if (mediaProjection != null) {
|
|
136
379
|
mediaProjection.stop();
|
|
137
380
|
mediaProjection = null;
|
|
138
381
|
}
|
|
139
382
|
|
|
383
|
+
// Stop foreground service
|
|
384
|
+
if (serviceIntent != null) {
|
|
385
|
+
getContext().stopService(serviceIntent);
|
|
386
|
+
serviceIntent = null;
|
|
387
|
+
}
|
|
388
|
+
|
|
140
389
|
isRecording = false;
|
|
141
390
|
long duration = (System.currentTimeMillis() - recordingStartTime) / 1000;
|
|
142
391
|
|
|
@@ -230,7 +479,7 @@ public class CuoralPlugin extends Plugin {
|
|
|
230
479
|
}
|
|
231
480
|
}
|
|
232
481
|
|
|
233
|
-
@
|
|
482
|
+
@PermissionCallback
|
|
234
483
|
private void permissionsCallback(PluginCall call) {
|
|
235
484
|
JSObject ret = new JSObject();
|
|
236
485
|
ret.put("granted", hasRequiredPermissions());
|
|
@@ -275,11 +524,19 @@ public class CuoralPlugin extends Plugin {
|
|
|
275
524
|
Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;
|
|
276
525
|
}
|
|
277
526
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
527
|
+
@Override
|
|
528
|
+
public boolean hasRequiredPermissions() {
|
|
529
|
+
boolean hasAudio = hasAudioPermission();
|
|
530
|
+
|
|
531
|
+
// Android 10+ (API 29+) doesn't need WRITE_EXTERNAL_STORAGE for app-specific
|
|
532
|
+
// storage
|
|
533
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
534
|
+
return hasAudio;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Android 9 and below need both RECORD_AUDIO and WRITE_EXTERNAL_STORAGE
|
|
538
|
+
return hasAudio && ContextCompat.checkSelfPermission(getContext(),
|
|
539
|
+
Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
|
|
283
540
|
}
|
|
284
541
|
|
|
285
542
|
private JSObject createErrorResult(String message) {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
package com.cuoral.ionic;
|
|
2
|
+
|
|
3
|
+
import android.app.Notification;
|
|
4
|
+
import android.app.NotificationChannel;
|
|
5
|
+
import android.app.NotificationManager;
|
|
6
|
+
import android.app.Service;
|
|
7
|
+
import android.content.Intent;
|
|
8
|
+
import android.os.Build;
|
|
9
|
+
import android.os.IBinder;
|
|
10
|
+
import androidx.core.app.NotificationCompat;
|
|
11
|
+
|
|
12
|
+
public class ScreenRecordService extends Service {
|
|
13
|
+
private static final String CHANNEL_ID = "cuoral_screen_record_channel";
|
|
14
|
+
private static final int NOTIFICATION_ID = 1001;
|
|
15
|
+
|
|
16
|
+
@Override
|
|
17
|
+
public void onCreate() {
|
|
18
|
+
super.onCreate();
|
|
19
|
+
createNotificationChannel();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@Override
|
|
23
|
+
public int onStartCommand(Intent intent, int flags, int startId) {
|
|
24
|
+
Notification notification = createNotification();
|
|
25
|
+
startForeground(NOTIFICATION_ID, notification);
|
|
26
|
+
return START_NOT_STICKY;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@Override
|
|
30
|
+
public IBinder onBind(Intent intent) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private void createNotificationChannel() {
|
|
35
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
36
|
+
NotificationChannel channel = new NotificationChannel(
|
|
37
|
+
CHANNEL_ID,
|
|
38
|
+
"Screen Recording",
|
|
39
|
+
NotificationManager.IMPORTANCE_LOW);
|
|
40
|
+
channel.setDescription("Screen recording in progress");
|
|
41
|
+
|
|
42
|
+
NotificationManager manager = getSystemService(NotificationManager.class);
|
|
43
|
+
if (manager != null) {
|
|
44
|
+
manager.createNotificationChannel(channel);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private Notification createNotification() {
|
|
50
|
+
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
|
|
51
|
+
.setContentTitle("Screen Recording")
|
|
52
|
+
.setContentText("Recording your screen...")
|
|
53
|
+
.setSmallIcon(android.R.drawable.ic_menu_view)
|
|
54
|
+
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
55
|
+
.setOngoing(true);
|
|
56
|
+
|
|
57
|
+
return builder.build();
|
|
58
|
+
}
|
|
59
|
+
}
|
package/dist/cuoral.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export declare class Cuoral {
|
|
|
15
15
|
private bridge;
|
|
16
16
|
private recorder;
|
|
17
17
|
private modal?;
|
|
18
|
+
private intelligence?;
|
|
18
19
|
private options;
|
|
19
20
|
private static readonly PRODUCTION_WIDGET_URL;
|
|
20
21
|
private static readonly DEV_WIDGET_URL;
|
|
@@ -22,7 +23,23 @@ export declare class Cuoral {
|
|
|
22
23
|
/**
|
|
23
24
|
* Initialize Cuoral
|
|
24
25
|
*/
|
|
25
|
-
initialize(): void
|
|
26
|
+
initialize(): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Initialize intelligence based on backend configuration
|
|
29
|
+
*/
|
|
30
|
+
private initializeIntelligence;
|
|
31
|
+
/**
|
|
32
|
+
* Fetch session configuration from backend
|
|
33
|
+
*/
|
|
34
|
+
private fetchSessionConfiguration;
|
|
35
|
+
/**
|
|
36
|
+
* Track a page/screen view
|
|
37
|
+
*/
|
|
38
|
+
trackPageView(screen: string, metadata?: any): void;
|
|
39
|
+
/**
|
|
40
|
+
* Track an error manually
|
|
41
|
+
*/
|
|
42
|
+
trackError(message: string, stackTrace?: string, metadata?: any): void;
|
|
26
43
|
/**
|
|
27
44
|
* Open the widget modal
|
|
28
45
|
*/
|
|
@@ -43,6 +60,18 @@ export declare class Cuoral {
|
|
|
43
60
|
* Clean up resources
|
|
44
61
|
*/
|
|
45
62
|
destroy(): void;
|
|
63
|
+
/**
|
|
64
|
+
* Get or create session ID
|
|
65
|
+
*/
|
|
66
|
+
private getOrCreateSessionId;
|
|
67
|
+
/**
|
|
68
|
+
* Initiate a new session with backend (like inline.js does)
|
|
69
|
+
*/
|
|
70
|
+
private initiateSession;
|
|
71
|
+
/**
|
|
72
|
+
* Set user profile for the session
|
|
73
|
+
*/
|
|
74
|
+
private setProfile;
|
|
46
75
|
/**
|
|
47
76
|
* Setup automatic message handlers
|
|
48
77
|
*/
|
package/dist/cuoral.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cuoral.d.ts","sourceRoot":"","sources":["../src/cuoral.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cuoral.d.ts","sourceRoot":"","sources":["../src/cuoral.ts"],"names":[],"mappings":"AAOA,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,YAAY,CAAC,CAAqB;IAC1C,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;IA2ClC;;OAEG;IACU,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAYxC;;OAEG;YACW,sBAAsB;IAwCpC;;OAEG;YACW,yBAAyB;IAoCvC;;OAEG;IACI,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,GAAG,GAAG,IAAI;IAM1D;;OAEG;IACI,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,GAAG,GAAG,IAAI;IAM7E;;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;IActB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAa5B;;OAEG;YACW,eAAe;IAmC7B;;OAEG;YACW,UAAU;IAmBxB;;OAEG;IACH,OAAO,CAAC,oBAAoB;CAuE7B"}
|