diff --git a/Sources/LaunchDarklyObservability/Client/ObservabilityService.swift b/Sources/LaunchDarklyObservability/Client/ObservabilityService.swift index 12cbe2b..0ed6b94 100644 --- a/Sources/LaunchDarklyObservability/Client/ObservabilityService.swift +++ b/Sources/LaunchDarklyObservability/Client/ObservabilityService.swift @@ -169,7 +169,7 @@ final class ObservabilityService: InternalObserve { ) self.tracer = appTraceClient - let userInteractionManager = UserInteractionManager(options: options) { interaction in + let userInteractionManager = UserInteractionManager(options: options, sessionManaging: sessionManager) { interaction in interaction.startEndSpan(tracer: tracerDecorator) } self.userInteractionManager = userInteractionManager diff --git a/Sources/LaunchDarklyObservability/Session/SessionManager.swift b/Sources/LaunchDarklyObservability/Session/SessionManager.swift index 897f978..e46fe8b 100644 --- a/Sources/LaunchDarklyObservability/Session/SessionManager.swift +++ b/Sources/LaunchDarklyObservability/Session/SessionManager.swift @@ -16,6 +16,10 @@ extension SessionManaging { func start(sessionId: String) { start(sessionId: sessionId, isCustomSession: true) } + + public var sessionIdProvider: @Sendable () -> String { + { [self] in self.sessionInfo.id } + } } final class SessionManager: SessionManaging { diff --git a/Sources/LaunchDarklyObservability/UIInteractions/InputCaptureCoordinator.swift b/Sources/LaunchDarklyObservability/UIInteractions/InputCaptureCoordinator.swift index 3c1654a..798137b 100644 --- a/Sources/LaunchDarklyObservability/UIInteractions/InputCaptureCoordinator.swift +++ b/Sources/LaunchDarklyObservability/UIInteractions/InputCaptureCoordinator.swift @@ -21,12 +21,15 @@ struct TouchSample: Sendable { // relative to system startup time let timestamp: TimeInterval let target: TouchTarget? + /// Session id fixed at main-thread capture time (with location/target). + let sessionId: String - init(touch: UITouch, window: UIWindow, target: TouchTarget?) { + init(touch: UITouch, window: UIWindow, target: TouchTarget?, sessionId: String) { self.id = ObjectIdentifier(touch) self.location = touch.location(in: window) self.timestamp = touch.timestamp self.target = target + self.sessionId = sessionId self.phase = switch touch.phase { case .began: .began case .moved: .moved @@ -47,16 +50,19 @@ final class InputCaptureCoordinator { private let touchInterpreter: TouchInterpreter private let pressInterpreter: PressInterpreter private let receiverChecker: UIEventReceiverChecker + private let sessionIdProvider: @Sendable () -> String var onTouch: TouchInteractionYield? var onPress: PressInteractionYield? - + init(targetResolver: TargetResolving = TargetResolver(), - receiverChecker: UIEventReceiverChecker = UIEventReceiverChecker()) { + receiverChecker: UIEventReceiverChecker = UIEventReceiverChecker(), + sessionIdProvider: @Sendable @escaping () -> String) { self.targetResolver = targetResolver self.touchInterpreter = TouchInterpreter() self.pressInterpreter = PressInterpreter() self.source = UIWindowSwizzleSource() self.receiverChecker = receiverChecker + self.sessionIdProvider = sessionIdProvider } func start() { @@ -108,6 +114,7 @@ final class InputCaptureCoordinator { continuation: AsyncStream.Continuation ) { guard let touches = event.allTouches else { return } + let sessionId = sessionIdProvider() for touch in touches { guard touch.phase == .began else { continue } let target = targetResolver.resolve(view: touch.view, window: window, event: event) @@ -115,7 +122,8 @@ final class InputCaptureCoordinator { phase: PressInteraction.phase(forTouch: touch.phase), kind: .untrackedWindowTouch, timestamp: touch.timestamp, - target: target + target: target, + sessionId: sessionId ) continuation.yield(.press(interaction)) } @@ -127,6 +135,7 @@ final class InputCaptureCoordinator { continuation: AsyncStream.Continuation ) { guard let touches = event.allTouches else { return } + let sessionId = sessionIdProvider() for touch in touches { let target: TouchTarget? if touch.phase == .began || touch.phase == .ended { @@ -135,7 +144,12 @@ final class InputCaptureCoordinator { target = nil } - let touchSample = TouchSample(touch: touch, window: window, target: target) + let touchSample = TouchSample( + touch: touch, + window: window, + target: target, + sessionId: sessionId + ) continuation.yield(.touch(touchSample)) } } @@ -146,10 +160,11 @@ final class InputCaptureCoordinator { continuation: AsyncStream.Continuation ) { guard let pressesEvent = event as? UIPressesEvent else { return } + let sessionId = sessionIdProvider() for press in pressesEvent.allPresses { guard press.phase == .began else { continue } let target = targetResolver.resolve(press: press, window: window) - let interaction = PressInteraction(press: press, target: target) + let interaction = PressInteraction(press: press, target: target, sessionId: sessionId) if case .other = interaction.kind { continue } continuation.yield(.press(interaction)) diff --git a/Sources/LaunchDarklyObservability/UIInteractions/PressCaptureModels.swift b/Sources/LaunchDarklyObservability/UIInteractions/PressCaptureModels.swift index f34e5c1..9313529 100644 --- a/Sources/LaunchDarklyObservability/UIInteractions/PressCaptureModels.swift +++ b/Sources/LaunchDarklyObservability/UIInteractions/PressCaptureModels.swift @@ -38,23 +38,26 @@ public struct PressInteraction: Sendable { public let kind: RemotePressKind public let timestamp: TimeInterval public let target: TouchTarget? + public let sessionId: String public var isKeyboard: Bool { kind == .keyboard || kind == .untrackedWindowTouch } - init(press: UIPress, target: TouchTarget?) { + init(press: UIPress, target: TouchTarget?, sessionId: String) { self.phase = Self.phase(for: press.phase) self.kind = press.key != nil ? .keyboard : RemotePressKind(pressType: press.type) self.timestamp = press.timestamp self.target = target + self.sessionId = sessionId } - init(phase: Phase, kind: RemotePressKind = .untrackedWindowTouch, timestamp: TimeInterval, target: TouchTarget?) { + init(phase: Phase, kind: RemotePressKind = .untrackedWindowTouch, timestamp: TimeInterval, target: TouchTarget?, sessionId: String) { self.phase = phase self.kind = kind self.timestamp = timestamp self.target = target + self.sessionId = sessionId } static func phase(forTouch touchPhase: UITouch.Phase) -> Phase { diff --git a/Sources/LaunchDarklyObservability/UIInteractions/PressInterpreter.swift b/Sources/LaunchDarklyObservability/UIInteractions/PressInterpreter.swift index 64b9957..720eeb5 100644 --- a/Sources/LaunchDarklyObservability/UIInteractions/PressInterpreter.swift +++ b/Sources/LaunchDarklyObservability/UIInteractions/PressInterpreter.swift @@ -7,7 +7,8 @@ final class PressInterpreter { phase: pressInteraction.phase, kind: pressInteraction.kind, timestamp: pressInteraction.timestamp + uptimeDifference, - target: pressInteraction.target + target: pressInteraction.target, + sessionId: pressInteraction.sessionId ) yield(corrected) } diff --git a/Sources/LaunchDarklyObservability/UIInteractions/TouchInteraction.swift b/Sources/LaunchDarklyObservability/UIInteractions/TouchInteraction.swift index a203471..62de64e 100644 --- a/Sources/LaunchDarklyObservability/UIInteractions/TouchInteraction.swift +++ b/Sources/LaunchDarklyObservability/UIInteractions/TouchInteraction.swift @@ -26,6 +26,7 @@ public struct TouchInteraction: Sendable { public let startTimestamp: TimeInterval public let timestamp: TimeInterval public let target: TouchTarget? + public let sessionId: String } public enum SwipeDirection: Sendable { diff --git a/Sources/LaunchDarklyObservability/UIInteractions/TouchInterpreter.swift b/Sources/LaunchDarklyObservability/UIInteractions/TouchInterpreter.swift index fb5434c..f3ef098 100644 --- a/Sources/LaunchDarklyObservability/UIInteractions/TouchInterpreter.swift +++ b/Sources/LaunchDarklyObservability/UIInteractions/TouchInterpreter.swift @@ -25,6 +25,7 @@ final class TouchInterpreter { } func process(touchSample: TouchSample, yield: TouchInteractionYield) { + let sessionId = touchSample.sessionId // UITouch and UIEvent use time based on systemUptime getting and we needed adjustment for proper time let uptimeDifference = Date().timeIntervalSince1970 - ProcessInfo.processInfo.systemUptime switch touchSample.phase { @@ -35,29 +36,31 @@ final class TouchInterpreter { points: [TouchPoint(position: touchSample.location, timestamp: touchSample.timestamp + uptimeDifference)], target: touchSample.target) tracks[touchSample.id] = track - + let downInteraction = TouchInteraction(id: incrementingId, kind: .touchDown(touchSample.location), startTimestamp: touchSample.timestamp + uptimeDifference, timestamp: touchSample.timestamp + uptimeDifference, - target: touchSample.target) + target: touchSample.target, + sessionId: sessionId) yield(downInteraction) - + case .moved: guard var track = tracks[touchSample.id] else { return } - + let trackDuration = touchSample.timestamp - 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) track.target = touchSample.target - + let moveInteraction = TouchInteraction(id: incrementingId, kind: .touchPath(points: track.points), startTimestamp: track.start + uptimeDifference, timestamp: touchSample.timestamp + uptimeDifference, - target: touchSample.target) + target: touchSample.target, + sessionId: sessionId) track.points.removeAll() track.start = lastPoint.timestamp - uptimeDifference track.startPoint = touchSample.location @@ -65,24 +68,24 @@ final class TouchInterpreter { yield(moveInteraction) return } - + if let prevPoint = tracks[touchSample.id]?.points.last { let duration = touchSample.timestamp + uptimeDifference - prevPoint.timestamp guard duration >= TouchConstants.touchMoveThrottle else { return } } - + let distance = squaredDistance(from: track.startPoint, to: touchSample.location) guard distance >= TouchConstants.tapMaxDistanceSquared else { return } - + track.end = touchSample.timestamp track.target = touchSample.target track.points.append(TouchPoint(position: touchSample.location, timestamp: touchSample.timestamp + uptimeDifference)) tracks[touchSample.id] = track - + case .ended, .cancelled: // touchUp let startTimestamp = tracks[touchSample.id]?.start ?? touchSample.timestamp @@ -90,31 +93,34 @@ final class TouchInterpreter { kind: .touchUp(touchSample.location), startTimestamp: startTimestamp + uptimeDifference, timestamp: touchSample.timestamp + uptimeDifference, - target: touchSample.target) + target: touchSample.target, + sessionId: sessionId) yield(upInteraction) - + // touchPath guard let track = tracks.removeValue(forKey: touchSample.id), track.points.isNotEmpty else { return } - + let moveInteraction = TouchInteraction(id: incrementingId, kind: .touchPath(points: track.points), startTimestamp: startTimestamp + uptimeDifference, timestamp: touchSample.timestamp + uptimeDifference, - target: touchSample.target) + target: touchSample.target, + sessionId: sessionId) yield(moveInteraction) case .unknown: () //NOOP } } - + func flushMovements(touchSample: TouchSample, uptimeDifference: TimeInterval, startTimestamp: TimeInterval, yield: TouchInteractionYield) { guard var track = tracks[touchSample.id], track.points.isNotEmpty else { return } - + let moveInteraction = TouchInteraction(id: incrementingId, kind: .touchPath(points: track.points), startTimestamp: startTimestamp + uptimeDifference, timestamp: touchSample.timestamp + uptimeDifference, - target: touchSample.target) + target: touchSample.target, + sessionId: touchSample.sessionId) if let lastPoint = track.points.last { track.points.removeAll() track.start = lastPoint.timestamp - uptimeDifference diff --git a/Sources/LaunchDarklyObservability/UIInteractions/UserInteractionManager.swift b/Sources/LaunchDarklyObservability/UIInteractions/UserInteractionManager.swift index 86cd343..7d8f89a 100644 --- a/Sources/LaunchDarklyObservability/UIInteractions/UserInteractionManager.swift +++ b/Sources/LaunchDarklyObservability/UIInteractions/UserInteractionManager.swift @@ -13,9 +13,12 @@ public final class UserInteractionManager { interactionEventSubject.eraseToAnyPublisher() } - init(options: ObservabilityOptions, yield: @escaping TouchInteractionYield) { + init(options: ObservabilityOptions, sessionManaging: SessionManaging, yield: @escaping TouchInteractionYield) { let targetResolver = TargetResolver() - self.inputCaptureCoordinator = InputCaptureCoordinator(targetResolver: targetResolver) + self.inputCaptureCoordinator = InputCaptureCoordinator( + targetResolver: targetResolver, + sessionIdProvider: sessionManaging.sessionIdProvider + ) self.inputCaptureCoordinator.onTouch = { [interactionEventSubject] interaction in yield(interaction) interactionEventSubject.send(.touch(interaction)) diff --git a/Sources/LaunchDarklySessionReplay/BenchMark/CompressionBenchmarkRunner.swift b/Sources/LaunchDarklySessionReplay/BenchMark/CompressionBenchmarkRunner.swift index 3253fbb..f225777 100644 --- a/Sources/LaunchDarklySessionReplay/BenchMark/CompressionBenchmarkRunner.swift +++ b/Sources/LaunchDarklySessionReplay/BenchMark/CompressionBenchmarkRunner.swift @@ -18,6 +18,7 @@ public final class CompressionBenchmarkRunner { var captureTime: TimeInterval = 0 let start = CFAbsoluteTimeGetCurrent() + let sessionId = SessionInfo().id for frame in frames { let captureStart = CFAbsoluteTimeGetCurrent() @@ -27,7 +28,7 @@ public final class CompressionBenchmarkRunner { continue } - let item = EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame)) + let item = EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame, sessionId: sessionId)) let events = await eventGenerator.generateEvents(items: [item]) if let data = try? encoder.encode(events) { diff --git a/Sources/LaunchDarklySessionReplay/Exporter/IdentifyItemPayload.swift b/Sources/LaunchDarklySessionReplay/Exporter/IdentifyItemPayload.swift index 6e80834..b95409b 100644 --- a/Sources/LaunchDarklySessionReplay/Exporter/IdentifyItemPayload.swift +++ b/Sources/LaunchDarklySessionReplay/Exporter/IdentifyItemPayload.swift @@ -4,11 +4,12 @@ import LaunchDarklyObservability struct IdentifyItemPayload: EventQueueItemPayload { let attributes: [String: String] var timestamp: TimeInterval + let sessionId: String var exporterClass: AnyClass { SessionReplayExporter.self } - + func cost() -> Int { attributes.count * 100 } @@ -57,7 +58,7 @@ extension IdentifyItemPayload { } @MainActor - init(options: ObservabilityOptions, sessionAttributes: [String: AttributeValue]?, ldContext: LDContext? = nil, timestamp: TimeInterval) { + init(options: ObservabilityOptions, sessionAttributes: [String: AttributeValue]?, ldContext: LDContext? = nil, timestamp: TimeInterval, sessionId: String) { let canonicalKey = ldContext?.fullyQualifiedKey() ?? "unknown" let contextKeys = ldContext?.contextKeys() ?? [:] @@ -68,11 +69,12 @@ extension IdentifyItemPayload { canonicalKey: canonicalKey ) self.timestamp = timestamp + self.sessionId = sessionId } /// Proxy-friendly initialiser that accepts pre-extracted context keys /// instead of LDContext, so the MAUI bridge can call it with simple types. - init(options: ObservabilityOptions, sessionAttributes: [String: AttributeValue]?, contextKeys: [String: String], canonicalKey: String, timestamp: TimeInterval) { + init(options: ObservabilityOptions, sessionAttributes: [String: AttributeValue]?, contextKeys: [String: String], canonicalKey: String, timestamp: TimeInterval, sessionId: String) { self.attributes = Self.buildAttributes( options: options, sessionAttributes: sessionAttributes, @@ -80,6 +82,7 @@ extension IdentifyItemPayload { canonicalKey: canonicalKey ) self.timestamp = timestamp + self.sessionId = sessionId } } diff --git a/Sources/LaunchDarklySessionReplay/Exporter/ImageItemPayload.swift b/Sources/LaunchDarklySessionReplay/Exporter/ImageItemPayload.swift index 872d070..a04b1c5 100644 --- a/Sources/LaunchDarklySessionReplay/Exporter/ImageItemPayload.swift +++ b/Sources/LaunchDarklySessionReplay/Exporter/ImageItemPayload.swift @@ -5,14 +5,15 @@ struct ImageItemPayload: EventQueueItemPayload { var exporterClass: AnyClass { SessionReplayExporter.self } - + var timestamp: TimeInterval { exportFrame.timestamp } - + func cost() -> Int { exportFrame.addImages.reduce(0) { $0 + $1.data.count } } - + let exportFrame: ExportFrame + let sessionId: String } diff --git a/Sources/LaunchDarklySessionReplay/Exporter/SessionReplayExporter.swift b/Sources/LaunchDarklySessionReplay/Exporter/SessionReplayExporter.swift index 0133157..889975e 100644 --- a/Sources/LaunchDarklySessionReplay/Exporter/SessionReplayExporter.swift +++ b/Sources/LaunchDarklySessionReplay/Exporter/SessionReplayExporter.swift @@ -81,7 +81,7 @@ actor SessionReplayExporter: EventExporting { let session = try await initializeSession(sessionSecureId: sessionInfo.id) var identifyPayload = self.identifyPayload if identifyPayload == nil { - identifyPayload = await IdentifyItemPayload(options: context.observabilityContext.options, sessionAttributes: context.observabilityContext.sessionAttributes, timestamp: Date().timeIntervalSince1970) + identifyPayload = await IdentifyItemPayload(options: context.observabilityContext.options, sessionAttributes: context.observabilityContext.sessionAttributes, timestamp: Date().timeIntervalSince1970, sessionId: sessionInfo.id) } if let identifyPayload { try await identifySession(sessionSecureId: session.secureId, userObject: identifyPayload.attributes) diff --git a/Sources/LaunchDarklySessionReplay/ScreenCapture/CaptureManager.swift b/Sources/LaunchDarklySessionReplay/ScreenCapture/CaptureManager.swift index eff22c4..7157f9e 100644 --- a/Sources/LaunchDarklySessionReplay/ScreenCapture/CaptureManager.swift +++ b/Sources/LaunchDarklySessionReplay/ScreenCapture/CaptureManager.swift @@ -19,7 +19,8 @@ final class CaptureManager: EventSource { private var cancellables = Set() private let debugFrameWriter = false private let rawFrameWriter: RawFrameWriter? - + private let sessionIdProvider: @Sendable () -> String + @MainActor var isEnabled: Bool = false { didSet { @@ -35,12 +36,14 @@ final class CaptureManager: EventSource { init(captureService: ImageCaptureService, compression: SessionReplayOptions.CompressionMethod, appLifecycleManager: AppLifecycleManaging, - eventQueue: EventQueue) { + eventQueue: EventQueue, + sessionIdProvider: @Sendable @escaping () -> String) { self.captureService = captureService self.exportDiffManager = ExportDiffManager(compression: compression, scale: 1.0) self.eventQueue = eventQueue self.appLifecycleManager = appLifecycleManager self.rawFrameWriter = debugFrameWriter ? (try? RawFrameWriter()) : nil + self.sessionIdProvider = sessionIdProvider let sessionExporterId = self.sessionExporterId Task { @MainActor in @@ -133,7 +136,7 @@ final class CaptureManager: EventSource { return } - await self.eventQueue.send(ImageItemPayload(exportFrame: exportFrame)) + await self.eventQueue.send(ImageItemPayload(exportFrame: exportFrame, sessionId: self.sessionIdProvider())) } } } diff --git a/Sources/LaunchDarklySessionReplay/SessionReplayService.swift b/Sources/LaunchDarklySessionReplay/SessionReplayService.swift index 86be2e4..c2e23e6 100644 --- a/Sources/LaunchDarklySessionReplay/SessionReplayService.swift +++ b/Sources/LaunchDarklySessionReplay/SessionReplayService.swift @@ -80,7 +80,8 @@ final class SessionReplayService: SessionReplayServicing { self.captureManager = CaptureManager(captureService: captureService, compression: sessonReplayOptions.compression, appLifecycleManager: observabilityContext.appLifecycleManager, - eventQueue: transportService.eventQueue) + eventQueue: transportService.eventQueue, + sessionIdProvider: observabilityContext.sessionManager.sessionIdProvider) self.userInteractionManager = observabilityContext.userInteractionManager let sessionReplayContext = SessionReplayContext( @@ -107,13 +108,15 @@ final class SessionReplayService: SessionReplayServicing { func afterIdentify(contextKeys: [String: String], canonicalKey: String, completed: Bool) { guard completed else { return } + let sessionId = observabilityContext.sessionManager.sessionInfo.id Task { let identifyPayload = IdentifyItemPayload( options: observabilityContext.options, sessionAttributes: observabilityContext.sessionAttributes, contextKeys: contextKeys, canonicalKey: canonicalKey, - timestamp: Date().timeIntervalSince1970 + timestamp: Date().timeIntervalSince1970, + sessionId: sessionId ) await scheduleIdentifySession(identifyPayload: identifyPayload) } diff --git a/Tests/SessionReplayTests/RRWebEventGeneratorTests.swift b/Tests/SessionReplayTests/RRWebEventGeneratorTests.swift index 8ae1288..9d50ef5 100644 --- a/Tests/SessionReplayTests/RRWebEventGeneratorTests.swift +++ b/Tests/SessionReplayTests/RRWebEventGeneratorTests.swift @@ -45,8 +45,8 @@ struct RRWebEventGeneratorTests { let secondImage = makeExportFrame(dataSize: 256, width: 320, height: 480, timestamp: 2.0) let items: [EventQueueItem] = [ - EventQueueItem(payload: ImageItemPayload(exportFrame: firstImage)), - EventQueueItem(payload: ImageItemPayload(exportFrame: secondImage)) + EventQueueItem(payload: ImageItemPayload(exportFrame: firstImage, sessionId: "test-session")), + EventQueueItem(payload: ImageItemPayload(exportFrame: secondImage, sessionId: "test-session")) ] // Act @@ -75,8 +75,8 @@ struct RRWebEventGeneratorTests { let secondImageSameSize = makeExportFrame(dataSize: 256, width: 320, height: 480, timestamp: 2.0) let items: [EventQueueItem] = [ - EventQueueItem(payload: ImageItemPayload(exportFrame: largeFirstImage)), - EventQueueItem(payload: ImageItemPayload(exportFrame: secondImageSameSize)) + EventQueueItem(payload: ImageItemPayload(exportFrame: largeFirstImage, sessionId: "test-session")), + EventQueueItem(payload: ImageItemPayload(exportFrame: secondImageSameSize, sessionId: "test-session")) ] // Act @@ -121,8 +121,8 @@ struct RRWebEventGeneratorTests { ) let items: [EventQueueItem] = [ - EventQueueItem(payload: ImageItemPayload(exportFrame: keyframeImage)), - EventQueueItem(payload: ImageItemPayload(exportFrame: nonKeyframeImage)) + EventQueueItem(payload: ImageItemPayload(exportFrame: keyframeImage, sessionId: "test-session")), + EventQueueItem(payload: ImageItemPayload(exportFrame: nonKeyframeImage, sessionId: "test-session")) ] // Act @@ -147,7 +147,8 @@ struct RRWebEventGeneratorTests { phase: .began, kind: .select, timestamp: 99.0, - target: nil + target: nil, + sessionId: "test-session" ) let items: [EventQueueItem] = [EventQueueItem(payload: PressInteractionPayload(pressInteraction: pressInteraction))] let events = await generator.generateEvents(items: items) @@ -174,7 +175,8 @@ struct RRWebEventGeneratorTests { phase: .began, kind: .keyboard, timestamp: 12.0, - target: nil + target: nil, + sessionId: "test-session" ) let items: [EventQueueItem] = [EventQueueItem(payload: PressInteractionPayload(pressInteraction: pressInteraction))] let events = await generator.generateEvents(items: items) @@ -200,7 +202,8 @@ struct RRWebEventGeneratorTests { phase: .began, kind: .untrackedWindowTouch, timestamp: 50.0, - target: nil + target: nil, + sessionId: "test-session" ) let items: [EventQueueItem] = [EventQueueItem(payload: PressInteractionPayload(pressInteraction: pressInteraction))] let events = await generator.generateEvents(items: items) diff --git a/Tests/SessionReplayTests/RawFramesRRWebEventGeneratorTests.swift b/Tests/SessionReplayTests/RawFramesRRWebEventGeneratorTests.swift index 2cc4606..4393992 100644 --- a/Tests/SessionReplayTests/RawFramesRRWebEventGeneratorTests.swift +++ b/Tests/SessionReplayTests/RawFramesRRWebEventGeneratorTests.swift @@ -35,7 +35,7 @@ struct RawFramesRRWebEventGeneratorTests { } // Mirrors BenchmarkExecutor flow: exportFrame -> EventQueueItem -> generateEvents. - let item = EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame)) + let item = EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame, sessionId: "test-session")) let events = await eventGenerator.generateEvents(items: [item]) extractedColors.append(contentsOf: extractEventImageColors(events: events)) extractedSizes.append(contentsOf: extractEventImageSizes(events: events)) @@ -70,19 +70,19 @@ struct RawFramesRRWebEventGeneratorTests { #expect(Bool(false), "Expected export frame for base frame") return } - let events1 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame1))]) + let events1 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame1, sessionId: "test-session"))]) guard let exportFrame2 = exportDiffManager.exportFrame(from: navBarFrame) else { #expect(Bool(false), "Expected export frame for nav bar frame") return } - let events2 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame2))]) + let events2 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame2, sessionId: "test-session"))]) guard let exportFrame3 = exportDiffManager.exportFrame(from: rollbackFrame) else { #expect(Bool(false), "Expected export frame for rollback frame") return } - let events3 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame3))]) + let events3 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame3, sessionId: "test-session"))]) let firstSize = firstAddedImageSize(events: events1) let secondSize = firstAddedImageSize(events: events2) @@ -122,31 +122,31 @@ struct RawFramesRRWebEventGeneratorTests { #expect(Bool(false), "Expected export frame for frame 1") return } - let events1 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame1))]) + let events1 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame1, sessionId: "test-session"))]) guard let exportFrame2 = exportDiffManager.exportFrame(from: frame2) else { #expect(Bool(false), "Expected export frame for frame 2") return } - let events2 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame2))]) + let events2 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame2, sessionId: "test-session"))]) guard let exportFrame3 = exportDiffManager.exportFrame(from: frame3) else { #expect(Bool(false), "Expected export frame for frame 3") return } - let events3 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame3))]) + let events3 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame3, sessionId: "test-session"))]) guard let exportFrame4 = exportDiffManager.exportFrame(from: frame4) else { #expect(Bool(false), "Expected export frame for frame 4") return } - let events4 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame4))]) + let events4 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame4, sessionId: "test-session"))]) guard let exportFrame5 = exportDiffManager.exportFrame(from: frame5) else { #expect(Bool(false), "Expected export frame for frame 5") return } - let events5 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame5))]) + let events5 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame5, sessionId: "test-session"))]) let firstSize = firstAddedImageSize(events: events1) let secondSize = firstAddedImageSize(events: events2) @@ -202,21 +202,21 @@ struct RawFramesRRWebEventGeneratorTests { #expect(Bool(false), "Expected export frame for frame 1") return } - let events1 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame1))]) + let events1 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame1, sessionId: "test-session"))]) trackedNodeIds.formUnion(addedNodeIds(events: events1)) guard let exportFrame2 = exportDiffManager.exportFrame(from: frame2) else { #expect(Bool(false), "Expected export frame for frame 2") return } - let events2 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame2))]) + let events2 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame2, sessionId: "test-session"))]) trackedNodeIds.formUnion(addedNodeIds(events: events2)) guard let exportFrame3 = exportDiffManager.exportFrame(from: frame3) else { #expect(Bool(false), "Expected export frame for frame 3") return } - let events3 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame3))]) + let events3 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame3, sessionId: "test-session"))]) trackedNodeIds.formUnion(addedNodeIds(events: events3)) #expect(trackedNodeIds.count == 3) @@ -225,7 +225,7 @@ struct RawFramesRRWebEventGeneratorTests { return } #expect(exportFrame4.isKeyframe, "Frame 4 should be a keyframe with layers: 3") - let events4 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame4))]) + let events4 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame4, sessionId: "test-session"))]) guard let fourthMutation = firstMutationData(events: events4) else { #expect(Bool(false), "Expected mutation event for frame 4") @@ -244,7 +244,7 @@ struct RawFramesRRWebEventGeneratorTests { #expect(Bool(false), "Expected export frame for frame 5") return } - let events5 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame5))]) + let events5 = await eventGenerator.generateEvents(items: [EventQueueItem(payload: ImageItemPayload(exportFrame: exportFrame5, sessionId: "test-session"))]) guard let fifthMutation = firstMutationData(events: events5) else { #expect(Bool(false), "Expected mutation event for frame 5")