integrations
Client-agnostic by design.
One line per callback.
HTTP capture is automatic with the Ktor plugin. For everything else, Sharingan deliberately ships no client adapters — your MQTT/BLE library already has callbacks, and one logger call inside each is the whole integration. No version coupling, no transitive dependencies.
Ktor — automatic HTTP capture
val client = HttpClient {
install(SharinganKtor)
}
Tunable when you need it:
install(SharinganKtor) {
captureBodies = true // default
maxBodyBytes = 64 * 1024 // default — longer bodies truncate with a marker
redactedHeaders = setOf("Authorization", "X-Api-Key") // additive to defaults
}
- Default redactions:
Authorization,Proxy-Authorization,Cookie,Set-Cookie— masked to••••before the event is stored. - Bodies are captured only for textual content types (JSON, XML, text, form-urlencoded); streaming and binary responses are never read.
- Timing records two phases — TTFB and Download — rendered as a waterfall in the detail view.
OkHttp / NSURLSession — manual logging
Using OkHttp or NSURLSession directly today? Log through the public
HttpLogger — same event model, same UI, same exports:
Sharingan.http.log(
method = "GET",
url = "https://api.example.dev/v1/devices",
statusCode = 200,
durationMillis = 214,
requestHeaders = listOf("Accept" to "application/json"),
responseBody = bodyText, // JSON renders pretty-printed
timing = listOf(TimingPhase("TTFB", 180), TimingPhase("Download", 34)),
)
sharingan-okhttp — is designed and on the roadmap: application-interceptor
capture with the same config vocabulary as the Ktor plugin. Until it ships, manual
logging above is the supported path.MQTT — clients with callbacks (KMQTT, HiveMQ, Paho…)
client.subscribe(filter, qos) { /* granted */ Sharingan.mqtt.subscribed(filter, qos) }
client.onMessage { msg ->
Sharingan.mqtt.received(msg.topic, msg.payload.decodeToString(), msg.qos)
}
client.publish(topic, bytes, qos, retained).also {
Sharingan.mqtt.publish(topic, bytes.decodeToString(), qos, retained)
}
BLE — Kable
val peripheral = scope.peripheral(advertisement)
peripheral.state.onEach { state ->
when (state) {
is State.Connected -> Sharingan.ble.connect(peripheral.nameOrId())
is State.Disconnected -> Sharingan.ble.disconnect(peripheral.nameOrId(), state.status?.toString())
else -> Unit
}
}.launchIn(scope)
peripheral.observe(hrCharacteristic).onEach { bytes ->
Sharingan.ble.notify(
device = peripheral.nameOrId(),
characteristic = "Heart Rate Measurement",
uuid = hrCharacteristic.characteristicUuid.toString(),
value = """{"bpm":${bytes.toHeartRate()}}""",
)
}.launchIn(scope)
private fun Peripheral.nameOrId() = name ?: identifier.toString()
Errors get a first-class call too:
Sharingan.ble.error(device, message, characteristic, uuid) — GATT status
codes land on the failure rail like any HTTP 500.
iOS — SwiftUI wrapper
import SwiftUI
import shared // your shared framework
// No-wrapper alternative — presents over topmost VC, any thread:
SharinganViewControllerKt.presentSharingan(animated: true)
// Or embed the view controller yourself:
struct SharinganView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
SharinganViewControllerKt.SharinganViewController()
}
func updateUIViewController(_ vc: UIViewController, context: Context) {}
}
// usage
.sheet(isPresented: $showLogs) { SharinganView() }
Extras
Shake-to-open (Android)
// on shake trigger:
Sharingan.show(context)
iOS Live Activity analog (app-side)
iOS doesn't let a library ship a sticky notification. If you want a lock-screen
capture card, add an ActivityKit widget extension in your app and drive it from
Sharingan.events (via SKIE / KMP-NativeCoroutines) — counters plus the last
event line. This stays app-side by design: widget extensions can't ship from a Kotlin
library.