sparkling-debug-tool 2.1.0-rc.3 → 2.1.0-rc.30
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.gradle.kts +53 -19
- package/android/src/main/java/com/tiktok/sparkling/debugtool/SparklingDebugTool.kt +345 -5
- package/android/src/main/java/com/tiktok/sparkling/debugtool/SparklingDebugToolProviderImpl.kt +93 -0
- package/android/src/main/java/com/tiktok/sparkling/debugtool/console/ConsoleLog.kt +128 -0
- package/android/src/main/java/com/tiktok/sparkling/debugtool/console/ConsoleLogAdapter.kt +133 -0
- package/android/src/main/java/com/tiktok/sparkling/debugtool/console/ConsoleLogStore.kt +81 -0
- package/android/src/main/java/com/tiktok/sparkling/debugtool/console/LynxConsoleLogDelegate.kt +119 -0
- package/android/src/main/java/com/tiktok/sparkling/debugtool/console/LynxConsoleLogcatBridge.kt +107 -0
- package/android/src/main/java/com/tiktok/sparkling/debugtool/console/LynxConsoleSink.kt +66 -0
- package/android/src/main/java/com/tiktok/sparkling/debugtool/floating/SparklingFloatingBallManager.kt +43 -0
- package/android/src/main/java/com/tiktok/sparkling/debugtool/inspect/GlobalPropsRegistry.kt +59 -0
- package/android/src/main/java/com/tiktok/sparkling/debugtool/inspect/MethodInvocation.kt +52 -0
- package/android/src/main/java/com/tiktok/sparkling/debugtool/inspect/MethodInvocationStore.kt +107 -0
- package/android/src/main/java/com/tiktok/sparkling/debugtool/inspect/SparklingDebugAutoWiring.kt +44 -0
- package/android/src/main/java/com/tiktok/sparkling/debugtool/inspector/SearchInputSupport.kt +77 -0
- package/android/src/main/java/com/tiktok/sparkling/debugtool/inspector/SparklingConsolePanelView.kt +130 -0
- package/android/src/main/java/com/tiktok/sparkling/debugtool/inspector/SparklingGlobalPropsPanelView.kt +319 -0
- package/android/src/main/java/com/tiktok/sparkling/debugtool/inspector/SparklingInspectorFragment.kt +155 -0
- package/android/src/main/java/com/tiktok/sparkling/debugtool/inspector/SparklingMethodPanelView.kt +225 -0
- package/android/src/main/java/com/tiktok/sparkling/debugtool/ui/BottomSheetSupport.kt +43 -0
- package/android/src/main/res/color/sparkling_chip_toggle_text.xml +6 -0
- package/android/src/main/res/color/sparkling_inspector_tab_text.xml +5 -0
- package/android/src/main/res/color/sparkling_log_level_tab_text.xml +5 -0
- package/android/src/main/res/drawable/sparkling_chip_bg.xml +6 -0
- package/android/src/main/res/drawable/sparkling_chip_toggle_bg.xml +23 -0
- package/android/src/main/res/drawable/sparkling_flat_tab_bg.xml +25 -0
- package/android/src/main/res/drawable/sparkling_floating_ball_bg.xml +8 -0
- package/android/src/main/res/drawable/sparkling_floating_logo.png +0 -0
- package/android/src/main/res/drawable/sparkling_ic_close.xml +11 -0
- package/android/src/main/res/drawable/sparkling_ic_copy.xml +14 -0
- package/android/src/main/res/drawable/sparkling_inspector_close_bg.xml +9 -0
- package/android/src/main/res/drawable/sparkling_inspector_tab_bg.xml +16 -0
- package/android/src/main/res/drawable/sparkling_log_level_tab_bg.xml +24 -0
- package/android/src/main/res/drawable-night/sparkling_chip_bg.xml +5 -0
- package/android/src/main/res/drawable-night/sparkling_flat_tab_bg.xml +24 -0
- package/android/src/main/res/drawable-night/sparkling_log_level_tab_bg.xml +23 -0
- package/android/src/main/res/layout/fragment_sparkling_inspector.xml +110 -0
- package/android/src/main/res/layout/item_sparkling_console.xml +34 -0
- package/android/src/main/res/layout/item_sparkling_global_props_kv.xml +27 -0
- package/android/src/main/res/layout/item_sparkling_global_props_section.xml +25 -0
- package/android/src/main/res/layout/item_sparkling_method_invocation.xml +74 -0
- package/android/src/main/res/layout/view_sparkling_console_panel.xml +141 -0
- package/android/src/main/res/layout/view_sparkling_global_props_panel.xml +70 -0
- package/android/src/main/res/layout/view_sparkling_method_panel.xml +68 -0
- package/android/src/main/res/values/colors.xml +25 -0
- package/android/src/main/res/values-night/colors.xml +19 -0
- package/android/src/main/resources/META-INF/services/com.tiktok.sparkling.debug.SparklingDebugToolProvider +1 -0
- package/ios/Resources/sparkling_floating_logo.png +0 -0
- package/ios/Resources/sparkling_floating_logo@2x.png +0 -0
- package/ios/Resources/sparkling_floating_logo@3x.png +0 -0
- package/ios/Sources/Console/ConsoleLog.swift +139 -0
- package/ios/Sources/Console/ConsoleLogStore.swift +67 -0
- package/ios/Sources/Console/LynxConsoleSink.swift +50 -0
- package/ios/Sources/Console/SparklingConsoleViewController.swift +459 -0
- package/ios/Sources/Floating/SparklingFloatingBallManager.swift +179 -0
- package/ios/Sources/Inspect/GlobalPropsRegistry.swift +54 -0
- package/ios/Sources/Inspect/MethodInvocation.swift +127 -0
- package/ios/Sources/Inspect/SparklingDebugAutoWiring.swift +231 -0
- package/ios/Sources/Inspect/SparklingGlobalPropsViewController.swift +387 -0
- package/ios/Sources/Inspect/SparklingMethodInvocationViewController.swift +386 -0
- package/ios/Sources/Inspector/SparklingInspectorViewController.swift +296 -0
- package/ios/Sources/SparklingDebugTool.swift +173 -3
- package/ios/Sparkling-DebugTool.podspec +15 -11
- package/module.config.json +1 -0
- package/package.json +1 -1
package/android/build.gradle.kts
CHANGED
|
@@ -23,7 +23,7 @@ android {
|
|
|
23
23
|
isMinifyEnabled = false
|
|
24
24
|
proguardFiles(
|
|
25
25
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
|
26
|
-
"proguard-rules.pro"
|
|
26
|
+
"proguard-rules.pro",
|
|
27
27
|
)
|
|
28
28
|
}
|
|
29
29
|
}
|
|
@@ -37,22 +37,49 @@ android {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
dependencies {
|
|
40
|
+
implementation(libs.androidx.appcompat)
|
|
41
|
+
implementation("androidx.fragment:fragment-ktx:1.6.2")
|
|
42
|
+
implementation("androidx.recyclerview:recyclerview:1.3.2")
|
|
43
|
+
implementation("androidx.cardview:cardview:1.0.0")
|
|
40
44
|
implementation(libs.lynx)
|
|
41
45
|
implementation(libs.lynx.service.log)
|
|
42
46
|
implementation(libs.lynx.service.devtool)
|
|
43
47
|
implementation(libs.lynx.devtool)
|
|
48
|
+
|
|
49
|
+
val sparklingVersion =
|
|
50
|
+
(findProperty("SPARKLING_ANDROID_SDK_VERSION") as? String)
|
|
51
|
+
?: System.getenv("SPARKLING_ANDROID_SDK_VERSION")
|
|
52
|
+
?: "2.1.0-rc.30"
|
|
53
|
+
val localSparkling = rootProject.findProject(":sparkling")
|
|
54
|
+
if (localSparkling != null) {
|
|
55
|
+
compileOnly(localSparkling)
|
|
56
|
+
} else {
|
|
57
|
+
compileOnly("com.tiktok.sparkling:sparkling:$sparklingVersion")
|
|
58
|
+
}
|
|
59
|
+
val localSparklingMethod = rootProject.findProject(":sparkling-method")
|
|
60
|
+
if (localSparklingMethod != null) {
|
|
61
|
+
compileOnly(localSparklingMethod)
|
|
62
|
+
} else {
|
|
63
|
+
compileOnly("com.tiktok.sparkling:sparkling-method:$sparklingVersion")
|
|
64
|
+
}
|
|
44
65
|
}
|
|
45
66
|
|
|
46
|
-
val publishingGroupId =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
67
|
+
val publishingGroupId =
|
|
68
|
+
(findProperty("SPARKLING_PUBLISHING_GROUP_ID") as? String)
|
|
69
|
+
?: System.getenv("SPARKLING_PUBLISHING_GROUP_ID")
|
|
70
|
+
?: "com.tiktok.sparkling"
|
|
71
|
+
val publishingVersion =
|
|
72
|
+
(findProperty("SPARKLING_PUBLISHING_VERSION") as? String)
|
|
73
|
+
?: System.getenv("SPARKLING_PUBLISHING_VERSION")
|
|
74
|
+
?: "2.0.0"
|
|
52
75
|
|
|
53
76
|
val androidSourcesJar by tasks.register<Jar>("androidSourcesJar") {
|
|
54
77
|
archiveClassifier.set("sources")
|
|
55
|
-
from(
|
|
78
|
+
from(
|
|
79
|
+
android.sourceSets
|
|
80
|
+
.getByName("main")
|
|
81
|
+
.java.srcDirs,
|
|
82
|
+
)
|
|
56
83
|
}
|
|
57
84
|
|
|
58
85
|
val emptyJavadocJar by tasks.register<Jar>("javadocJar") {
|
|
@@ -103,9 +130,10 @@ afterEvaluate {
|
|
|
103
130
|
repositories {
|
|
104
131
|
maven {
|
|
105
132
|
name = "MavenCentral"
|
|
106
|
-
val repoUrl =
|
|
107
|
-
|
|
108
|
-
|
|
133
|
+
val repoUrl =
|
|
134
|
+
(findProperty("mavenCentralRepoUrl") as? String)
|
|
135
|
+
?: System.getenv("MAVEN_CENTRAL_REPO_URL")
|
|
136
|
+
?: "https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/"
|
|
109
137
|
url = uri(repoUrl)
|
|
110
138
|
credentials {
|
|
111
139
|
username = (findProperty("mavenCentralUsername") as? String)
|
|
@@ -121,12 +149,15 @@ afterEvaluate {
|
|
|
121
149
|
}
|
|
122
150
|
|
|
123
151
|
signing {
|
|
124
|
-
val signingKeyId =
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
152
|
+
val signingKeyId =
|
|
153
|
+
(findProperty("signing.keyId") as? String)
|
|
154
|
+
?: System.getenv("SIGNING_KEY_ID")
|
|
155
|
+
val signingPassword =
|
|
156
|
+
(findProperty("signing.password") as? String)
|
|
157
|
+
?: System.getenv("SIGNING_PASSWORD")
|
|
158
|
+
val signingSecretKeyRingFile =
|
|
159
|
+
(findProperty("signing.secretKeyRingFile") as? String)
|
|
160
|
+
?: System.getenv("SIGNING_SECRET_KEY_RING_FILE")
|
|
130
161
|
val signingKey = System.getenv("SIGNING_KEY")
|
|
131
162
|
|
|
132
163
|
if (!signingKeyId.isNullOrBlank() && !signingPassword.isNullOrBlank()) {
|
|
@@ -146,8 +177,11 @@ signing {
|
|
|
146
177
|
|
|
147
178
|
afterEvaluate {
|
|
148
179
|
signing {
|
|
149
|
-
val hasSigningConfig =
|
|
150
|
-
|
|
180
|
+
val hasSigningConfig =
|
|
181
|
+
!(
|
|
182
|
+
System.getenv("SIGNING_KEY_ID").isNullOrBlank() ||
|
|
183
|
+
System.getenv("SIGNING_PASSWORD").isNullOrBlank()
|
|
184
|
+
)
|
|
151
185
|
if (hasSigningConfig) {
|
|
152
186
|
sign(extensions.getByType<PublishingExtension>().publications["release"])
|
|
153
187
|
} else {
|
|
@@ -3,19 +3,359 @@
|
|
|
3
3
|
// LICENSE file in the root directory of this source tree.
|
|
4
4
|
package com.tiktok.sparkling.debugtool
|
|
5
5
|
|
|
6
|
+
import android.app.Activity
|
|
7
|
+
import android.app.AlertDialog
|
|
6
8
|
import android.app.Application
|
|
9
|
+
import android.content.Context
|
|
10
|
+
import android.content.pm.ApplicationInfo
|
|
11
|
+
import android.os.Looper
|
|
12
|
+
import android.text.InputType
|
|
13
|
+
import android.widget.EditText
|
|
14
|
+
import android.widget.Toast
|
|
15
|
+
import androidx.fragment.app.DialogFragment
|
|
16
|
+
import androidx.fragment.app.FragmentActivity
|
|
7
17
|
import com.lynx.devtool.LynxDevtoolEnv
|
|
8
18
|
import com.lynx.service.devtool.LynxDevToolService
|
|
19
|
+
import com.lynx.service.log.LynxLogService
|
|
9
20
|
import com.lynx.tasm.LynxEnv
|
|
21
|
+
import com.lynx.tasm.LynxView
|
|
10
22
|
import com.lynx.tasm.service.LynxServiceCenter
|
|
23
|
+
import com.tiktok.sparkling.debugtool.console.ConsoleLog
|
|
24
|
+
import com.tiktok.sparkling.debugtool.console.ConsoleLogStore
|
|
25
|
+
import com.tiktok.sparkling.debugtool.console.LynxConsoleLogDelegate
|
|
26
|
+
import com.tiktok.sparkling.debugtool.console.LynxConsoleLogcatBridge
|
|
27
|
+
import com.tiktok.sparkling.debugtool.console.LynxConsoleSink
|
|
28
|
+
import com.tiktok.sparkling.debugtool.floating.SparklingFloatingBallManager
|
|
29
|
+
import com.tiktok.sparkling.debugtool.inspect.GlobalPropsRegistry
|
|
30
|
+
import com.tiktok.sparkling.debugtool.inspect.GlobalPropsSnapshot
|
|
31
|
+
import com.tiktok.sparkling.debugtool.inspect.MethodInvocation
|
|
32
|
+
import com.tiktok.sparkling.debugtool.inspect.MethodInvocationStore
|
|
33
|
+
import com.tiktok.sparkling.debugtool.inspect.SparklingDebugAutoWiring
|
|
34
|
+
import com.tiktok.sparkling.debugtool.inspector.SparklingInspectorFragment
|
|
11
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Entry point for the Sparkling debug tool. The host typically only needs to
|
|
38
|
+
* call [init] once during `Application.onCreate`; everything else
|
|
39
|
+
* (Lynx debug flags, JS console capture, GlobalProps tracking, Sparkling
|
|
40
|
+
* Method observation, debugTag entry) is wired up automatically when
|
|
41
|
+
* `enableFloatingBall = true`.
|
|
42
|
+
*/
|
|
12
43
|
object SparklingDebugTool {
|
|
44
|
+
data class Config(
|
|
45
|
+
/**
|
|
46
|
+
* When `true` the host app is expected to call [attachConsoleSink] for each
|
|
47
|
+
* created `LynxView` so JS console messages are captured. When the
|
|
48
|
+
* debugTag entry is enabled the debug tool wires this up itself via
|
|
49
|
+
* `KitViewManager`, so this flag is mainly here for explicit override.
|
|
50
|
+
*/
|
|
51
|
+
val enableJsConsole: Boolean = false,
|
|
52
|
+
val enableInNonDebuggableApp: Boolean = false,
|
|
53
|
+
/** Maximum number of console messages kept in the in-memory ring buffer. */
|
|
54
|
+
val consoleBufferSize: Int = 300,
|
|
55
|
+
/**
|
|
56
|
+
* When `true` SparklingView shows its bottom-left debugTag entry. Tapping
|
|
57
|
+
* the tag opens the unified inspector; turning this on also auto-enables
|
|
58
|
+
* every Lynx debug capability and wires up the
|
|
59
|
+
* console / globalProps / method tracers.
|
|
60
|
+
*/
|
|
61
|
+
val enableFloatingBall: Boolean = false,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @deprecated Switches are no longer surfaced in the inspector. The tool
|
|
66
|
+
* applies the all-on configuration whenever the debugTag entry is enabled.
|
|
67
|
+
* The class is kept for binary compatibility with hosts that still call
|
|
68
|
+
* [getFlags] / [setFlags] directly.
|
|
69
|
+
*/
|
|
70
|
+
data class DebugFlags(
|
|
71
|
+
val lynxDebugEnabled: Boolean = true,
|
|
72
|
+
val devtoolEnabled: Boolean = true,
|
|
73
|
+
val logboxEnabled: Boolean = true,
|
|
74
|
+
val longPressMenuEnabled: Boolean = true,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
private const val PREFS_NAME = "sparkling_debug_tool"
|
|
78
|
+
private const val KEY_DEV_URL = "dev_url"
|
|
79
|
+
private const val KEY_LYNX_DEBUG = "lynx_debug"
|
|
80
|
+
private const val KEY_DEVTOOL = "devtool"
|
|
81
|
+
private const val KEY_LOGBOX = "logbox"
|
|
82
|
+
private const val KEY_LONG_PRESS = "long_press_menu"
|
|
83
|
+
|
|
84
|
+
private var currentConfig = Config()
|
|
85
|
+
|
|
86
|
+
@JvmStatic
|
|
87
|
+
fun init(
|
|
88
|
+
application: Application,
|
|
89
|
+
config: Config = Config(),
|
|
90
|
+
) {
|
|
91
|
+
currentConfig = config
|
|
92
|
+
ConsoleLogStore.setCapacity(config.consoleBufferSize)
|
|
93
|
+
if (!isDebuggableApp(application) && !config.enableInNonDebuggableApp) {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
// Always apply the all-on debug flag set when running under the debug
|
|
97
|
+
// tool: switches are no longer user-visible. Hosts that need a custom
|
|
98
|
+
// subset can still call [setFlags] explicitly.
|
|
99
|
+
applyFlags(DebugFlags())
|
|
100
|
+
SparklingFloatingBallManager.install(application)
|
|
101
|
+
SparklingFloatingBallManager.setEnabled(config.enableFloatingBall)
|
|
102
|
+
if (config.enableFloatingBall || config.enableJsConsole) {
|
|
103
|
+
LynxConsoleLogDelegate.installOnce()
|
|
104
|
+
LynxConsoleLogcatBridge.startOnce()
|
|
105
|
+
SparklingDebugAutoWiring.installOnce()
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Show or hide the bottom-left debugTag entry at runtime. */
|
|
110
|
+
@JvmStatic
|
|
111
|
+
fun setFloatingBallEnabled(enabled: Boolean) {
|
|
112
|
+
SparklingFloatingBallManager.setEnabled(enabled)
|
|
113
|
+
if (enabled) {
|
|
114
|
+
applyFlags(DebugFlags())
|
|
115
|
+
LynxConsoleLogDelegate.installOnce()
|
|
116
|
+
LynxConsoleLogcatBridge.startOnce()
|
|
117
|
+
SparklingDebugAutoWiring.installOnce()
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Legacy hook kept for compatibility; debugTag clicks use SparklingView's default action. */
|
|
122
|
+
@JvmStatic
|
|
123
|
+
fun setFloatingBallActionHandler(handler: SparklingFloatingBallManager.ActionHandler?) {
|
|
124
|
+
SparklingFloatingBallManager.setActionHandler(handler)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Build a fresh inspector fragment. Equivalent to invoking the debugTag's
|
|
129
|
+
* default tap action; useful for hosts that want to surface the
|
|
130
|
+
* inspector via a menu / button.
|
|
131
|
+
*/
|
|
132
|
+
@JvmStatic
|
|
133
|
+
fun createFragment(): DialogFragment = SparklingInspectorFragment.newInstance()
|
|
134
|
+
|
|
135
|
+
@JvmStatic
|
|
136
|
+
fun getFlags(context: Context): DebugFlags = resolveFlags(context)
|
|
137
|
+
|
|
138
|
+
@JvmStatic
|
|
139
|
+
fun setFlags(
|
|
140
|
+
context: Context,
|
|
141
|
+
flags: DebugFlags,
|
|
142
|
+
) {
|
|
143
|
+
prefs(context)
|
|
144
|
+
.edit()
|
|
145
|
+
.putBoolean(KEY_LYNX_DEBUG, flags.lynxDebugEnabled)
|
|
146
|
+
.putBoolean(KEY_DEVTOOL, flags.devtoolEnabled)
|
|
147
|
+
.putBoolean(KEY_LOGBOX, flags.logboxEnabled)
|
|
148
|
+
.putBoolean(KEY_LONG_PRESS, flags.longPressMenuEnabled)
|
|
149
|
+
.apply()
|
|
150
|
+
if (isDebuggableApp(context) || currentConfig.enableInNonDebuggableApp) {
|
|
151
|
+
applyFlags(flags)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@JvmStatic
|
|
156
|
+
fun getDevUrl(
|
|
157
|
+
context: Context,
|
|
158
|
+
fallback: String,
|
|
159
|
+
): String {
|
|
160
|
+
val stored = prefs(context).getString(KEY_DEV_URL, null)?.trim()
|
|
161
|
+
return if (stored.isNullOrEmpty()) fallback else stored
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@JvmStatic
|
|
165
|
+
fun setDevUrl(
|
|
166
|
+
context: Context,
|
|
167
|
+
url: String,
|
|
168
|
+
) {
|
|
169
|
+
prefs(context)
|
|
170
|
+
.edit()
|
|
171
|
+
.putString(KEY_DEV_URL, url.trim())
|
|
172
|
+
.apply()
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@JvmStatic
|
|
176
|
+
fun isJsConsoleEnabled(): Boolean = currentConfig.enableJsConsole
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Hook the Lynx inspector console delegate so that every `console.*` call from
|
|
180
|
+
* the JS context is captured into the shared [ConsoleLogStore]. The debug
|
|
181
|
+
* tool already wires this up automatically via `KitViewManager` when the
|
|
182
|
+
* debugTag entry is enabled; this entry point is kept for hosts that don't
|
|
183
|
+
* use `KitViewManager`.
|
|
184
|
+
*
|
|
185
|
+
* Returns `true` if the delegate was successfully installed.
|
|
186
|
+
*/
|
|
187
|
+
@JvmStatic
|
|
188
|
+
fun attachConsoleSink(lynxView: LynxView): Boolean = LynxConsoleSink.attach(lynxView)
|
|
189
|
+
|
|
190
|
+
@JvmStatic
|
|
191
|
+
fun detachConsoleSink(lynxView: LynxView) {
|
|
192
|
+
LynxConsoleSink.detach(lynxView)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Open the unified inspector dialog with the given initial tab. */
|
|
196
|
+
@JvmStatic
|
|
197
|
+
@JvmOverloads
|
|
198
|
+
fun openInspectorPanel(
|
|
199
|
+
activity: FragmentActivity,
|
|
200
|
+
initialTab: SparklingInspectorFragment.Tab = SparklingInspectorFragment.Tab.CONSOLE,
|
|
201
|
+
) {
|
|
202
|
+
val fm = activity.supportFragmentManager
|
|
203
|
+
if (fm.findFragmentByTag(SparklingInspectorFragment.FRAGMENT_TAG) != null) return
|
|
204
|
+
SparklingInspectorFragment
|
|
205
|
+
.newInstance(initialTab)
|
|
206
|
+
.show(fm, SparklingInspectorFragment.FRAGMENT_TAG)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Open the inspector with the Console tab pre-selected. */
|
|
210
|
+
@JvmStatic
|
|
211
|
+
fun openConsolePanel(activity: FragmentActivity) {
|
|
212
|
+
openInspectorPanel(activity, SparklingInspectorFragment.Tab.CONSOLE)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Open the inspector with the Sparkling Method invocations tab
|
|
217
|
+
* pre-selected.
|
|
218
|
+
*/
|
|
13
219
|
@JvmStatic
|
|
14
|
-
fun
|
|
220
|
+
fun openMethodInvocationPanel(activity: FragmentActivity) {
|
|
221
|
+
openInspectorPanel(activity, SparklingInspectorFragment.Tab.METHODS)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Open the inspector with the GlobalProps tab pre-selected. */
|
|
225
|
+
@JvmStatic
|
|
226
|
+
fun openGlobalPropsPanel(activity: FragmentActivity) {
|
|
227
|
+
openInspectorPanel(activity, SparklingInspectorFragment.Tab.GLOBAL_PROPS)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Append one method invocation record (already-formatted strings). */
|
|
231
|
+
@JvmStatic
|
|
232
|
+
fun recordMethodInvocation(record: MethodInvocation) {
|
|
233
|
+
MethodInvocationStore.add(record)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Replace an in-flight invocation record (matched by id). */
|
|
237
|
+
@JvmStatic
|
|
238
|
+
fun updateMethodInvocation(record: MethodInvocation) {
|
|
239
|
+
MethodInvocationStore.update(record)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
@JvmStatic
|
|
243
|
+
fun clearMethodInvocations() {
|
|
244
|
+
MethodInvocationStore.clear()
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Override the GlobalProps supplier. The debug tool registers a default
|
|
249
|
+
* provider that walks `KitViewManager` reflectively when `init` is called
|
|
250
|
+
* with `enableFloatingBall = true`; hosts only need to call this if they
|
|
251
|
+
* want to expose a different data source.
|
|
252
|
+
*/
|
|
253
|
+
@JvmStatic
|
|
254
|
+
fun setGlobalPropsProvider(provider: GlobalPropsRegistry.Provider?) {
|
|
255
|
+
GlobalPropsRegistry.setProvider(provider)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Push one externally-collected console line (e.g. from the host
|
|
260
|
+
* `HybridLogger`) into the shared store. The native panel will display it
|
|
261
|
+
* alongside JS console messages captured via [attachConsoleSink].
|
|
262
|
+
*/
|
|
263
|
+
@JvmStatic
|
|
264
|
+
fun recordConsoleLine(
|
|
265
|
+
level: String,
|
|
266
|
+
tag: String,
|
|
267
|
+
message: String,
|
|
268
|
+
) {
|
|
269
|
+
ConsoleLogStore.add(ConsoleLog.fromPlain(level, tag, message))
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Snapshot helper kept for backwards compatibility with previous tooling that
|
|
274
|
+
* rendered the console as a single text dump.
|
|
275
|
+
*/
|
|
276
|
+
@JvmStatic
|
|
277
|
+
fun getConsoleLines(): List<String> = ConsoleLogStore.snapshot().map { "[${it.type}] ${if (it.tag.isEmpty()) "" else it.tag + ": "}${it.message}" }
|
|
278
|
+
|
|
279
|
+
@JvmStatic
|
|
280
|
+
fun clearConsoleLines() {
|
|
281
|
+
ConsoleLogStore.clear()
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
@JvmStatic
|
|
285
|
+
fun showDevUrlDialog(
|
|
286
|
+
activity: Activity,
|
|
287
|
+
initialUrl: String? = null,
|
|
288
|
+
onSaved: ((String) -> Unit)? = null,
|
|
289
|
+
) {
|
|
290
|
+
if (Looper.myLooper() != Looper.getMainLooper()) {
|
|
291
|
+
activity.runOnUiThread { showDevUrlDialog(activity, initialUrl, onSaved) }
|
|
292
|
+
return
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
val input =
|
|
296
|
+
EditText(activity).apply {
|
|
297
|
+
setText(initialUrl ?: "")
|
|
298
|
+
hint = "http://127.0.0.1:5969/main.lynx.bundle"
|
|
299
|
+
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_URI
|
|
300
|
+
setSelection(text.length)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
val dialog =
|
|
304
|
+
AlertDialog
|
|
305
|
+
.Builder(activity)
|
|
306
|
+
.setTitle("Set Sparkling Dev URL")
|
|
307
|
+
.setMessage("Update the main Lynx bundle URL for debug mode.")
|
|
308
|
+
.setView(input)
|
|
309
|
+
.setNegativeButton("Cancel", null)
|
|
310
|
+
.setPositiveButton("Save", null)
|
|
311
|
+
.create()
|
|
312
|
+
|
|
313
|
+
dialog.setOnShowListener {
|
|
314
|
+
val saveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
|
|
315
|
+
saveButton.setOnClickListener {
|
|
316
|
+
val value =
|
|
317
|
+
input.text
|
|
318
|
+
?.toString()
|
|
319
|
+
?.trim()
|
|
320
|
+
.orEmpty()
|
|
321
|
+
if (value.isEmpty()) {
|
|
322
|
+
Toast.makeText(activity, "Dev URL cannot be empty", Toast.LENGTH_SHORT).show()
|
|
323
|
+
return@setOnClickListener
|
|
324
|
+
}
|
|
325
|
+
setDevUrl(activity, value)
|
|
326
|
+
onSaved?.invoke(value)
|
|
327
|
+
dialog.dismiss()
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
dialog.show()
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private fun prefs(context: Context) = context.applicationContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
335
|
+
|
|
336
|
+
private fun isDebuggableApp(context: Context): Boolean = (context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
|
|
337
|
+
|
|
338
|
+
private fun resolveFlags(context: Context): DebugFlags {
|
|
339
|
+
val prefs = prefs(context)
|
|
340
|
+
return DebugFlags(
|
|
341
|
+
lynxDebugEnabled = prefs.getBoolean(KEY_LYNX_DEBUG, true),
|
|
342
|
+
devtoolEnabled = prefs.getBoolean(KEY_DEVTOOL, true),
|
|
343
|
+
logboxEnabled = prefs.getBoolean(KEY_LOGBOX, true),
|
|
344
|
+
longPressMenuEnabled = prefs.getBoolean(KEY_LONG_PRESS, true),
|
|
345
|
+
)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private fun applyFlags(flags: DebugFlags) {
|
|
349
|
+
LynxDevToolService.INSTANCE.setLynxDebugPresetValue(flags.lynxDebugEnabled)
|
|
350
|
+
LynxDevToolService.INSTANCE.setLogBoxPresetValue(flags.logboxEnabled)
|
|
351
|
+
LynxDevToolService.INSTANCE.setLoadQJSBridge(flags.devtoolEnabled)
|
|
352
|
+
|
|
353
|
+
LynxServiceCenter.inst().registerService(LynxLogService)
|
|
15
354
|
LynxServiceCenter.inst().registerService(LynxDevToolService.INSTANCE)
|
|
16
|
-
|
|
17
|
-
LynxEnv.inst().
|
|
18
|
-
LynxEnv.inst().
|
|
19
|
-
|
|
355
|
+
|
|
356
|
+
LynxEnv.inst().enableLynxDebug(flags.lynxDebugEnabled)
|
|
357
|
+
LynxEnv.inst().enableDevtool(flags.devtoolEnabled)
|
|
358
|
+
LynxEnv.inst().enableLogBox(flags.logboxEnabled)
|
|
359
|
+
LynxDevtoolEnv.inst().enableLongPressMenu(flags.longPressMenuEnabled)
|
|
20
360
|
}
|
|
21
361
|
}
|
package/android/src/main/java/com/tiktok/sparkling/debugtool/SparklingDebugToolProviderImpl.kt
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Copyright (c) 2026 TikTok Pte. Ltd.
|
|
2
|
+
// Licensed under the Apache License Version 2.0 that can be found in the
|
|
3
|
+
// LICENSE file in the root directory of this source tree.
|
|
4
|
+
package com.tiktok.sparkling.debugtool
|
|
5
|
+
|
|
6
|
+
import android.net.Uri
|
|
7
|
+
import androidx.fragment.app.FragmentActivity
|
|
8
|
+
import com.lynx.tasm.LynxView
|
|
9
|
+
import com.tiktok.sparkling.debug.SparklingDebugToolProvider
|
|
10
|
+
import com.tiktok.sparkling.debugtool.console.LynxConsoleSink
|
|
11
|
+
import com.tiktok.sparkling.debugtool.inspect.GlobalPropsRegistry
|
|
12
|
+
import com.tiktok.sparkling.debugtool.inspect.GlobalPropsSnapshot
|
|
13
|
+
import com.tiktok.sparkling.debugtool.inspect.MethodInvocation
|
|
14
|
+
import com.tiktok.sparkling.debugtool.inspect.MethodInvocationStore
|
|
15
|
+
import com.tiktok.sparkling.debugtool.inspect.SparklingDebugAutoWiring
|
|
16
|
+
import com.tiktok.sparkling.hybridkit.base.IKitView
|
|
17
|
+
import com.tiktok.sparkling.hybridkit.config.RuntimeInfo
|
|
18
|
+
import com.tiktok.sparkling.method.registry.api.SparklingMethodInvocationCenter
|
|
19
|
+
|
|
20
|
+
class SparklingDebugToolProviderImpl : SparklingDebugToolProvider {
|
|
21
|
+
override fun openInspectorPanel(activity: FragmentActivity) {
|
|
22
|
+
SparklingDebugTool.openInspectorPanel(activity)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
override fun onKitViewCreated(
|
|
26
|
+
containerId: String,
|
|
27
|
+
kitView: IKitView,
|
|
28
|
+
) {
|
|
29
|
+
SparklingDebugAutoWiring.attachConsoleWithRetry(kitView.realView())
|
|
30
|
+
GlobalPropsRegistry.notifyChanged()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
override fun onKitViewDestroyed(
|
|
34
|
+
containerId: String,
|
|
35
|
+
kitView: IKitView?,
|
|
36
|
+
) {
|
|
37
|
+
(kitView?.realView() as? LynxView)?.let { LynxConsoleSink.detach(it) }
|
|
38
|
+
GlobalPropsRegistry.notifyChanged()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
override fun onMethodInvocationStart(event: SparklingMethodInvocationCenter.Event) {
|
|
42
|
+
MethodInvocationStore.add(event.toMethodInvocation(isEnd = false))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
override fun onMethodInvocationEnd(event: SparklingMethodInvocationCenter.Event) {
|
|
46
|
+
MethodInvocationStore.update(event.toMethodInvocation(isEnd = true))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
override fun setGlobalPropsCollector(collector: () -> Map<String, IKitView>) {
|
|
50
|
+
GlobalPropsRegistry.setProvider {
|
|
51
|
+
collector().map { (containerId, kitView) ->
|
|
52
|
+
val raw = kitView.getGlobalProps().orEmpty()
|
|
53
|
+
val templateUrl = kitView.getScheme()
|
|
54
|
+
|
|
55
|
+
@Suppress("UNCHECKED_CAST")
|
|
56
|
+
val queryItems =
|
|
57
|
+
(raw[RuntimeInfo.QUERY_ITEMS] as? Map<String, Any?>)
|
|
58
|
+
?.takeIf { it.isNotEmpty() }
|
|
59
|
+
?: parseQueryItems(templateUrl)
|
|
60
|
+
GlobalPropsSnapshot(
|
|
61
|
+
containerId = containerId,
|
|
62
|
+
templateUrl = templateUrl,
|
|
63
|
+
globalProps = raw.filterKeys { it != RuntimeInfo.QUERY_ITEMS },
|
|
64
|
+
queryItems = queryItems,
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private fun SparklingMethodInvocationCenter.Event.toMethodInvocation(isEnd: Boolean): MethodInvocation {
|
|
71
|
+
val code = code
|
|
72
|
+
return MethodInvocation(
|
|
73
|
+
id = id,
|
|
74
|
+
name = name,
|
|
75
|
+
namespace = namespace,
|
|
76
|
+
platform = platform.toString(),
|
|
77
|
+
params = MethodInvocation.formatPayload(params),
|
|
78
|
+
result = if (isEnd) MethodInvocation.formatPayload(result) else null,
|
|
79
|
+
code = code,
|
|
80
|
+
success = if (isEnd) code == null || code == 1 else null,
|
|
81
|
+
startTimeMs = startTimeMs,
|
|
82
|
+
endTimeMs = endTimeMs,
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private fun parseQueryItems(url: String?): Map<String, Any?> {
|
|
87
|
+
if (url.isNullOrBlank()) return emptyMap()
|
|
88
|
+
return runCatching {
|
|
89
|
+
val uri = Uri.parse(url)
|
|
90
|
+
uri.queryParameterNames.associateWith { uri.getQueryParameter(it) }
|
|
91
|
+
}.getOrDefault(emptyMap())
|
|
92
|
+
}
|
|
93
|
+
}
|