Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import UIKit

private enum TouchConstants {
static let tapMaxDistance = 12.0
static let tapMaxDistance = 4.0
static let tapMaxDistanceSquared: CGFloat = tapMaxDistance * tapMaxDistance
static let touchMoveMaxDuration: TimeInterval = 0.11
static let touchMoveThrottle: TimeInterval = 0.05 // From RRWeb code
static let touchPathDuration: TimeInterval = 0.18 // found through testing
}

final class TouchIntepreter {
Expand Down Expand Up @@ -47,26 +48,41 @@ final class TouchIntepreter {
track.end = touchSample.timestamp
track.target = touchSample.target

let trackDuration = track.end - track.start
guard trackDuration <= TouchConstants.touchPathDuration else {
// flush movements of long touch path do not have dead time in the replay player
let lastPoint = TouchPoint(position: touchSample.location, timestamp: touchSample.timestamp + uptimeDifference)
track.points.append(lastPoint)

let moveInteraction = TouchInteraction(id: incrementingId,
kind: .touchPath(points: track.points),
startTimestamp: track.start + uptimeDifference,
timestamp: touchSample.timestamp + uptimeDifference,
target: touchSample.target)
track.points.removeAll()
track.start = lastPoint.timestamp - uptimeDifference
track.startPoint = touchSample.location
tracks[touchSample.id] = track
yield(moveInteraction)
return
}

let previousTimestamp = (track.points.last?.timestamp ?? track.start)
let duration = touchSample.timestamp + uptimeDifference - previousTimestamp
guard duration >= TouchConstants.touchMoveMaxDuration else {
guard duration >= TouchConstants.touchMoveThrottle else {
return
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Inconsistent Throttle Timing Post-Flush

After flushing touch points when track duration exceeds touchPathDuration, the throttle check is skipped for subsequent points because track.points is empty. The previous implementation used track.start as a fallback timestamp when no points existed, ensuring consistent throttling. Without this fallback, points immediately following a flush bypass time-based throttling and rely only on distance checks, potentially capturing points more frequently than touchMoveThrottle allows.

Fix in Cursor Fix in Web

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want that more frequently


let distance = squaredDistance(from: track.startPoint, to: touchSample.location)
guard distance >= TouchConstants.tapMaxDistanceSquared else {
return
if let lastPoint = tracks[touchSample.id]?.points.last {
let distance = squaredDistance(from: lastPoint.position, to: touchSample.location)
guard distance >= TouchConstants.tapMaxDistanceSquared else {
return
}
}

track.points.append(TouchPoint(position: touchSample.location, timestamp: touchSample.timestamp + uptimeDifference))
tracks[touchSample.id] = track

let trackDuration = track.end - track.start
if trackDuration > 0.9 {
// flush movements of long touch path do not have dead time in the replay player
flushMovements(touchSample: touchSample, uptimeDifference: uptimeDifference, startTimestamp: track.start, yield: yield)
}

case .ended, .cancelled:
// touchUp
let startTimestamp = tracks[touchSample.id]?.start ?? touchSample.timestamp
Expand Down Expand Up @@ -97,13 +113,13 @@ final class TouchIntepreter {
startTimestamp: startTimestamp + uptimeDifference,
timestamp: touchSample.timestamp + uptimeDifference,
target: touchSample.target)
track.points.removeAll()
tracks[touchSample.id] = track
yield(moveInteraction)
}

func flushTrack(touchSample: TouchSample, uptimeDifference: TimeInterval, yield: TouchInteractionYield) {

if let lastPoint = track.points.last {
print("touchPath.points: \(track.points.count)", touchSample.id)
track.points.removeAll()
track.start = lastPoint.timestamp - uptimeDifference
tracks[touchSample.id] = track
yield(moveInteraction)
}
}

func squaredDistance(from: CGPoint, to: CGPoint) -> CGFloat {
Expand Down
34 changes: 17 additions & 17 deletions TestApp/Sources/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,27 @@ import LaunchDarklySessionReplay
//let mobileKey = "mob-a211d8b4-9f80-4170-ba05-0120566a7bd7" // Andrey Sessions stg production


let mobileKey = "mob-d6e200b8-4a13-4c47-8ceb-7eb1f1705070" // Spree demo app Alexis Perflet config = { () -> LDConfig in
let config = { () -> LDConfig in
var config = LDConfig(
mobileKey: mobileKey,
autoEnvAttributes: .enabled
)
config.plugins = [
Observability(options: .init(
serviceName: "alexis-perf",
otlpEndpoint: "https://otel.observability.ld-stg.launchdarkly.com:4318",
backendUrl: "https://pub.observability.ld-stg.launchdarkly.com/",

//let mobileKey = "mob-f2aca03d-4a84-4b9d-bc35-db20cbb4ca0a" // iOS Session Production
//let mobileKey = "mob-d6e200b8-4a13-4c47-8ceb-7eb1f1705070" // Spree demo app Alexis Perflet config = { () -> LDConfig in
//let config = { () -> LDConfig in
// var config = LDConfig(
// mobileKey: mobileKey,
// autoEnvAttributes: .enabled
// )
// mobileKey: mobileKey,
// autoEnvAttributes: .enabled
// )
// config.plugins = [
// Observability(options: .init(
// serviceName: "i-os-sessions",
// serviceName: "alexis-perf",
// otlpEndpoint: "https://otel.observability.ld-stg.launchdarkly.com:4318",
// backendUrl: "https://pub.observability.ld-stg.launchdarkly.com/",

let mobileKey = "mob-f2aca03d-4a84-4b9d-bc35-db20cbb4ca0a" // iOS Session Production
let config = { () -> LDConfig in
var config = LDConfig(
mobileKey: mobileKey,
autoEnvAttributes: .enabled
)
config.plugins = [
Observability(options: .init(
serviceName: "i-os-sessions",

sessionBackgroundTimeout: 3,
autoInstrumentation: [.memory, .urlSession])),
Expand Down
Loading