Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ANDR][Replay] Flatten and de-spaghettize Session Replay logic #118

Merged
merged 13 commits into from
Nov 12, 2024
7 changes: 3 additions & 4 deletions examples/android/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import io.bitdrift.capture.LogLevel
import io.bitdrift.capture.common.ErrorHandler
import io.bitdrift.capture.network.okhttp.CaptureOkHttpEventListenerFactory
import io.bitdrift.capture.replay.ReplayLogger
import io.bitdrift.capture.replay.ReplayModule
import io.bitdrift.capture.replay.ReplayPreviewClient
import io.bitdrift.capture.replay.SessionReplayConfiguration
import io.bitdrift.capture.replay.internal.EncodedScreenMetrics
Expand All @@ -52,7 +51,7 @@ import kotlin.time.toDuration
class MainActivity : ComponentActivity() {

private val replayPreviewClient: ReplayPreviewClient by lazy {
ReplayPreviewClient(ReplayModule(
ReplayPreviewClient(
object: ErrorHandler {
override fun handleError(detail: String, e: Throwable?) {
Log.e("HelloWorldApp", "Replay handleError: $detail $e")
Expand Down Expand Up @@ -81,9 +80,9 @@ class MainActivity : ComponentActivity() {
Log.e("HelloWorldApp", message, e)
}
},
this.applicationContext,
SessionReplayConfiguration(),
this.applicationContext
))
)
}
private lateinit var clipboardManager: ClipboardManager
private lateinit var client: OkHttpClient
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ import io.bitdrift.capture.common.ErrorHandler
import io.bitdrift.capture.common.MainThreadHandler
import io.bitdrift.capture.common.Runtime
import io.bitdrift.capture.common.RuntimeFeature
import io.bitdrift.capture.providers.FieldValue
import io.bitdrift.capture.providers.toFieldValue
import io.bitdrift.capture.providers.toFields
import io.bitdrift.capture.replay.ReplayCaptureController
import io.bitdrift.capture.replay.ReplayLogger
import io.bitdrift.capture.replay.ReplayModule
import io.bitdrift.capture.replay.SessionReplayConfiguration
import io.bitdrift.capture.replay.internal.EncodedScreenMetrics
import io.bitdrift.capture.replay.internal.FilteredCapture
Expand All @@ -34,10 +33,10 @@ internal class SessionReplayTarget(
mainThreadHandler: MainThreadHandler = MainThreadHandler(),
) : ISessionReplayTarget, ReplayLogger {
// TODO(Augustyniak): Make non nullable and pass at initialization time after
// `sessionReplayTarget` argument is moved from logger creation time to logger start time.
// Refer to TODO in `LoggerImpl` for more details.
// `sessionReplayTarget` argument is moved from logger creation time to logger start time.
// Refer to TODO in `LoggerImpl` for more details.
internal var runtime: Runtime? = null
private val replayModule: ReplayModule = ReplayModule(
private val replayCaptureController: ReplayCaptureController = ReplayCaptureController(
errorHandler,
this,
configuration,
Expand All @@ -50,17 +49,17 @@ internal class SessionReplayTarget(
runtime?.isEnabled(RuntimeFeature.SESSION_REPLAY_COMPOSE)
?: RuntimeFeature.SESSION_REPLAY_COMPOSE.defaultValue
)
replayModule.captureScreen(skipReplayComposeViews)
replayCaptureController.captureScreen(skipReplayComposeViews)
}

override fun captureScreenshot() {
// TODO(Augustyniak): Implement this method to add support for screenshot capture on Android.
// As currently implemented, the function must emit a session replay screenshot log.
// Without this emission, the SDK is blocked from requesting additional screenshots.
// As currently implemented, the function must emit a session replay screenshot log.
// Without this emission, the SDK is blocked from requesting additional screenshots.
}

override fun onScreenCaptured(encodedScreen: ByteArray, screen: FilteredCapture, metrics: EncodedScreenMetrics) {
val fields = buildMap<String, FieldValue> {
val fields = buildMap {
put("screen", encodedScreen.toFieldValue())
putAll(metrics.toMap().toFields())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ class SessionReplayTargetTest {
private val handler: MainThreadHandler = Mocks.sameThreadHandler

private val target = SessionReplayTarget(
errorHandler = errorHandler,
context = ApplicationProvider.getApplicationContext(),
logger = logger,
configuration = SessionReplayConfiguration(),
mainThreadHandler = handler,
SessionReplayConfiguration(),
errorHandler,
ApplicationProvider.getApplicationContext(),
logger,
handler,
)

init {
Expand All @@ -46,7 +46,7 @@ class SessionReplayTargetTest {
fun sessionReplayTargetEmitsScreenLog() {
target.captureScreen()
// TODO: Make this test work, the issue is that in test environment session replay
// sees 0 views and as a result it doesn't emit a session replay screen log.
// sees 0 views and as a result it doesn't emit a session replay screen log.
// verify(logger, timeout(1000).times(1)).logSessionReplayScreen(any(), any())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,7 @@ import android.content.Context
import android.util.Base64
import android.util.Log
import io.bitdrift.capture.common.ErrorHandler
import io.bitdrift.capture.common.MainThreadHandler
import io.bitdrift.capture.common.Runtime
import io.bitdrift.capture.common.RuntimeFeature
import io.bitdrift.capture.replay.ReplayLogger
import io.bitdrift.capture.replay.ReplayModule
import io.bitdrift.capture.replay.ReplayPreviewClient
import io.bitdrift.capture.replay.SessionReplayConfiguration
import io.bitdrift.capture.replay.internal.EncodedScreenMetrics
Expand All @@ -31,51 +27,47 @@ object TestUtils {
context: Context
): ReplayPreviewClient {
return ReplayPreviewClient(
ReplayModule(
object : ErrorHandler {
override fun handleError(detail: String, e: Throwable?) {
Log.e("Replay Tests", "error: $detail $e")
}
},
object : ReplayLogger {
override fun onScreenCaptured(
encodedScreen: ByteArray,
screen: FilteredCapture,
metrics: EncodedScreenMetrics
) {
Log.d("Replay Tests", "took ${metrics.captureTimeMs}ms")
Log.d("Replay Tests", "Captured a total of ${screen.size} ReplayRect views.")
Log.d("Replay Tests", screen.toString())
Log.d(
"Replay Tests",
"echo \"${
Base64.encodeToString(
encodedScreen,
0
)
}\" | websocat ws://localhost:3001 --base64 -bv -1"
)
object : ErrorHandler {
override fun handleError(detail: String, e: Throwable?) {
Log.e("Replay Tests", "error: $detail $e")
}
},
object : ReplayLogger {
override fun onScreenCaptured(
encodedScreen: ByteArray,
screen: FilteredCapture,
metrics: EncodedScreenMetrics
) {
Log.d("Replay Tests", "took ${metrics.captureTimeMs}ms")
Log.d("Replay Tests", "Captured a total of ${screen.size} ReplayRect views.")
Log.d("Replay Tests", screen.toString())
Log.d(
"Replay Tests",
"echo \"${
Base64.encodeToString(
encodedScreen,
0
)
}\" | websocat ws://localhost:3001 --base64 -bv -1"
)

replay.set(Pair(screen, metrics))
latch.countDown()
}
replay.set(Pair(screen, metrics))
latch.countDown()
}

override fun logVerboseInternal(message: String, fields: Map<String, String>?) {
Log.v("Replay Tests", message)
}
override fun logVerboseInternal(message: String, fields: Map<String, String>?) {
Log.v("Replay Tests", message)
}

override fun logDebugInternal(message: String, fields: Map<String, String>?) {
Log.d("Replay Tests", message)
}
override fun logDebugInternal(message: String, fields: Map<String, String>?) {
Log.d("Replay Tests", message)
}

override fun logErrorInternal(message: String, e: Throwable?, fields: Map<String, String>?) {
Log.e("Replay Tests", message, e)
}
},
SessionReplayConfiguration(),
context,
MainThreadHandler(),
),
override fun logErrorInternal(message: String, e: Throwable?, fields: Map<String, String>?) {
Log.e("Replay Tests", message, e)
}
},
context,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// capture-sdk - bitdrift's client SDK
// Copyright Bitdrift, Inc. All rights reserved.
//
// Use of this source code is governed by a source available license that can be found in the
// LICENSE file or at:
// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt

package io.bitdrift.capture.replay

import android.content.Context
import io.bitdrift.capture.common.ErrorHandler
import io.bitdrift.capture.common.MainThreadHandler
import io.bitdrift.capture.replay.internal.ReplayCaptureEngine

/**
* Sets up and controls the replay feature
* @param errorHandler allows to report internal errors to our backend
* @param replayLogger the callback to use to report replay captured screens
* @param sessionReplayConfiguration the configuration to use
* @param runtime allows for the feature to be remotely disabled
*/
class ReplayCaptureController(
errorHandler: ErrorHandler,
logger: ReplayLogger,
sessionReplayConfiguration: SessionReplayConfiguration,
context: Context,
mainThreadHandler: MainThreadHandler,
) {
private val replayCaptureEngine: ReplayCaptureEngine

init {
L.logger = logger
replayCaptureEngine = ReplayCaptureEngine(
sessionReplayConfiguration,
errorHandler,
context,
logger,
mainThreadHandler,
)
}

/**
* Prepares and emits a session replay screen log using a logger instance passed
* at initialization time.
*/
fun captureScreen(skipReplayComposeViews: Boolean) {
replayCaptureEngine.captureScreen(skipReplayComposeViews)
}

internal object L {
internal var logger: ReplayLogger? = null

fun v(message: String) {
logger?.logVerboseInternal(message)
}

fun d(message: String) {
logger?.logDebugInternal(message)
}

fun e(e: Throwable?, message: String) {
logger?.logErrorInternal(message, e)
}
}
}

This file was deleted.

Loading
Loading