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.
Files changed (65) hide show
  1. package/android/build.gradle.kts +53 -19
  2. package/android/src/main/java/com/tiktok/sparkling/debugtool/SparklingDebugTool.kt +345 -5
  3. package/android/src/main/java/com/tiktok/sparkling/debugtool/SparklingDebugToolProviderImpl.kt +93 -0
  4. package/android/src/main/java/com/tiktok/sparkling/debugtool/console/ConsoleLog.kt +128 -0
  5. package/android/src/main/java/com/tiktok/sparkling/debugtool/console/ConsoleLogAdapter.kt +133 -0
  6. package/android/src/main/java/com/tiktok/sparkling/debugtool/console/ConsoleLogStore.kt +81 -0
  7. package/android/src/main/java/com/tiktok/sparkling/debugtool/console/LynxConsoleLogDelegate.kt +119 -0
  8. package/android/src/main/java/com/tiktok/sparkling/debugtool/console/LynxConsoleLogcatBridge.kt +107 -0
  9. package/android/src/main/java/com/tiktok/sparkling/debugtool/console/LynxConsoleSink.kt +66 -0
  10. package/android/src/main/java/com/tiktok/sparkling/debugtool/floating/SparklingFloatingBallManager.kt +43 -0
  11. package/android/src/main/java/com/tiktok/sparkling/debugtool/inspect/GlobalPropsRegistry.kt +59 -0
  12. package/android/src/main/java/com/tiktok/sparkling/debugtool/inspect/MethodInvocation.kt +52 -0
  13. package/android/src/main/java/com/tiktok/sparkling/debugtool/inspect/MethodInvocationStore.kt +107 -0
  14. package/android/src/main/java/com/tiktok/sparkling/debugtool/inspect/SparklingDebugAutoWiring.kt +44 -0
  15. package/android/src/main/java/com/tiktok/sparkling/debugtool/inspector/SearchInputSupport.kt +77 -0
  16. package/android/src/main/java/com/tiktok/sparkling/debugtool/inspector/SparklingConsolePanelView.kt +130 -0
  17. package/android/src/main/java/com/tiktok/sparkling/debugtool/inspector/SparklingGlobalPropsPanelView.kt +319 -0
  18. package/android/src/main/java/com/tiktok/sparkling/debugtool/inspector/SparklingInspectorFragment.kt +155 -0
  19. package/android/src/main/java/com/tiktok/sparkling/debugtool/inspector/SparklingMethodPanelView.kt +225 -0
  20. package/android/src/main/java/com/tiktok/sparkling/debugtool/ui/BottomSheetSupport.kt +43 -0
  21. package/android/src/main/res/color/sparkling_chip_toggle_text.xml +6 -0
  22. package/android/src/main/res/color/sparkling_inspector_tab_text.xml +5 -0
  23. package/android/src/main/res/color/sparkling_log_level_tab_text.xml +5 -0
  24. package/android/src/main/res/drawable/sparkling_chip_bg.xml +6 -0
  25. package/android/src/main/res/drawable/sparkling_chip_toggle_bg.xml +23 -0
  26. package/android/src/main/res/drawable/sparkling_flat_tab_bg.xml +25 -0
  27. package/android/src/main/res/drawable/sparkling_floating_ball_bg.xml +8 -0
  28. package/android/src/main/res/drawable/sparkling_floating_logo.png +0 -0
  29. package/android/src/main/res/drawable/sparkling_ic_close.xml +11 -0
  30. package/android/src/main/res/drawable/sparkling_ic_copy.xml +14 -0
  31. package/android/src/main/res/drawable/sparkling_inspector_close_bg.xml +9 -0
  32. package/android/src/main/res/drawable/sparkling_inspector_tab_bg.xml +16 -0
  33. package/android/src/main/res/drawable/sparkling_log_level_tab_bg.xml +24 -0
  34. package/android/src/main/res/drawable-night/sparkling_chip_bg.xml +5 -0
  35. package/android/src/main/res/drawable-night/sparkling_flat_tab_bg.xml +24 -0
  36. package/android/src/main/res/drawable-night/sparkling_log_level_tab_bg.xml +23 -0
  37. package/android/src/main/res/layout/fragment_sparkling_inspector.xml +110 -0
  38. package/android/src/main/res/layout/item_sparkling_console.xml +34 -0
  39. package/android/src/main/res/layout/item_sparkling_global_props_kv.xml +27 -0
  40. package/android/src/main/res/layout/item_sparkling_global_props_section.xml +25 -0
  41. package/android/src/main/res/layout/item_sparkling_method_invocation.xml +74 -0
  42. package/android/src/main/res/layout/view_sparkling_console_panel.xml +141 -0
  43. package/android/src/main/res/layout/view_sparkling_global_props_panel.xml +70 -0
  44. package/android/src/main/res/layout/view_sparkling_method_panel.xml +68 -0
  45. package/android/src/main/res/values/colors.xml +25 -0
  46. package/android/src/main/res/values-night/colors.xml +19 -0
  47. package/android/src/main/resources/META-INF/services/com.tiktok.sparkling.debug.SparklingDebugToolProvider +1 -0
  48. package/ios/Resources/sparkling_floating_logo.png +0 -0
  49. package/ios/Resources/sparkling_floating_logo@2x.png +0 -0
  50. package/ios/Resources/sparkling_floating_logo@3x.png +0 -0
  51. package/ios/Sources/Console/ConsoleLog.swift +139 -0
  52. package/ios/Sources/Console/ConsoleLogStore.swift +67 -0
  53. package/ios/Sources/Console/LynxConsoleSink.swift +50 -0
  54. package/ios/Sources/Console/SparklingConsoleViewController.swift +459 -0
  55. package/ios/Sources/Floating/SparklingFloatingBallManager.swift +179 -0
  56. package/ios/Sources/Inspect/GlobalPropsRegistry.swift +54 -0
  57. package/ios/Sources/Inspect/MethodInvocation.swift +127 -0
  58. package/ios/Sources/Inspect/SparklingDebugAutoWiring.swift +231 -0
  59. package/ios/Sources/Inspect/SparklingGlobalPropsViewController.swift +387 -0
  60. package/ios/Sources/Inspect/SparklingMethodInvocationViewController.swift +386 -0
  61. package/ios/Sources/Inspector/SparklingInspectorViewController.swift +296 -0
  62. package/ios/Sources/SparklingDebugTool.swift +173 -3
  63. package/ios/Sparkling-DebugTool.podspec +15 -11
  64. package/module.config.json +1 -0
  65. package/package.json +1 -1
@@ -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 = (findProperty("SPARKLING_PUBLISHING_GROUP_ID") as? String)
47
- ?: System.getenv("SPARKLING_PUBLISHING_GROUP_ID")
48
- ?: "com.tiktok.sparkling"
49
- val publishingVersion = (findProperty("SPARKLING_PUBLISHING_VERSION") as? String)
50
- ?: System.getenv("SPARKLING_PUBLISHING_VERSION")
51
- ?: "2.0.0"
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(android.sourceSets.getByName("main").java.srcDirs)
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 = (findProperty("mavenCentralRepoUrl") as? String)
107
- ?: System.getenv("MAVEN_CENTRAL_REPO_URL")
108
- ?: "https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/"
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 = (findProperty("signing.keyId") as? String)
125
- ?: System.getenv("SIGNING_KEY_ID")
126
- val signingPassword = (findProperty("signing.password") as? String)
127
- ?: System.getenv("SIGNING_PASSWORD")
128
- val signingSecretKeyRingFile = (findProperty("signing.secretKeyRingFile") as? String)
129
- ?: System.getenv("SIGNING_SECRET_KEY_RING_FILE")
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 = !(System.getenv("SIGNING_KEY_ID").isNullOrBlank() ||
150
- System.getenv("SIGNING_PASSWORD").isNullOrBlank())
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 init(application: Application) {
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
- LynxEnv.inst().enableLynxDebug(true)
17
- LynxEnv.inst().enableDevtool(true)
18
- LynxEnv.inst().enableLogBox(true)
19
- LynxDevtoolEnv.inst().enableLongPressMenu(true)
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
  }
@@ -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
+ }