capdebug 0.1.0
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/CapDebug.podspec +17 -0
- package/README.md +231 -0
- package/android/build.gradle +63 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/com/capdebug/plugin/CapDebugPlugin.kt +241 -0
- package/dist/collectors/bridge.collector.d.ts +8 -0
- package/dist/collectors/browser-event.collector.d.ts +10 -0
- package/dist/collectors/collector.d.ts +2 -0
- package/dist/collectors/console.collector.d.ts +9 -0
- package/dist/collectors/custom-event.collector.d.ts +11 -0
- package/dist/collectors/error.collector.d.ts +9 -0
- package/dist/collectors/network.collector.d.ts +12 -0
- package/dist/definitions.d.ts +9 -0
- package/dist/index.d.ts +75 -0
- package/dist/plugin.cjs.js +2786 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +2789 -0
- package/dist/plugin.js.map +1 -0
- package/dist/plugin.mjs +2766 -0
- package/dist/plugin.mjs.map +1 -0
- package/dist/sanitizer.d.ts +16 -0
- package/dist/store.d.ts +38 -0
- package/dist/types.d.ts +148 -0
- package/dist/ui/floating-button.d.ts +28 -0
- package/dist/ui/json-viewer.d.ts +5 -0
- package/dist/ui/panel.d.ts +48 -0
- package/dist/ui/styles.d.ts +1 -0
- package/dist/ui/viewer.element.d.ts +20 -0
- package/dist/web.d.ts +10 -0
- package/ios/Sources/CapDebugPlugin/CapDebugPlugin.swift +204 -0
- package/package.json +68 -0
package/CapDebug.podspec
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = 'CapDebug'
|
|
7
|
+
s.version = package['version']
|
|
8
|
+
s.summary = package['description']
|
|
9
|
+
s.license = package['license']
|
|
10
|
+
s.homepage = 'https://github.com/capdebug/capdebug'
|
|
11
|
+
s.author = package['author']
|
|
12
|
+
s.source = { :git => 'https://github.com/capdebug/capdebug.git', :tag => s.version.to_s }
|
|
13
|
+
s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
|
|
14
|
+
s.ios.deployment_target = '13.0'
|
|
15
|
+
s.dependency 'Capacitor'
|
|
16
|
+
s.swift_version = '5.1'
|
|
17
|
+
end
|
package/README.md
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# CapDebug
|
|
2
|
+
|
|
3
|
+
> Lightweight, embedded debug viewer and live diagnostics plugin for Capacitor applications.
|
|
4
|
+
|
|
5
|
+
`capdebug` runs entirely inside your Capacitor WebView as an isolated Web Component with Shadow DOM. It displays a draggable floating debug pill on the screen that opens a mobile-first developer drawer with real-time logs, errors, network traffic, custom events, and native system diagnostics for iOS (Swift) and Android (Kotlin).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- 🚀 **Zero UI Framework Dependencies**: Built purely with Web Components, Shadow DOM, and Vanilla CSS. Works seamlessly with React, Vue, Svelte, Angular, Solid, or Vanilla JS.
|
|
12
|
+
- 🛡️ **Complete CSS Isolation**: Shadow DOM guarantees that the debug UI never conflicts with or inherits host application styles.
|
|
13
|
+
- 📱 **Mobile-First Floating Trigger**: Draggable floating pill that automatically snaps to the left/right screen edge, persists coordinates in `localStorage`, and supports safe-area insets.
|
|
14
|
+
- 📑 **Developer Drawer & Tabs**:
|
|
15
|
+
- **All**: Unified chronological event stream.
|
|
16
|
+
- **Console**: Intercepts `console.log`, `info`, `warn`, `error`, `debug`.
|
|
17
|
+
- **Errors**: Intercepts `window.onerror` and `unhandledrejection` with stack traces.
|
|
18
|
+
- **Events**: Custom app events dispatched via `CapDebug.emit()` or `CustomEvent('capdebug')`.
|
|
19
|
+
- **Native**: Native plugin events and bridge telemetry.
|
|
20
|
+
- **Device**: Live lazy-loaded diagnostics card for Device, Memory/Heap, App, WebView, and Network status.
|
|
21
|
+
- **Network**: Intercepts `fetch` requests with latency, status codes, and sanitized headers.
|
|
22
|
+
- **Bridge**: Capacitor plugin bridge activity collector.
|
|
23
|
+
- 🔍 **Search & Level Filters**: Real-time filtering by search query (`type`, `message`, `data`) and severity levels (`ALL`, `DEBUG`, `INFO`, `WARN`, `ERROR`).
|
|
24
|
+
- 🌳 **Interactive JSON Tree Viewer**: Collapsible nested objects/arrays, syntax coloring, individual value copying, and full payload JSON export.
|
|
25
|
+
- 🔒 **Privacy & Sanitization by Default**: Recursively sanitizes data and masks sensitive keys (`authorization`, `cookie`, `password`, `token`, `secret`, `api_key`, `jwt`, `credit_card`, etc.) while safely handling circular references, BigInt, Symbols, and DOM nodes.
|
|
26
|
+
- ⚡ **High Performance**: Circular FIFO ring buffer (default 500 events), incremental rendering, and `requestAnimationFrame` UI updates ensure zero lag in the host application.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install -D capdebug
|
|
34
|
+
npx cap sync
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
Initialize CapDebug in your app's entry point:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { CapDebug } from 'capdebug';
|
|
45
|
+
|
|
46
|
+
// Mount the debug viewer
|
|
47
|
+
CapDebug.mount();
|
|
48
|
+
|
|
49
|
+
// Emit a test event
|
|
50
|
+
CapDebug.emit({
|
|
51
|
+
type: 'app.booted',
|
|
52
|
+
message: 'Application started successfully',
|
|
53
|
+
data: { version: '1.0.0' }
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Disabling in Production Builds (Recommended)
|
|
58
|
+
|
|
59
|
+
To ensure zero bundle size overhead in production builds, dynamically import CapDebug only in development mode:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
if (import.meta.env.DEV) {
|
|
63
|
+
const { CapDebug } = await import('capdebug');
|
|
64
|
+
|
|
65
|
+
CapDebug.mount({
|
|
66
|
+
console: true,
|
|
67
|
+
errors: true,
|
|
68
|
+
network: true,
|
|
69
|
+
native: true,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Configuration Options
|
|
77
|
+
|
|
78
|
+
Pass options to `CapDebug.mount(options)`:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
CapDebug.mount({
|
|
82
|
+
button: true, // Show floating trigger button (default: true)
|
|
83
|
+
position: 'right', // Initial placement: 'right' | 'left' | { x, y } (default: 'right')
|
|
84
|
+
maxEvents: 500, // Max events retained in ring buffer (default: 500)
|
|
85
|
+
console: true, // Auto-capture console.log/info/warn/error/debug (default: true)
|
|
86
|
+
errors: true, // Auto-capture window errors & unhandled promise rejections (default: true)
|
|
87
|
+
network: false, // Auto-capture fetch API requests (default: false)
|
|
88
|
+
native: true, // Enable iOS/Android native diagnostics bridge (default: true)
|
|
89
|
+
bridge: false, // Enable bridge telemetry collector (default: false)
|
|
90
|
+
persistPosition: true, // Save button screen position in localStorage (default: true)
|
|
91
|
+
defaultTab: 'all', // Initial active tab: 'all' | 'console' | 'errors' | 'events' | 'device' | 'network' (default: 'all')
|
|
92
|
+
customMaskKeys: ['pin'] // Additional sensitive property keys to mask
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## CustomEvent Event Bus
|
|
99
|
+
|
|
100
|
+
CapDebug uses a standardized `CustomEvent('capdebug')` architecture as its core event bus. You can emit events from anywhere in your codebase without importing `capdebug`:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
window.dispatchEvent(
|
|
104
|
+
new CustomEvent('capdebug', {
|
|
105
|
+
detail: {
|
|
106
|
+
source: 'web',
|
|
107
|
+
type: 'user.login',
|
|
108
|
+
level: 'info',
|
|
109
|
+
message: 'User logged in',
|
|
110
|
+
data: {
|
|
111
|
+
userId: 'usr_102',
|
|
112
|
+
role: 'admin',
|
|
113
|
+
token: 'secret_jwt_token' // Auto-masked as '********'
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Or use the convenient public helper:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { CapDebug } from 'capdebug';
|
|
124
|
+
|
|
125
|
+
CapDebug.emit({
|
|
126
|
+
type: 'cart.checkout',
|
|
127
|
+
level: 'info',
|
|
128
|
+
message: 'Checkout started',
|
|
129
|
+
duration: 180,
|
|
130
|
+
data: { total: 49.99, itemsCount: 3 }
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Public API Reference
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
// Lifecycle
|
|
140
|
+
CapDebug.mount(options?: CapDebugOptions);
|
|
141
|
+
CapDebug.unmount();
|
|
142
|
+
|
|
143
|
+
// Visibility
|
|
144
|
+
CapDebug.show(); // Open drawer
|
|
145
|
+
CapDebug.hide(); // Close drawer
|
|
146
|
+
CapDebug.toggle(); // Toggle drawer
|
|
147
|
+
|
|
148
|
+
// Event & Buffer Control
|
|
149
|
+
CapDebug.emit(event: CapDebugEventInput);
|
|
150
|
+
CapDebug.clear(); // Clear all recorded events
|
|
151
|
+
CapDebug.pause(); // Pause recording
|
|
152
|
+
CapDebug.resume(); // Resume recording
|
|
153
|
+
|
|
154
|
+
// Interception Collectors
|
|
155
|
+
CapDebug.captureConsole();
|
|
156
|
+
CapDebug.releaseConsole();
|
|
157
|
+
|
|
158
|
+
CapDebug.captureErrors();
|
|
159
|
+
CapDebug.releaseErrors();
|
|
160
|
+
|
|
161
|
+
CapDebug.captureFetch({ body?: boolean });
|
|
162
|
+
CapDebug.releaseFetch();
|
|
163
|
+
|
|
164
|
+
CapDebug.observeEvent('online');
|
|
165
|
+
CapDebug.observeEvent('visibilitychange');
|
|
166
|
+
CapDebug.unobserveEvent('online');
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Native Device Diagnostics
|
|
172
|
+
|
|
173
|
+
When running in Capacitor iOS or Android (or web fallback), diagnostics are fetched on-demand when opening the **Device** tab or via programmatic calls:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
import { CapDebug } from 'capdebug';
|
|
177
|
+
|
|
178
|
+
// Device Info (manufacturer, model, OS version, architecture, emulator flag)
|
|
179
|
+
const device = await CapDebug.native.getDeviceInfo();
|
|
180
|
+
|
|
181
|
+
// Memory Info (app heap usage, total/max heap, system available memory)
|
|
182
|
+
const memory = await CapDebug.native.getMemoryInfo();
|
|
183
|
+
|
|
184
|
+
// WebView Info (user agent, webview version, debug mode)
|
|
185
|
+
const webView = await CapDebug.native.getWebViewInfo();
|
|
186
|
+
|
|
187
|
+
// App Info (bundle ID / package name, version, build number)
|
|
188
|
+
const app = await CapDebug.native.getAppInfo();
|
|
189
|
+
|
|
190
|
+
// Network Info (connection type: wifi/cellular/ethernet, metered status)
|
|
191
|
+
const network = await CapDebug.native.getNetworkInfo();
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Privacy & Security
|
|
197
|
+
|
|
198
|
+
CapDebug is designed with privacy-first defaults:
|
|
199
|
+
1. **Recursive Masking**: Passwords, tokens, cookies, auth headers, and API keys are automatically replaced with `'********'` in all data payloads and event views.
|
|
200
|
+
2. **No Payload Leaks in Network Capture**: Network body capturing is disabled by default. If enabled via `{ network: { body: true } }`, sensitive fields inside JSON bodies are masked.
|
|
201
|
+
3. **Permission Safety**: Native plugins only use standard, non-invasive system metrics (e.g. `NetworkCapabilities`, `ProcessInfo`, `mach_task_basic_info`). No fine location or private telephony permissions are requested.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Running the Demo App
|
|
206
|
+
|
|
207
|
+
The repository includes a complete Capacitor example app in the `example/` directory:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
# Build capdebug package
|
|
211
|
+
npm run build
|
|
212
|
+
|
|
213
|
+
# Enter example directory and install
|
|
214
|
+
cd example
|
|
215
|
+
npm install
|
|
216
|
+
npm run build
|
|
217
|
+
|
|
218
|
+
# Run in browser
|
|
219
|
+
npm run dev
|
|
220
|
+
|
|
221
|
+
# Or sync to native mobile platforms
|
|
222
|
+
npx cap add ios
|
|
223
|
+
npx cap add android
|
|
224
|
+
npx cap sync
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## License
|
|
230
|
+
|
|
231
|
+
MIT
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
ext {
|
|
2
|
+
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
|
|
3
|
+
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.6.1'
|
|
4
|
+
androidxCoreVersion = project.hasProperty('androidxCoreVersion') ? rootProject.ext.androidxCoreVersion : '1.12.0'
|
|
5
|
+
androidxWebkitVersion = project.hasProperty('androidxWebkitVersion') ? rootProject.ext.androidxWebkitVersion : '1.10.0'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
buildscript {
|
|
9
|
+
repositories {
|
|
10
|
+
google()
|
|
11
|
+
mavenCentral()
|
|
12
|
+
}
|
|
13
|
+
dependencies {
|
|
14
|
+
classpath 'com.android.tools.build:gradle:8.2.1'
|
|
15
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
apply plugin: 'com.android.library'
|
|
20
|
+
apply plugin: 'kotlin-android'
|
|
21
|
+
|
|
22
|
+
android {
|
|
23
|
+
namespace "com.capdebug.plugin"
|
|
24
|
+
compileSdkVersion project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 34
|
|
25
|
+
defaultConfig {
|
|
26
|
+
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 22
|
|
27
|
+
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 34
|
|
28
|
+
versionCode 1
|
|
29
|
+
versionName "0.1.0"
|
|
30
|
+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
31
|
+
}
|
|
32
|
+
buildTypes {
|
|
33
|
+
release {
|
|
34
|
+
minifyEnabled false
|
|
35
|
+
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
lintOptions {
|
|
39
|
+
abortOnError false
|
|
40
|
+
}
|
|
41
|
+
compileOptions {
|
|
42
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
43
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
44
|
+
}
|
|
45
|
+
kotlinOptions {
|
|
46
|
+
jvmTarget = '17'
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
repositories {
|
|
51
|
+
google()
|
|
52
|
+
mavenCentral()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
dependencies {
|
|
56
|
+
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
57
|
+
implementation project(':capacitor-android')
|
|
58
|
+
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
|
59
|
+
implementation "androidx.core:core-ktx:$androidxCoreVersion"
|
|
60
|
+
implementation "androidx.webkit:webkit:$androidxWebkitVersion"
|
|
61
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.22"
|
|
62
|
+
testImplementation "junit:junit:$junitVersion"
|
|
63
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
package com.capdebug.plugin
|
|
2
|
+
|
|
3
|
+
import android.app.ActivityManager
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.pm.ApplicationInfo
|
|
6
|
+
import android.content.pm.PackageInfo
|
|
7
|
+
import android.content.pm.PackageManager
|
|
8
|
+
import android.net.ConnectivityManager
|
|
9
|
+
import android.net.NetworkCapabilities
|
|
10
|
+
import android.os.Build
|
|
11
|
+
import android.webkit.WebView
|
|
12
|
+
import androidx.webkit.WebViewCompat
|
|
13
|
+
import com.getcapacitor.JSObject
|
|
14
|
+
import com.getcapacitor.Plugin
|
|
15
|
+
import com.getcapacitor.PluginCall
|
|
16
|
+
import com.getcapacitor.PluginMethod
|
|
17
|
+
import com.getcapacitor.annotation.CapacitorPlugin
|
|
18
|
+
import java.util.Locale
|
|
19
|
+
|
|
20
|
+
@CapacitorPlugin(name = "CapDebug")
|
|
21
|
+
class CapDebugPlugin : Plugin() {
|
|
22
|
+
|
|
23
|
+
@PluginMethod
|
|
24
|
+
fun getDeviceInfo(call: PluginCall) {
|
|
25
|
+
try {
|
|
26
|
+
val ret = JSObject()
|
|
27
|
+
ret.put("manufacturer", Build.MANUFACTURER)
|
|
28
|
+
ret.put("model", Build.MODEL)
|
|
29
|
+
ret.put("device", Build.DEVICE)
|
|
30
|
+
ret.put("androidVersion", Build.VERSION.RELEASE)
|
|
31
|
+
ret.put("sdkVersion", Build.VERSION.SDK_INT)
|
|
32
|
+
ret.put("isEmulator", isEmulator())
|
|
33
|
+
ret.put("architecture", Build.SUPPORTED_ABIS.joinToString(", "))
|
|
34
|
+
ret.put("availableProcessors", Runtime.getRuntime().availableProcessors())
|
|
35
|
+
ret.put("platform", "android")
|
|
36
|
+
call.resolve(ret)
|
|
37
|
+
} catch (e: Exception) {
|
|
38
|
+
call.reject("Failed to get device info: ${e.message}", e)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@PluginMethod
|
|
43
|
+
fun getMemoryInfo(call: PluginCall) {
|
|
44
|
+
try {
|
|
45
|
+
val ret = JSObject()
|
|
46
|
+
val runtime = Runtime.getRuntime()
|
|
47
|
+
val totalHeap = runtime.totalMemory()
|
|
48
|
+
val freeHeap = runtime.freeMemory()
|
|
49
|
+
val maxHeap = runtime.maxMemory()
|
|
50
|
+
val usedHeap = totalHeap - freeHeap
|
|
51
|
+
|
|
52
|
+
ret.put("appMemoryUsage", usedHeap)
|
|
53
|
+
ret.put("appMemoryUsageFormatted", formatBytes(usedHeap))
|
|
54
|
+
ret.put("totalHeap", totalHeap)
|
|
55
|
+
ret.put("totalHeapFormatted", formatBytes(totalHeap))
|
|
56
|
+
ret.put("freeHeap", freeHeap)
|
|
57
|
+
ret.put("freeHeapFormatted", formatBytes(freeHeap))
|
|
58
|
+
ret.put("maxHeap", maxHeap)
|
|
59
|
+
ret.put("maxHeapFormatted", formatBytes(maxHeap))
|
|
60
|
+
|
|
61
|
+
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
|
|
62
|
+
if (activityManager != null) {
|
|
63
|
+
val memInfo = ActivityManager.MemoryInfo()
|
|
64
|
+
activityManager.getMemoryInfo(memInfo)
|
|
65
|
+
|
|
66
|
+
ret.put("systemAvailableMemory", memInfo.availMem)
|
|
67
|
+
ret.put("systemAvailableMemoryFormatted", formatBytes(memInfo.availMem))
|
|
68
|
+
ret.put("systemTotalMemory", memInfo.totalMem)
|
|
69
|
+
ret.put("systemTotalMemoryFormatted", formatBytes(memInfo.totalMem))
|
|
70
|
+
ret.put("lowMemory", memInfo.lowMemory)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
call.resolve(ret)
|
|
74
|
+
} catch (e: Exception) {
|
|
75
|
+
call.reject("Failed to get memory info: ${e.message}", e)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@PluginMethod
|
|
80
|
+
fun getWebViewInfo(call: PluginCall) {
|
|
81
|
+
try {
|
|
82
|
+
val ret = JSObject()
|
|
83
|
+
|
|
84
|
+
var webViewVersion = "Unknown"
|
|
85
|
+
var webViewPackage = "Unknown"
|
|
86
|
+
|
|
87
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
88
|
+
val pkg = WebView.getCurrentWebViewPackage()
|
|
89
|
+
if (pkg != null) {
|
|
90
|
+
webViewVersion = pkg.versionName ?: "Unknown"
|
|
91
|
+
webViewPackage = pkg.packageName ?: "Unknown"
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
val pkg = WebViewCompat.getCurrentWebViewPackage(context)
|
|
95
|
+
if (pkg != null) {
|
|
96
|
+
webViewVersion = pkg.versionName ?: "Unknown"
|
|
97
|
+
webViewPackage = pkg.packageName ?: "Unknown"
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
val userAgent = try {
|
|
102
|
+
bridge?.webView?.settings?.userAgentString ?: "Unknown"
|
|
103
|
+
} catch (e: Exception) {
|
|
104
|
+
"Unknown"
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
val isDebuggable = (context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
|
|
108
|
+
|
|
109
|
+
ret.put("version", webViewVersion)
|
|
110
|
+
ret.put("packageName", webViewPackage)
|
|
111
|
+
ret.put("userAgent", userAgent)
|
|
112
|
+
ret.put("debugEnabled", isDebuggable)
|
|
113
|
+
|
|
114
|
+
call.resolve(ret)
|
|
115
|
+
} catch (e: Exception) {
|
|
116
|
+
call.reject("Failed to get webview info: ${e.message}", e)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@PluginMethod
|
|
121
|
+
fun getAppInfo(call: PluginCall) {
|
|
122
|
+
try {
|
|
123
|
+
val ret = JSObject()
|
|
124
|
+
val packageName = context.packageName
|
|
125
|
+
val packageManager = context.packageManager
|
|
126
|
+
|
|
127
|
+
var versionName = "Unknown"
|
|
128
|
+
var versionCode: Long = -1
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
val packageInfo: PackageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
132
|
+
packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0))
|
|
133
|
+
} else {
|
|
134
|
+
@Suppress("DEPRECATION")
|
|
135
|
+
packageManager.getPackageInfo(packageName, 0)
|
|
136
|
+
}
|
|
137
|
+
versionName = packageInfo.versionName ?: "Unknown"
|
|
138
|
+
versionCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
139
|
+
packageInfo.longVersionCode
|
|
140
|
+
} else {
|
|
141
|
+
@Suppress("DEPRECATION")
|
|
142
|
+
packageInfo.versionCode.toLong()
|
|
143
|
+
}
|
|
144
|
+
} catch (e: Exception) {
|
|
145
|
+
// ignore
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
val isDebuggable = (context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
|
|
149
|
+
|
|
150
|
+
ret.put("applicationId", packageName)
|
|
151
|
+
ret.put("versionName", versionName)
|
|
152
|
+
ret.put("version", versionName)
|
|
153
|
+
ret.put("versionCode", versionCode)
|
|
154
|
+
ret.put("buildType", if (isDebuggable) "debug" else "release")
|
|
155
|
+
|
|
156
|
+
call.resolve(ret)
|
|
157
|
+
} catch (e: Exception) {
|
|
158
|
+
call.reject("Failed to get app info: ${e.message}", e)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@PluginMethod
|
|
163
|
+
fun getNetworkInfo(call: PluginCall) {
|
|
164
|
+
try {
|
|
165
|
+
val ret = JSObject()
|
|
166
|
+
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
|
|
167
|
+
|
|
168
|
+
if (connectivityManager == null) {
|
|
169
|
+
ret.put("connected", false)
|
|
170
|
+
ret.put("connectionType", "none")
|
|
171
|
+
ret.put("metered", false)
|
|
172
|
+
call.resolve(ret)
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
val activeNetwork = connectivityManager.activeNetwork
|
|
177
|
+
val capabilities = connectivityManager.getNetworkCapabilities(activeNetwork)
|
|
178
|
+
|
|
179
|
+
if (capabilities == null) {
|
|
180
|
+
ret.put("connected", false)
|
|
181
|
+
ret.put("connectionType", "none")
|
|
182
|
+
ret.put("metered", false)
|
|
183
|
+
call.resolve(ret)
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
val isConnected = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
|
188
|
+
val connectionType = when {
|
|
189
|
+
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> "wifi"
|
|
190
|
+
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> "cellular"
|
|
191
|
+
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> "ethernet"
|
|
192
|
+
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) -> "vpn"
|
|
193
|
+
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> "bluetooth"
|
|
194
|
+
else -> "other"
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
val isMetered = connectivityManager.isActiveNetworkMetered
|
|
198
|
+
|
|
199
|
+
ret.put("connected", isConnected)
|
|
200
|
+
ret.put("connectionType", connectionType)
|
|
201
|
+
ret.put("metered", isMetered)
|
|
202
|
+
|
|
203
|
+
call.resolve(ret)
|
|
204
|
+
} catch (e: Exception) {
|
|
205
|
+
call.reject("Failed to get network info: ${e.message}", e)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private fun isEmulator(): Boolean {
|
|
210
|
+
val finger = Build.FINGERPRINT.lowercase(Locale.ROOT)
|
|
211
|
+
val model = Build.MODEL.lowercase(Locale.ROOT)
|
|
212
|
+
val manufacturer = Build.MANUFACTURER.lowercase(Locale.ROOT)
|
|
213
|
+
val product = Build.PRODUCT.lowercase(Locale.ROOT)
|
|
214
|
+
val hardware = Build.HARDWARE.lowercase(Locale.ROOT)
|
|
215
|
+
|
|
216
|
+
return finger.startsWith("generic") ||
|
|
217
|
+
finger.startsWith("unknown") ||
|
|
218
|
+
model.contains("google_sdk") ||
|
|
219
|
+
model.contains("emulator") ||
|
|
220
|
+
model.contains("android sdk built for x86") ||
|
|
221
|
+
manufacturer.contains("genymotion") ||
|
|
222
|
+
hardware == "goldfish" ||
|
|
223
|
+
hardware == "ranchu" ||
|
|
224
|
+
product.contains("sdk_gphone") ||
|
|
225
|
+
product.contains("sdk") ||
|
|
226
|
+
product.contains("emulator")
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private fun formatBytes(bytes: Long): String {
|
|
230
|
+
if (bytes <= 0) return "0 B"
|
|
231
|
+
val units = arrayOf("B", "KB", "MB", "GB", "TB")
|
|
232
|
+
val digitGroups = (Math.log10(bytes.toDouble()) / Math.log10(1024.0)).toInt()
|
|
233
|
+
val formatted = String.format(
|
|
234
|
+
Locale.US,
|
|
235
|
+
"%.2f %s",
|
|
236
|
+
bytes / Math.pow(1024.0, digitGroups.toDouble()),
|
|
237
|
+
units[digitGroups]
|
|
238
|
+
)
|
|
239
|
+
return formatted
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CapDebugCollector } from '../types';
|
|
2
|
+
export declare class BridgeCollector implements CapDebugCollector {
|
|
3
|
+
readonly id = "bridge";
|
|
4
|
+
private running;
|
|
5
|
+
start(): void;
|
|
6
|
+
stop(): void;
|
|
7
|
+
emitBridgeCall(pluginName: string, methodName: string, options?: unknown, duration?: number): void;
|
|
8
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CapDebugCollector } from '../types';
|
|
2
|
+
export declare class BrowserEventCollector implements CapDebugCollector {
|
|
3
|
+
readonly id = "browser-event";
|
|
4
|
+
private running;
|
|
5
|
+
private observedEvents;
|
|
6
|
+
start(): void;
|
|
7
|
+
observe(eventName: string): void;
|
|
8
|
+
unobserve(eventName: string): void;
|
|
9
|
+
stop(): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CapDebugCollector } from '../types';
|
|
2
|
+
import type { CapDebugStore } from '../store';
|
|
3
|
+
export declare class CustomEventCollector implements CapDebugCollector {
|
|
4
|
+
readonly id = "custom-event";
|
|
5
|
+
private store;
|
|
6
|
+
private running;
|
|
7
|
+
private handler;
|
|
8
|
+
constructor(store: CapDebugStore);
|
|
9
|
+
start(): void;
|
|
10
|
+
stop(): void;
|
|
11
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CapDebugCollector, NetworkCaptureOptions } from '../types';
|
|
2
|
+
export declare class NetworkCollector implements CapDebugCollector {
|
|
3
|
+
readonly id = "network";
|
|
4
|
+
private running;
|
|
5
|
+
private originalFetch;
|
|
6
|
+
private options;
|
|
7
|
+
constructor(options?: NetworkCaptureOptions);
|
|
8
|
+
setOptions(options: NetworkCaptureOptions): void;
|
|
9
|
+
start(): void;
|
|
10
|
+
stop(): void;
|
|
11
|
+
private emitNetworkEvent;
|
|
12
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DeviceInfo, MemoryInfo, WebViewInfo, AppInfo, NetworkInfo } from './types';
|
|
2
|
+
export interface CapDebugNativePlugin {
|
|
3
|
+
getDeviceInfo(): Promise<DeviceInfo>;
|
|
4
|
+
getMemoryInfo(): Promise<MemoryInfo>;
|
|
5
|
+
getWebViewInfo(): Promise<WebViewInfo>;
|
|
6
|
+
getAppInfo(): Promise<AppInfo>;
|
|
7
|
+
getNetworkInfo(): Promise<NetworkInfo>;
|
|
8
|
+
}
|
|
9
|
+
export declare const CapDebugNative: CapDebugNativePlugin;
|