Inspektify

repository·main·Indexed 42 days ago

https://github.com/bvantur/inspektify

A Kotlin Multiplatform library for iOS, Android, and Desktop that enables real-time network traffic observation directly on the device. It integrates as a plugin for Ktor-based networking, supporting Ktor 2.3.1 to 3.x.x. Features include customizable data retention policies, auto-detection triggers (shake gestures or shortcuts), and a plugin system for registering custom debug tools via InspektifyExtension.

Tokens
6.9K
Snippets
30
Records
34
Agent score
53%

What's inside Inspektify

  1. How to use custom navigation inside an extension

    main

    Since the content property of an InspektifyExtension is a standard @Composable slot, you can host your own navigation library (such as Jetpack Navigation, Voyager, or Decompose) inside the extension detail screen. This is the recommended approach for extensions requiring multiple screens.

    Limitations:

    • Inspektify controls the detail screen's top bar (title and back action).
    • The system back action is hardcoded to navigate back to Inspektify's outer navigation.
    • Customizing the top bar per subscreen or coordinating the system back action with an inner navigator is not currently supported.
    InspektifyExtensions.register(
        InspektifyExtension(
            id = "feature-flags",
            title = "Feature flags",
            content = {
                val navController = rememberNavController()
                NavHost(navController = navController, startDestination = "list") {
                    composable("list") { FlagListScreen(onOpenDetail = { navController.navigate("detail/$it") }) }
                    composable("detail/{flag}") { FlagDetailScreen(/* ... */) }
                }
            }
        )
    )
  2. Accumulate tags across different layers

    main

    Tags are additive. You can define broad tags at a global level (e.g., in a Ktor defaultRequest block) and then add more specific tags at individual call sites.

    When tags are accumulated:

    • Repeated calls add to the existing tag list.
    • Blank tags are automatically dropped.
    • Duplicate tags are collapsed, while preserving the original order of addition.

    Example: A global tag api-v2 combined with a call-site tag SearchProducts results in a request tagged with both "api-v2" and "SearchProducts".

    HttpClient {
        defaultRequest { inspektifyTags("api-v2") }
        install(InspektifyKtor)
    }
    
    // ...
    
    client.get("products") { inspektifyTags("SearchProducts") } // tagged "api-v2" and "SearchProducts"
  3. Redact specific HTTP headers in Inspektify

    main

    To prevent sensitive information in HTTP headers from being exposed during development, you can specify a list of header names to be redacted. Inspektify will replace the values of these headers with *** REDACTED *** in its recordings. Use the redactHeaders property within the install(InspektifyKtor) configuration block.

    install(InspektifyKtor) {
        redactHeaders = listOf("x-api-key", "Auth-Token")
    }
  4. Centralize tagging in API helpers

    main

    To avoid repetitive code, instead of calling inspektifyTags at every single call site, you can call it once inside a centralized API helper function. This ensures every request made through that helper is automatically labeled.

    Important Note on Release Builds: If you are excluding Inspektify from release builds (using debug-only configurations), remember that inspektifyTags is part of the Inspektify artifact. If a call site containing inspektifyTags is included in your release classpath, the app will fail to compile or run. Ensure that any code calling inspektifyTags is wrapped in the same source set or conditional flag used to install the plugin.

    suspend fun callApi(operation: String, body: String): HttpResponse =
        client.post("https://www.example.com/graphql") {
            inspektifyTags(operation)
            contentType(ContentType.Application.Json)
            setBody(body)
        }
  5. Use No-op variant for Android Release builds

    main

    To prevent Inspektify from running in production, use the matching -no-op variant for your release build type. This allows you to swap the dependency at build time without changing any Kotlin code. The no-op variant exposes the same API but performs no operations at runtime.

    // Ktor 3.x.x
    dependencies {
        debugImplementation("io.github.bvantur:inspektify-ktor3:{mavenVersion}")
        releaseImplementation("io.github.bvantur:inspektify-ktor3-no-op:{mavenVersion}")
    }
    
    // Ktor 2.3.1 - 3.0.0
    dependencies {
        debugImplementation("io.github.bvantur:inspektify-ktor2:{mavenVersion}")
        releaseImplementation("io.github.bvantur:inspektify-ktor2-no-op:{mavenVersion}")
    }
  6. Configure Inspektify Ktor logging levels

    main

    By default, logging of network communication in-app output is disabled (LogLevel.None). You can enable and configure the verbosity of network logs when installing the InspektifyKtor plugin into your Ktor client using the logLevel property.

    Available LogLevel options:

    • LogLevel.None: No logging (default).
    • LogLevel.Info: Logs base transaction information: URL, HTTP method, and response code.
    • LogLevel.Headers: Logs everything in LogLevel.Info plus all request and response headers.
    • LogLevel.Body: Logs everything in LogLevel.Info plus the full request and response payloads.
    • LogLevel.All: Logs everything (Info, Headers, and Body) for the network transaction.
    install(InspektifyKtor) {
        logLevel = LogLevel.All
    }
  7. Enable Inspektify shortcuts for mobile clients

    main

    By default, Inspektify uses a shake gesture to present the debug tool window. If your project uses third-party libraries that also detect shakes, you should enable the shortcut capability.

    On Android, enabling this flag makes the shortcut work immediately. On iOS, enabling this flag requires additional configuration in your Gradle files and your iOS native project to function.

    install(InspektifyKtor) {
        shortcutEnabled = true
    }
  8. Implement iOS Quick Action in AppDelegate

    main

    To enable the Inspektify Quick Action on iOS, you must override the application(_:configurationForConnecting:options:) method in your AppDelegate.swift.

    If you do not have an AppDelegate class, create an AppDelegate.swift file in your root folder with the following content:

    import UIKit
    
    class AppDelegate: NSObject, UIApplicationDelegate {
    }

    Then, import your shared module (e.g., import ComposeApp) and implement the method using InspektifyShortcutHandlerKt.getInspektifyUISceneConfiguration.

    import UIKit
    import ComposeApp // Replace with your actual shared module name
    
    // Inside your AppDelegate class:
    func application(
        _ application: UIApplication,
        configurationForConnecting connectingSceneSession: UISceneSession,
        options: UIScene.ConnectionOptions
    ) -> UISceneConfiguration {
        return InspektifyShortcutHandlerKt.getInspektifyUISceneConfiguration(configurationForConnectingSceneSession: connectingSceneSession)
    }
  9. Register an Inspektify extension

    main

    You can add custom debug tools (like design system previews, feature flags, or cache inspectors) to the Inspektify tool window by registering an InspektifyExtension.

    Extensions should be registered from the UI layer, typically within your top-level @Composable function (e.g., App()), wrapped in a remember { ... } block to ensure registration happens exactly once. Registering in the UI layer allows your extension's content to easily access your app's existing themes, resources, and dependencies.

    When at least one extension is registered, an Extensions tab appears in the Inspektify tool window. If no extensions are registered, this tab is hidden.

    @Composable
    fun App() {
        remember {
            InspektifyExtensions.register(
                InspektifyExtension(
                    id = "design-system",
                    title = "Design System",
                    icon = InspektifyIcon.Vector(Icons.Filled.Palette),
                    order = 0,
                    content = { DesignSystemShowcase() }
                )
            )
        }
    
        // ... rest of your app UI
    }
  10. Exclude Inspektify from Android release builds using no-op variants

    main

    To ensure Inspektify is not accessible to production users in Android projects, use the provided no-op artifacts. Each Inspektify artifact has a matching -no-op variant that exposes the exact same API but performs no runtime actions (no network interception, no database, no UI).

    By swapping the dependency based on the build type, your install(InspektifyKtor) calls and extension registrations do not require any conditional logic in your source code.

    // Ktor 3.x.x
    dependencies {
        debugImplementation("io.github.bvantur:inspektify-ktor3:{mavenVersion}")
        releaseImplementation("io.github.bvantur:inspektify-ktor3-no-op:{mavenVersion}")
    }
    
    // Ktor 2.3.1 - 3.0.0
    dependencies {
        debugImplementation("io.github.bvantur:inspektify-ktor2:{mavenVersion}")
        releaseImplementation("io.github.bvantur:inspektify-ktor2-no-op:{mavenVersion}")
    }
  11. Setup API key for the Inspektify Sample Project

    main

    The sample project uses reqres.in for network requests. To run the sample, you must provide a reqres.api.key in a secrets.properties file:

    1. Generate a key at https://app.reqres.in/api-keys.
    2. Copy the example file: cp secrets.properties.example secrets.properties.
    3. Edit secrets.properties and add your key: reqres.api.key=your_reqres_api_key_here
    cp secrets.properties.example secrets.properties
  12. Install Inspektify via Gradle

    main

    Inspektify is available on Maven Central. Ensure mavenCentral() is in your repositories block. The dependency you add depends on your Ktor version:

    • For Ktor 3.x.x: Use inspektify-ktor3.
    • For Ktor 2.3.1 to 3.0.0: Use inspektify-ktor2.

    Dependencies should be added to the commonMain.dependencies block in a Kotlin Multiplatform project.

    // Ktor 3.x.x
    commonMain.dependencies {
        implementation("io.github.bvantur:inspektify-ktor3:{mavenVersion}")
    }
    
    // Ktor 2.3.1 - 3.0.0
    commonMain.dependencies {
        implementation("io.github.bvantur:inspektify-ktor2:{mavenVersion}")
    }