From 3d1100ae0d26fd5b5cb1b1b502f3ec5cd8336244 Mon Sep 17 00:00:00 2001 From: Andrey Belonogov Date: Tue, 21 Oct 2025 11:06:38 -0700 Subject: [PATCH 1/7] remove import --- Sources/Observability/Session/SessionManager.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/Observability/Session/SessionManager.swift b/Sources/Observability/Session/SessionManager.swift index d7afaee..1ffae22 100644 --- a/Sources/Observability/Session/SessionManager.swift +++ b/Sources/Observability/Session/SessionManager.swift @@ -1,7 +1,6 @@ import Foundation import UIKit.UIApplication import OSLog -import OpenTelemetryApi public protocol SessionManaging { var sessionAttributes: [String: AttributeValue] { get } From c058eeac1856e12badf6a7a9d36c607edb24e360 Mon Sep 17 00:00:00 2001 From: Andrey Belonogov Date: Thu, 23 Oct 2025 09:12:24 -0700 Subject: [PATCH 2/7] feat: SnapshotTaker pausing --- Sources/Common/Broadcaster.swift | 57 ++++ Sources/Common/Task+Extensions.swift | 2 +- .../Client/ObservabilityClientFactory.swift | 6 +- .../Client/ObservabilityContext.swift | 21 +- .../Session/AppLifeCycleManager.swift | 86 +++++ .../Observability/Session/SessionInfo.swift | 12 + .../Session/SessionManager.swift | 138 ++++---- .../Observability/Session/SessionState.swift | 7 - .../SessionReplayEventGenerator.swift | 238 ++++++++++++++ .../Exporter/SessionReplayExporter.swift | 297 +++--------------- .../ScreenCapture/ScreenCaptureService.swift | 4 - .../ScreenCapture/SnapshotTaker.swift | 37 ++- .../SessionReplay/SessionReplayService.swift | 17 +- TestApp/Sources/AppDelegate.swift | 40 ++- 14 files changed, 573 insertions(+), 389 deletions(-) create mode 100644 Sources/Common/Broadcaster.swift create mode 100644 Sources/Observability/Session/AppLifeCycleManager.swift delete mode 100644 Sources/Observability/Session/SessionState.swift create mode 100644 Sources/SessionReplay/Exporter/SessionReplayEventGenerator.swift diff --git a/Sources/Common/Broadcaster.swift b/Sources/Common/Broadcaster.swift new file mode 100644 index 0000000..297d707 --- /dev/null +++ b/Sources/Common/Broadcaster.swift @@ -0,0 +1,57 @@ +import Foundation + +public actor Broadcaster { + private var continuations = [Int: AsyncStream.Continuation]() + private var nextIdentifier = 0 + private var finished = false + + public init() { } + + public func stream( + bufferingPolicy: AsyncStream.Continuation.BufferingPolicy = .unbounded + ) -> AsyncStream { + let id = nextIdentifier + nextIdentifier &+= 1 + var continuationRef: AsyncStream.Continuation? + + let stream = AsyncStream(bufferingPolicy: bufferingPolicy) { continuation in + continuationRef = continuation + continuation.onTermination = { [weak self] _ in + guard let self else { return } + Task { await self.removeContinuation(id: id) } + } + } + + if let continuation = continuationRef { + if finished { + continuation.finish() + } else { + continuations[id] = continuation + } + } + + return stream + } + + public func send(_ event: Value) { + guard !finished else { return } + for continuation in continuations.values { + _ = continuation.yield(event) + } + } + + public func finish() { + guard !finished else { return } + finished = true + for continuation in continuations.values { + continuation.finish() + } + continuations.removeAll() + } + + private func removeContinuation(id: Int) { + continuations.removeValue(forKey: id) + } +} + + diff --git a/Sources/Common/Task+Extensions.swift b/Sources/Common/Task+Extensions.swift index 6498169..4a24386 100644 --- a/Sources/Common/Task+Extensions.swift +++ b/Sources/Common/Task+Extensions.swift @@ -12,6 +12,6 @@ extension Task where Success == Never, Failure == Never { /// try await Task.sleep(seconds: 3.0) /// public static func sleep(seconds: TimeInterval) async throws { - try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000.0)) + try await Task.sleep(nanoseconds: UInt64(seconds * Double(NSEC_PER_SEC))) } } diff --git a/Sources/Observability/Client/ObservabilityClientFactory.swift b/Sources/Observability/Client/ObservabilityClientFactory.swift index 0baa839..5a47197 100644 --- a/Sources/Observability/Client/ObservabilityClientFactory.swift +++ b/Sources/Observability/Client/ObservabilityClientFactory.swift @@ -18,12 +18,15 @@ public struct ObservabilityClientFactory { withOptions options: Options, mobileKey: String ) throws -> Observe { + let appLifecycleManager = AppLifecycleManager() let sessionManager = SessionManager( options: .init( timeout: options.sessionBackgroundTimeout, isDebug: options.isDebug, - log: options.log) + log: options.log), + appLifecycleManager: appLifecycleManager ) + var autoInstrumentation = [AutoInstrumentation]() let sampler = CustomSampler(sampler: ThreadSafeSampler.shared.sample(_:)) let meter: MetricsApi @@ -107,6 +110,7 @@ public struct ObservabilityClientFactory { let context = ObservabilityContext( sdkKey: mobileKey, options: options, + appLifecycleManager: appLifecycleManager, sessionManager: sessionManager, transportService: transportService ) diff --git a/Sources/Observability/Client/ObservabilityContext.swift b/Sources/Observability/Client/ObservabilityContext.swift index be4c2de..f00a6fb 100644 --- a/Sources/Observability/Client/ObservabilityContext.swift +++ b/Sources/Observability/Client/ObservabilityContext.swift @@ -3,20 +3,23 @@ import OpenTelemetrySdk import Common /** Shared info between plugins */ -public struct ObservabilityContext { +public class ObservabilityContext { public let sdkKey: String public let options: Options - public var sessionManager: SessionManaging - public var transportService: TransportServicing - + public let sessionManager: SessionManaging + public let transportService: TransportServicing + public let appLifecycleManager: AppLifecycleManaging + public init( sdkKey: String, options: Options, + appLifecycleManager: AppLifecycleManaging, sessionManager: SessionManaging, transportService: TransportServicing) { - self.sdkKey = sdkKey - self.options = options - self.sessionManager = sessionManager - self.transportService = transportService - } + self.sdkKey = sdkKey + self.options = options + self.appLifecycleManager = appLifecycleManager + self.sessionManager = sessionManager + self.transportService = transportService + } } diff --git a/Sources/Observability/Session/AppLifeCycleManager.swift b/Sources/Observability/Session/AppLifeCycleManager.swift new file mode 100644 index 0000000..1dfc640 --- /dev/null +++ b/Sources/Observability/Session/AppLifeCycleManager.swift @@ -0,0 +1,86 @@ +import UIKit +import Common + +public enum AppLifeCycleEvent { + case didFinishLaunching + case willEnterForeground + case didBecomeActive + case willResignActive + case didEnterBackground + case willTerminate +} + +public protocol AppLifecycleManaging: AnyObject { + func events() async -> AsyncStream +} + +final class AppLifecycleManager: AppLifecycleManaging { + private let broadcaster = Broadcaster() + private var observers = [NSObjectProtocol]() + + init() { + observeLifecycleNotifications() + } + + private func observeLifecycleNotifications() { + observers.append(NotificationCenter.default.addObserver(forName: UIApplication.didFinishLaunchingNotification, object: nil, queue: .main) { [weak self] _ in + self?.didFinishLaunching() + }) + observers.append(NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: .main) { [weak self] _ in + self?.handleDidBecomeActive() + }) + observers.append(NotificationCenter.default.addObserver(forName: UIApplication.willResignActiveNotification, object: nil, queue: .main) { [weak self] _ in + self?.handleWillResignActive() + }) + observers.append(NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: .main) { [weak self] _ in + self?.handleDidEnterBackground() + }) + observers.append(NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { [weak self] _ in + self?.handleWillEnterForeground() + }) + observers.append(NotificationCenter.default.addObserver(forName: UIApplication.willTerminateNotification, object: nil, queue: .main) { [weak self] _ in + self?.handleWillTerminate() + }) + } + + deinit { + observers.forEach { + NotificationCenter.default.removeObserver($0) + } + Task { + await broadcaster.finish() + } + } + + func events() async -> AsyncStream { + await broadcaster.stream() + } + + private func send(_ event: AppLifeCycleEvent) { + Task { await broadcaster.send(event) } + } + + private func didFinishLaunching() { + send(.didFinishLaunching) + } + + private func handleDidBecomeActive() { + send(.didBecomeActive) + } + + private func handleWillResignActive() { + send(.willResignActive) + } + + private func handleDidEnterBackground() { + send(.didEnterBackground) + } + + private func handleWillEnterForeground() { + send(.willEnterForeground) + } + + private func handleWillTerminate() { + send(.willTerminate) + } +} diff --git a/Sources/Observability/Session/SessionInfo.swift b/Sources/Observability/Session/SessionInfo.swift index 2eced1a..70bbddf 100644 --- a/Sources/Observability/Session/SessionInfo.swift +++ b/Sources/Observability/Session/SessionInfo.swift @@ -1,4 +1,5 @@ import Foundation +import Common public struct SessionInfo: Sendable, Equatable { public let id: String @@ -8,4 +9,15 @@ public struct SessionInfo: Sendable, Equatable { self.id = id self.startTime = startTime } + + public init() { + self.init(id: SecureIDGenerator.generateSecureID(), startTime: Date()) + } + + var sessionAttributes: [String: AttributeValue] { + [ + "session.id": .string(id), + "session.start_time": .string(String(format: "%.0f", startTime.timeIntervalSince1970)) + ] + } } diff --git a/Sources/Observability/Session/SessionManager.swift b/Sources/Observability/Session/SessionManager.swift index 1ffae22..5c5a6e1 100644 --- a/Sources/Observability/Session/SessionManager.swift +++ b/Sources/Observability/Session/SessionManager.swift @@ -1,127 +1,95 @@ import Foundation import UIKit.UIApplication import OSLog +import Common public protocol SessionManaging { - var sessionAttributes: [String: AttributeValue] { get } + func sessionChanges() async -> AsyncStream var sessionInfo: SessionInfo { get } - var onSessionDidChange: ((SessionInfo) -> Void)? { get set } - var onStateDidChange: ((SessionState, SessionInfo) -> Void)? { get set } } final class SessionManager: SessionManaging { - private var id: String - private var startTime: Date - private var backgroundTime: Date? - private var options: SessionOptions - var sessionAttributes: [String: AttributeValue] { - [ - "session.id": .string(id), - "session.start_time": .string(String(format: "%.0f", startTime.timeIntervalSince1970)) - ] - } + let appLifecycleManager: AppLifecycleManaging + private let options: SessionOptions + private let broadcaster: Broadcaster + private var _sessionInfo = SessionInfo() + private var backgroundTime: DispatchTime? + private let stateQueue = DispatchQueue( label: "com.launchdarkly.observability.state-queue", attributes: .concurrent) - private var currentState: SessionState = .notRunning - var onSessionDidChange: ((SessionInfo) -> Void)? - var onStateDidChange: ((SessionState, SessionInfo) -> Void)? - - var sessionInfo: SessionInfo { - .init( - id: id, - startTime: startTime - ) - } - - init( - id: String = SecureIDGenerator.generateSecureID(), - startTime: Date = Date(), - options: SessionOptions - ) { - self.id = id - self.startTime = startTime + init(options: SessionOptions, appLifecycleManager: AppLifecycleManaging) { self.options = options - observeLifecycleNotifications() - } - - deinit { - NotificationCenter.default.removeObserver(self) - } - - private func observeLifecycleNotifications() { - NotificationCenter.default.addObserver(self, selector: #selector(handleDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(handleWillResignActive), name: UIApplication.willResignActiveNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(handleDidEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(handleWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(handleWillTerminate), name: UIApplication.willTerminateNotification, object: nil) - } - - @objc private func handleDidBecomeActive() { - transition(to: .active) - } - - @objc private func handleWillResignActive() { - transition(to: .inactive) - } - - @objc private func handleDidEnterBackground() { - transition(to: .background) + self.appLifecycleManager = appLifecycleManager + self._sessionInfo = SessionInfo() + self.broadcaster = Broadcaster() + + Task(priority: .background) { [weak self] in + guard let self else { return } + + for await event in await appLifecycleManager.events() { + transition(to: event) + } + } } - @objc private func handleWillEnterForeground() { - transition(to: .inactive) + var sessionInfo: SessionInfo { + // Consider using atomic synchronization + stateQueue.sync() { + return _sessionInfo + } } - - @objc private func handleWillTerminate() { - transition(to: .notRunning) + + func sessionChanges() async -> AsyncStream { + await broadcaster.stream() } - private func transition(to newState: SessionState) { - stateQueue.sync(flags: .barrier) { [weak self] in - guard let self else { return } - guard self.currentState != newState else { return } - self.currentState = newState + private func transition(to newState: AppLifeCycleEvent) { switch newState { - case .background: + case .didEnterBackground: self.handleBackgroundState() - case .active: + case .willEnterForeground: self.handleActiveState() default: break } - self.onStateDidChange?(newState, self.sessionInfo) - } } private func handleActiveState() { - guard let backgroundTime = self.backgroundTime else { return } - let timeInBackground = Date().timeIntervalSince1970 - backgroundTime.timeIntervalSince1970 - if timeInBackground >= self.options.timeout { - self.resetSession() + stateQueue.sync(flags: .barrier) { [weak self] in + guard let self else { return } + + guard let backgroundTime = self.backgroundTime else { return } + let timeInBackground = Double(DispatchTime.now().uptimeNanoseconds - backgroundTime.uptimeNanoseconds) / Double(NSEC_PER_SEC) + if timeInBackground >= self.options.timeout { + self.resetSession() + } + self.backgroundTime = nil } - self.backgroundTime = nil } private func handleBackgroundState() { - self.backgroundTime = Date() + stateQueue.sync(flags: .barrier) { [weak self] in + guard let self else { return } + + self.backgroundTime = DispatchTime.now() + } } private func resetSession() { - let oldSessionId = sessionInfo.id - let newSessionId = SecureIDGenerator.generateSecureID() - let oldStartTime = startTime + let oldSession = _sessionInfo + let newSession = SessionInfo() + self._sessionInfo = newSession - id = newSessionId - let newStartTime = Date() - startTime = newStartTime + Task { + await broadcaster.send(newSession) + } if options.isDebug { - os_log("%{public}@", log: options.log, type: .info, "🔄 Session reset: \(oldSessionId) -> \(sessionInfo.id)") - let dateInterval = DateInterval(start: oldStartTime, end: newStartTime) + os_log("%{public}@", log: options.log, type: .info, "🔄 Session reset: \(oldSession.id) -> \(_sessionInfo.id)") + let dateInterval = DateInterval(start: oldSession.startTime, end: newSession.startTime) os_log("%{public}@", log: options.log, type: .info, "Session duration: \(dateInterval.duration) seconds") } - onSessionDidChange?(sessionInfo) } } diff --git a/Sources/Observability/Session/SessionState.swift b/Sources/Observability/Session/SessionState.swift deleted file mode 100644 index 7a110d5..0000000 --- a/Sources/Observability/Session/SessionState.swift +++ /dev/null @@ -1,7 +0,0 @@ -public enum SessionState: String { - case notRunning - case inactive - case active - case background - case suspended -} diff --git a/Sources/SessionReplay/Exporter/SessionReplayEventGenerator.swift b/Sources/SessionReplay/Exporter/SessionReplayEventGenerator.swift new file mode 100644 index 0000000..7cc931b --- /dev/null +++ b/Sources/SessionReplay/Exporter/SessionReplayEventGenerator.swift @@ -0,0 +1,238 @@ +import Foundation +import Common +import Observability + +actor SessionReplayEventGenerator { + var sid = 0 + var nextSid: Int { + sid += 1 + return sid + } + + var id = 16 + var nextId: Int { + id += 1 + return id + } + var shouldMoveMouseOnce = true + var imageId: Int? + var lastExportImage: ExportImage? + let positionCorrection = ExportImage.padding + + init() { + } + + func generateEvents(items: [EventQueueItem]) -> [Event] { + var events = [Event]() + for item in items { + appendEvents(item: item, events: &events) + } + return events + } + + func appendEvents(item: EventQueueItem, events: inout [Event]) { + switch item.payload { + case let payload as ScreenImageItem: + let exportImage = payload.exportImage + guard lastExportImage != exportImage else { + return + } + defer { + lastExportImage = exportImage + } + let timestamp = item.timestamp + + if shouldMoveMouseOnce { + // TODO: make it through real event, when we subscribe device events + events.append(reloadEvent(timestamp: timestamp)) + // artificial mouse movement to wake up session replay player + if let mouseEvent = mouseEvent(timestamp: timestamp, x: 0, y: 0, timeOffset: 0) { + events.append(mouseEvent) + } + shouldMoveMouseOnce = false + } + + if let imageId, + let lastExportImage, + lastExportImage.originalWidth == exportImage.originalWidth, + lastExportImage.originalHeight == exportImage.originalHeight { + events.append(drawImageEvent(exportImage: exportImage, timestamp: timestamp, imageId: imageId)) + } else { + // if screen changed size we send fullSnapshot as canvas resizing might take to many hours on the server + appendFullSnapshotEvents(exportImage, timestamp, &events) + } + + case let interaction as UIInteraction: + appendTouchInteraction(interaction: interaction, events: &events) + default: + () // + } + } + + fileprivate func appendTouchInteraction(interaction: UIInteraction, events: inout [Event]) { + if let touchEventData: EventDataProtocol = switch interaction.kind { + case .touchDown(let point): + EventData(source: .mouseInteraction, + type: .touchStart, + id: imageId, + x: point.x + positionCorrection.x, + y: point.y + positionCorrection.y) + + case .touchUp(let point): + EventData(source: .mouseInteraction, + type: .touchEnd, + id: imageId, + x: point.x + positionCorrection.x, + y: point.y + positionCorrection.y) + + case .touchPath(let points): + MouseMoveEventData( + source: .touchMove, + positions: points.map { p in MouseMoveEventData.Position( + x: p.position.x + positionCorrection.x, + y: p.position.y + positionCorrection.y, + id: imageId, + timeOffset: p.timestamp - interaction.timestamp) }) + + default: + Optional.none + } { + let event = Event(type: .IncrementalSnapshot, + data: AnyEventData(touchEventData), + timestamp: interaction.timestamp, + _sid: nextSid) + events.append(event) + } + + if let clickEvent = clickEvent(interaction: interaction) { + events.append(clickEvent) + } + } + + func clickEvent(interaction: UIInteraction) -> Event? { + guard case .touchDown = interaction.kind else { return nil } + + let viewName = interaction.target?.className + let eventData = CustomEventData(tag: .click, payload: ClickPayload( + clickTarget: interaction.target?.className ?? "", + clickTextContent: interaction.target?.accessibilityIdentifier ?? "", + clickSelector: interaction.target?.accessibilityIdentifier ?? "view")) + let event = Event(type: .Custom, + data: AnyEventData(eventData), + timestamp: interaction.timestamp, + _sid: nextSid) + return event + } + + + + func windowEvent(href: String, width: Int, height: Int, timestamp: TimeInterval) -> Event { + let eventData = EventData(href: href, width: width, height: height) + let event = Event(type: .Meta, + data: AnyEventData(eventData), + timestamp: timestamp, + _sid: nextSid) + return event + } + + func reloadEvent(timestamp: TimeInterval) -> Event { + let eventData = CustomEventData(tag: .reload, payload: "iOS Demo") + let event = Event(type: .Custom, + data: AnyEventData(eventData), + timestamp: timestamp, + _sid: nextSid) + return event + } + + func viewPortEvent(exportImage: ExportImage, timestamp: TimeInterval) -> Event { + let payload = ViewportPayload(width: exportImage.originalWidth, + height: exportImage.originalHeight, + availWidth: exportImage.originalWidth, + availHeight: exportImage.originalHeight, + colorDepth: 30, + pixelDepth: 30, + orientation: Int.random(in: 0...1)) + let eventData = CustomEventData(tag: .viewport, payload: payload) + let event = Event(type: .Custom, + data: AnyEventData(eventData), + timestamp: timestamp, + _sid: nextSid) + return event + } + + func drawImageEvent(exportImage: ExportImage, timestamp: TimeInterval, imageId: Int) -> Event { + let clearRectCommand = ClearRect(x: 0, y: 0, width: exportImage.originalWidth, height: exportImage.originalHeight) + let arrayBuffer = RRArrayBuffer(base64: exportImage.data.base64EncodedString()) + let blob = AnyRRNode(RRBlob(data: [AnyRRNode(arrayBuffer)], type: exportImage.mimeType)) + let drawImageCommand = DrawImage(image: AnyRRNode(RRImageBitmap(args: [blob])), + dx: 0, + dy: 0, + dw: exportImage.originalWidth, + dh: exportImage.originalHeight) + + let eventData = CanvasDrawData(source: .canvasMutation, + id: imageId, + type: .mouseUp, + commands: [ + AnyCommand(clearRectCommand), + AnyCommand(drawImageCommand) + ]) + let event = Event(type: .IncrementalSnapshot, data: AnyEventData(eventData), timestamp: timestamp, _sid: nextSid) + return event + } + + func mouseEvent(timestamp: TimeInterval, x: CGFloat, y: CGFloat, timeOffset: TimeInterval) -> Event? { + guard let imageId else { return nil } + + let eventData = MouseMoveEventData(source: .mouseMove, positions: [.init(x: x, y: y, id: imageId, timeOffset: timeOffset)]) + let event = Event(type: .IncrementalSnapshot, + data: AnyEventData(eventData), + timestamp: timestamp, + _sid: nextSid) + return event + } + + func fullSnapshotEvent(exportImage: ExportImage, timestamp: TimeInterval, isEmpty: Bool) -> Event { + id = 0 + let rootNode = fullSnapshotNode(exportImage: exportImage, emtpyCanvas: isEmpty) + let eventData = EventData(node: rootNode) + let event = Event(type: .FullSnapshot, data: AnyEventData(eventData), timestamp: timestamp, _sid: nextSid) + return event + } + + func fullSnapshotNode(exportImage: ExportImage, emtpyCanvas: Bool) -> EventNode { + var rootNode = EventNode(id: nextId, type: .Document) + let htmlDocNode = EventNode(id: nextId, type: .DocumentType, name: "html") + rootNode.childNodes.append(htmlDocNode) + + let htmlNode = EventNode(id: nextId, type: .Element, tagName: "html", attributes: ["lang": "en"], childNodes: [ + EventNode(id: nextId, type: .Element, tagName: "head", attributes: [:]), + EventNode(id: nextId, type: .Element, tagName: "body", attributes: [:], childNodes: [ + exportImage.eventNode(id: nextId, use_rr_dataURL: !emtpyCanvas) + ]), + ]) + imageId = id + rootNode.childNodes.append(htmlNode) + + return rootNode + } + + private func appendFullSnapshotEvents(_ exportImage: ExportImage, _ timestamp: TimeInterval, _ events: inout [Event]) { + events.append(windowEvent(href: "", width: exportImage.paddedWidth, height: exportImage.paddedHeight, timestamp: timestamp)) + events.append(fullSnapshotEvent(exportImage: exportImage, timestamp: timestamp, isEmpty: false)) + + // Workaround to solve session player flicker. TODO: optimize but not generating base64 twice in case it persists + events.append(fullSnapshotEvent(exportImage: exportImage, timestamp: timestamp, isEmpty: true)) + if let imageId { + events.append(drawImageEvent(exportImage: exportImage, timestamp: timestamp, imageId: imageId)) + } + + events.append(viewPortEvent(exportImage: exportImage, timestamp: timestamp)) + } +} + +extension ScreenImageItem: SessionReplayItemPayload { + func sessionReplayEvent() -> Event? { + return nil + } +} diff --git a/Sources/SessionReplay/Exporter/SessionReplayExporter.swift b/Sources/SessionReplay/Exporter/SessionReplayExporter.swift index 3b65d1f..eb21a90 100644 --- a/Sources/SessionReplay/Exporter/SessionReplayExporter.swift +++ b/Sources/SessionReplay/Exporter/SessionReplayExporter.swift @@ -1,11 +1,17 @@ import Foundation import Common import Observability +import OSLog actor SessionReplayExporter: EventExporting { let replayApiService: SessionReplayAPIService let context: SessionReplayContext let sessionManager: SessionManaging + var isInitializing = false + var eventGenerator: SessionReplayEventGenerator + var log: OSLog + var initializedSession: InitializeSessionResponse? + var sessionInfo: SessionInfo? var payloadId = 0 var nextPayloadId: Int { @@ -13,149 +19,68 @@ actor SessionReplayExporter: EventExporting { return payloadId } - var sid = 0 - var nextSid: Int { - sid += 1 - return sid - } - - var id = 16 - var nextId: Int { - id += 1 - return id - } - - var imageId: Int? - var currentSession: InitializeSessionResponse? - var lastExportImage: ExportImage? - var shouldReload = true - - init(context: SessionReplayContext, sessionManager: SessionManaging, replayApiService: SessionReplayAPIService) { + init(context: SessionReplayContext, + sessionManager: SessionManaging, + replayApiService: SessionReplayAPIService) { self.context = context self.replayApiService = replayApiService self.sessionManager = sessionManager + self.eventGenerator = SessionReplayEventGenerator() + self.log = context.log + self.sessionInfo = sessionManager.sessionInfo + + Task(priority: .background) { + for await newSessionInfo in await self.sessionManager.sessionChanges() { + await updateSessionInfo(newSessionInfo) + } + } } - var notScreenItems = [EventQueueItem]() - var fakePayloadOnce = false - let positionCorrection = ExportImage.padding - - func export(items: [EventQueueItem]) async throws { - if currentSession == nil { - let session = try await initializeSession(sessionSecureId: sessionManager.sessionInfo.id) - try await identifySession(session: session) - currentSession = session - } - - var events = [Event]() - for item in items { - appendEvents(item: item, events: &events) - } - - guard events.isNotEmpty else { return } - - try await pushPayload(events: events) + func updateSessionInfo(_ sessionInfo: SessionInfo) async { + self.sessionInfo = sessionInfo + self.eventGenerator = SessionReplayEventGenerator() + self.initializedSession = nil } - func appendEvents(item: EventQueueItem, events: inout [Event]) { - switch item.payload { - case let payload as ScreenImageItem: - let exportImage = payload.exportImage - guard lastExportImage != exportImage else { - return - } + private func initializeSessionIfNeeded() async throws { + if initializedSession == nil { + guard !isInitializing else { return } + isInitializing = true defer { - lastExportImage = exportImage + isInitializing = false } - let timestamp = item.timestamp - if shouldReload { - // TODO: make it through real event, when we subscribe device events - events.append(reloadEvent(timestamp: timestamp)) - // fake movement - if let mouseEvent = mouseEvent(timestamp: timestamp, x: 0, y: 0, timeOffset: 0) { - events.append(mouseEvent) + do { + guard let sessionInfo else { + return } - shouldReload = false + + let session = try await initializeSession(sessionSecureId: sessionInfo.id) + try await identifySession(session: session) + initializedSession = session + } catch { + initializedSession = nil + os_log("%{public}@", log: log, type: .error, "Failed to initialize Session Replay:\n\(error)") } - - if let imageId, - let lastExportImage, - lastExportImage.originalWidth == exportImage.originalWidth, - lastExportImage.originalHeight == exportImage.originalHeight { - events.append(drawImageEvent(exportImage: exportImage, timestamp: timestamp, imageId: imageId)) - } else { - // if screen changed size we send fullSnapshot as canvas resizing might take to many hours on the server - appendFullSnapshotEvents(exportImage, timestamp, &events) - } - - case let interaction as UIInteraction: - appendTouchInteraction(interaction: interaction, events: &events) - default: - () // } } - fileprivate func appendTouchInteraction(interaction: UIInteraction, events: inout [Event]) { - if let touchEventData: EventDataProtocol = switch interaction.kind { - case .touchDown(let point): - EventData(source: .mouseInteraction, - type: .touchStart, - id: imageId, - x: point.x + positionCorrection.x, - y: point.y + positionCorrection.y) - - case .touchUp(let point): - EventData(source: .mouseInteraction, - type: .touchEnd, - id: imageId, - x: point.x + positionCorrection.x, - y: point.y + positionCorrection.y) - - case .touchPath(let points): - MouseMoveEventData( - source: .touchMove, - positions: points.map { p in MouseMoveEventData.Position( - x: p.position.x + positionCorrection.x, - y: p.position.y + positionCorrection.y, - id: imageId, - timeOffset: p.timestamp - interaction.timestamp) }) - - default: - Optional.none - } { - let event = Event(type: .IncrementalSnapshot, - data: AnyEventData(touchEventData), - timestamp: interaction.timestamp, - _sid: nextSid) - events.append(event) - } + func export(items: [EventQueueItem]) async throws { + try await initializeSessionIfNeeded() - if let clickEvent = clickEvent(interaction: interaction) { - events.append(clickEvent) - } - } - - func clickEvent(interaction: UIInteraction) -> Event? { - guard case .touchDown = interaction.kind else { return nil } + guard let initializedSession else { return } + + let events = await eventGenerator.generateEvents(items: items) + guard events.isNotEmpty else { return } - let viewName = interaction.target?.className - let eventData = CustomEventData(tag: .click, payload: ClickPayload( - clickTarget: interaction.target?.className ?? "", - clickTextContent: interaction.target?.accessibilityIdentifier ?? "", - clickSelector: interaction.target?.accessibilityIdentifier ?? "view")) - let event = Event(type: .Custom, - data: AnyEventData(eventData), - timestamp: interaction.timestamp, - _sid: nextSid) - return event + try await pushPayload(events: events) } func pushPayload(events: [Event]) async throws { - guard let currentSession else { return } + guard let initializedSession else { return } guard events.isNotEmpty else { return } - let input = PushPayloadVariables(sessionSecureId: currentSession.secureId, payloadId: "\(nextPayloadId)", events: events) + let input = PushPayloadVariables(sessionSecureId: initializedSession.secureId, payloadId: "\(nextPayloadId)", events: events) try await replayApiService.pushPayload(input) } @@ -172,134 +97,6 @@ actor SessionReplayExporter: EventExporting { "telemetry.sdk.version":"3.8.1", "feature_flag.set.id":"548f6741c1efad40031b18ae", "feature_flag.provider.name":"LaunchDarkly", - "key":"unknown"]) - } - - func windowEvent(href: String, width: Int, height: Int, timestamp: TimeInterval) -> Event { - let eventData = EventData(href: href, width: width, height: height) - let event = Event(type: .Meta, - data: AnyEventData(eventData), - timestamp: timestamp, - _sid: nextSid) - return event - } - - func reloadEvent(timestamp: TimeInterval) -> Event { - let eventData = CustomEventData(tag: .reload, payload: "iOS Demo") - let event = Event(type: .Custom, - data: AnyEventData(eventData), - timestamp: timestamp, - _sid: nextSid) - return event - } - - func viewPortEvent(exportImage: ExportImage, timestamp: TimeInterval) -> Event { - let payload = ViewportPayload(width: exportImage.originalWidth, - height: exportImage.originalHeight, - availWidth: exportImage.originalWidth, - availHeight: exportImage.originalHeight, - colorDepth: 30, - pixelDepth: 30, - orientation: Int.random(in: 0...1)) - let eventData = CustomEventData(tag: .viewport, payload: payload) - let event = Event(type: .Custom, - data: AnyEventData(eventData), - timestamp: timestamp, - _sid: nextSid) - return event - } - - func drawImageEvent(exportImage: ExportImage, timestamp: TimeInterval, imageId: Int) -> Event { - let clearRectCommand = ClearRect(x: 0, y: 0, width: exportImage.originalWidth, height: exportImage.originalHeight) - let arrayBuffer = RRArrayBuffer(base64: exportImage.data.base64EncodedString()) - let blob = AnyRRNode(RRBlob(data: [AnyRRNode(arrayBuffer)], type: exportImage.mimeType)) - let drawImageCommand = DrawImage(image: AnyRRNode(RRImageBitmap(args: [blob])), - dx: 0, - dy: 0, - dw: exportImage.originalWidth, - dh: exportImage.originalHeight) - - let eventData = CanvasDrawData(source: .canvasMutation, - id: imageId, - type: .mouseUp, - commands: [ - AnyCommand(clearRectCommand), - AnyCommand(drawImageCommand) - ]) - let event = Event(type: .IncrementalSnapshot, data: AnyEventData(eventData), timestamp: timestamp, _sid: nextSid) - return event - } - - func mouseEvent(timestamp: TimeInterval, x: CGFloat, y: CGFloat, timeOffset: TimeInterval) -> Event? { - guard let imageId else { return nil } - - let eventData = MouseMoveEventData(source: .mouseMove, positions: [.init(x: x, y: y, id: imageId, timeOffset: timeOffset)]) - let event = Event(type: .IncrementalSnapshot, - data: AnyEventData(eventData), - timestamp: timestamp, - _sid: nextSid) - return event - } - - func fullSnapshotEvent(exportImage: ExportImage, timestamp: TimeInterval, isEmpty: Bool) -> Event { - id = 0 - let rootNode = fullSnapshotNode(exportImage: exportImage, emtpyCanvas: isEmpty) - let eventData = EventData(node: rootNode) - let event = Event(type: .FullSnapshot, data: AnyEventData(eventData), timestamp: timestamp, _sid: nextSid) - return event - } - - func fullSnapshotNode(exportImage: ExportImage, emtpyCanvas: Bool) -> EventNode { - var rootNode = EventNode(id: nextId, type: .Document) - let htmlDocNode = EventNode(id: nextId, type: .DocumentType, name: "html") - rootNode.childNodes.append(htmlDocNode) - - let htmlNode = EventNode(id: nextId, type: .Element, tagName: "html", attributes: ["lang": "en"], childNodes: [ - EventNode(id: nextId, type: .Element, tagName: "head", attributes: [:]), - EventNode(id: nextId, type: .Element, tagName: "body", attributes: [:], childNodes: [ - exportImage.eventNode(id: nextId, use_rr_dataURL: !emtpyCanvas) - ]), - ]) - imageId = id - rootNode.childNodes.append(htmlNode) - - return rootNode - } - - private func appendFullSnapshotEvents(_ exportImage: ExportImage, _ timestamp: TimeInterval, _ events: inout [Event]) { - events.append(windowEvent(href: "", width: exportImage.paddedWidth, height: exportImage.paddedHeight, timestamp: timestamp)) - events.append(fullSnapshotEvent(exportImage: exportImage, timestamp: timestamp, isEmpty: false)) - - // Workaround to solve session player flicker. TODO: optimize but not generating base64 twice in case it persists - events.append(fullSnapshotEvent(exportImage: exportImage, timestamp: timestamp, isEmpty: true)) - if let imageId { - events.append(drawImageEvent(exportImage: exportImage, timestamp: timestamp, imageId: imageId)) - } - - events.append(viewPortEvent(exportImage: exportImage, timestamp: timestamp)) - } - - func pushPayloadFullSnapshot(session: InitializeSessionResponse, exportImage: ExportImage? = nil, timestamp: TimeInterval) async throws { - var events = [Event]() - guard let exportImage else { - return - } - - appendFullSnapshotEvents(exportImage, timestamp, &events) - - let input = PushPayloadVariables(sessionSecureId: session.secureId, payloadId: "\(nextPayloadId)", events: events) - try await replayApiService.pushPayload(input) - } - - func pushPayloadDrawImage(session: InitializeSessionResponse, timestamp: TimeInterval, exportImage: ExportImage, imageId: Int) async throws { - let event = drawImageEvent(exportImage: exportImage, timestamp: timestamp, imageId: imageId) - let input = PushPayloadVariables(sessionSecureId: session.secureId, payloadId: "\(nextPayloadId)", events: [event]) - try await replayApiService.pushPayload(input) - } -} - -extension ScreenImageItem: SessionReplayItemPayload { - func sessionReplayEvent() -> Event? { - return nil + "key":"test"]) } } diff --git a/Sources/SessionReplay/ScreenCapture/ScreenCaptureService.swift b/Sources/SessionReplay/ScreenCapture/ScreenCaptureService.swift index d7870ee..3adb1bd 100644 --- a/Sources/SessionReplay/ScreenCapture/ScreenCaptureService.swift +++ b/Sources/SessionReplay/ScreenCapture/ScreenCaptureService.swift @@ -93,10 +93,6 @@ public final class ScreenCaptureService { context.restoreGState() } } - -// private func runOnMain(_ work: @escaping () -> Void) { -// if Thread.isMainThread { work() } else { DispatchQueue.main.async { work() } } -// } } #endif diff --git a/Sources/SessionReplay/ScreenCapture/SnapshotTaker.swift b/Sources/SessionReplay/ScreenCapture/SnapshotTaker.swift index 7675afb..fa5dfe9 100644 --- a/Sources/SessionReplay/ScreenCapture/SnapshotTaker.swift +++ b/Sources/SessionReplay/ScreenCapture/SnapshotTaker.swift @@ -3,14 +3,38 @@ import Observability typealias ExportImageYield = @Sendable (ExportImage) async -> Void -class SnapshotTaker: EventSource { - let captureService: ScreenCaptureService - var timer: Timer? +final class SnapshotTaker: EventSource { + private let captureService: ScreenCaptureService private let yield: ExportImageYield - - init(captureService: ScreenCaptureService, yield: @escaping ExportImageYield) { + private let appLifecycleManager: AppLifecycleManaging + private var timer: Timer? + + init(captureService: ScreenCaptureService, + appLifecycleManager: AppLifecycleManaging, + yield: @escaping ExportImageYield) { self.captureService = captureService self.yield = yield + self.appLifecycleManager = appLifecycleManager + + let _appLifecycleManager = appLifecycleManager + Task(priority: .background) { [weak self] in + guard let self else { return } + + for await event in await _appLifecycleManager.events() { + switch event { + case .didBecomeActive: + await MainActor.run { [weak self] in + start() + } + case .willResignActive, .willTerminate: + await MainActor.run { [weak self] in + stop() + } + case .didFinishLaunching, .willEnterForeground, .didEnterBackground: + () // NO-OP + } + } + } } func start() { @@ -27,7 +51,8 @@ class SnapshotTaker: EventSource { } @objc func queueSnapshot() { - guard let capturedImage = captureService.captureUIImage() else { + guard let timer, + let capturedImage = captureService.captureUIImage() else { return } diff --git a/Sources/SessionReplay/SessionReplayService.swift b/Sources/SessionReplay/SessionReplayService.swift index 2fe3caf..a042a42 100644 --- a/Sources/SessionReplay/SessionReplayService.swift +++ b/Sources/SessionReplay/SessionReplayService.swift @@ -1,6 +1,7 @@ import Foundation import Common import Observability +import OSLog struct ScreenImageItem: EventQueueItemPayload { var timestamp: TimeInterval { @@ -22,16 +23,21 @@ public struct SessionReplayContext { public var sdkKey: String public var serviceName: String public var backendUrl: URL + public var log: OSLog - public init(sdkKey: String, serviceName: String, backendUrl: URL) { + public init(sdkKey: String, + serviceName: String, + backendUrl: URL, + log: OSLog) { self.sdkKey = sdkKey self.serviceName = serviceName self.backendUrl = backendUrl + self.log = log } } public final class SessionReplayService { - let screenshotManager: SnapshotTaker + let snapshotTaker: SnapshotTaker var transportService: TransportServicing public init(context: ObservabilityContext, @@ -43,14 +49,16 @@ public final class SessionReplayService { let captureService = ScreenCaptureService(options: sessonReplayOptions) self.transportService = context.transportService - self.screenshotManager = SnapshotTaker(captureService: captureService) { exportImage in + self.snapshotTaker = SnapshotTaker(captureService: captureService, + appLifecycleManager: context.appLifecycleManager) { exportImage in await context.transportService.eventQueue.send(EventQueueItem(payload: ScreenImageItem(exportImage: exportImage))) } let sessionReplayContext = SessionReplayContext( sdkKey: context.sdkKey, serviceName: context.options.serviceName, - backendUrl: url) + backendUrl: url, + log: context.options.log) let replayApiService = SessionReplayAPIService(gqlClient: graphQLClient) let replayPushService = SessionReplayExporter(context: sessionReplayContext, @@ -59,7 +67,6 @@ public final class SessionReplayService { Task { await transportService.batchWorker.addExporter(replayPushService) } - screenshotManager.start() // it maybe already started if observability plugin is used. transportService.start() diff --git a/TestApp/Sources/AppDelegate.swift b/TestApp/Sources/AppDelegate.swift index 9bacea4..786d18c 100644 --- a/TestApp/Sources/AppDelegate.swift +++ b/TestApp/Sources/AppDelegate.swift @@ -2,35 +2,32 @@ import UIKit import LaunchDarkly import LaunchDarklyObservability import LaunchDarklySessionReplay -//import SessionReplay //let mobileKey = "mob-48fd3788-eab7-4b72-b607-e41712049dbd" //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)), SessionReplay(options: .init( isEnabled: true, @@ -41,6 +38,7 @@ let config = { () -> LDConfig in ) )) ] + return config }() From 0022e8062b8d744c3a264f3023412b88f5f6ca0c Mon Sep 17 00:00:00 2001 From: Andrey Belonogov Date: Thu, 23 Oct 2025 10:31:46 -0700 Subject: [PATCH 3/7] initial version applifecycler logger --- .../Session/AppLifecycleLogger.swift | 34 +++++++++++++++++++ ...anager.swift => AppLifecycleManager.swift} | 0 2 files changed, 34 insertions(+) create mode 100644 Sources/Observability/Session/AppLifecycleLogger.swift rename Sources/Observability/Session/{AppLifeCycleManager.swift => AppLifecycleManager.swift} (100%) diff --git a/Sources/Observability/Session/AppLifecycleLogger.swift b/Sources/Observability/Session/AppLifecycleLogger.swift new file mode 100644 index 0000000..22da233 --- /dev/null +++ b/Sources/Observability/Session/AppLifecycleLogger.swift @@ -0,0 +1,34 @@ +import Foundation + +enum AppLifeCycleLogState: String, Sendable { + // The app has become active. Associated with UIKit notification applicationDidBecomeActive. + case active = "active" + // The app is now in the background. This value is associated with UIKit notification applicationDidEnterBackground. + case background = "background" + // The app is now in the foreground. This value is associated with UIKit notification applicationWillEnterForeground. + case foreground = "foreground" + // The app is now inactive. Associated with UIKit notification applicationWillResignActive. Development + case inactive = "inactive" + // The app is about to terminate. Associated with UIKit notification applicationWillTerminate. + case terminate = "terminate" +} + +public protocol AppLifecycleLogging { + +} + +final class AppLifecycleLogger: AppLifecycleLogging { + let appLifecycleManager: AppLifecycleManaging + + init(appLifecycleManager: AppLifecycleManaging) { + self.appLifecycleManager = appLifecycleManager + + Task(priority: .background) { [weak self] in + guard let self else { return } + + for await event in await appLifecycleManager.events() { + + } + } + } +} diff --git a/Sources/Observability/Session/AppLifeCycleManager.swift b/Sources/Observability/Session/AppLifecycleManager.swift similarity index 100% rename from Sources/Observability/Session/AppLifeCycleManager.swift rename to Sources/Observability/Session/AppLifecycleManager.swift From 0685d67c5a6a37555b0d02bf079a8f8a924034d2 Mon Sep 17 00:00:00 2001 From: Andrey Belonogov Date: Fri, 24 Oct 2025 06:47:58 -0700 Subject: [PATCH 4/7] delete logging stuff --- .../Session/AppLifecycleLogger.swift | 34 ------------------- 1 file changed, 34 deletions(-) delete mode 100644 Sources/Observability/Session/AppLifecycleLogger.swift diff --git a/Sources/Observability/Session/AppLifecycleLogger.swift b/Sources/Observability/Session/AppLifecycleLogger.swift deleted file mode 100644 index 22da233..0000000 --- a/Sources/Observability/Session/AppLifecycleLogger.swift +++ /dev/null @@ -1,34 +0,0 @@ -import Foundation - -enum AppLifeCycleLogState: String, Sendable { - // The app has become active. Associated with UIKit notification applicationDidBecomeActive. - case active = "active" - // The app is now in the background. This value is associated with UIKit notification applicationDidEnterBackground. - case background = "background" - // The app is now in the foreground. This value is associated with UIKit notification applicationWillEnterForeground. - case foreground = "foreground" - // The app is now inactive. Associated with UIKit notification applicationWillResignActive. Development - case inactive = "inactive" - // The app is about to terminate. Associated with UIKit notification applicationWillTerminate. - case terminate = "terminate" -} - -public protocol AppLifecycleLogging { - -} - -final class AppLifecycleLogger: AppLifecycleLogging { - let appLifecycleManager: AppLifecycleManaging - - init(appLifecycleManager: AppLifecycleManaging) { - self.appLifecycleManager = appLifecycleManager - - Task(priority: .background) { [weak self] in - guard let self else { return } - - for await event in await appLifecycleManager.events() { - - } - } - } -} From 1c39c74d97f1e0c95d5b9a15456c7260797c455a Mon Sep 17 00:00:00 2001 From: Andrey Belonogov Date: Fri, 24 Oct 2025 10:11:06 -0700 Subject: [PATCH 5/7] Fix: memory leak --- Sources/Observability/Session/SessionManager.swift | 11 ++++++----- .../ScreenCapture/SnapshotTaker.swift | 14 +++++++------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Sources/Observability/Session/SessionManager.swift b/Sources/Observability/Session/SessionManager.swift index 5c5a6e1..ec44d1e 100644 --- a/Sources/Observability/Session/SessionManager.swift +++ b/Sources/Observability/Session/SessionManager.swift @@ -25,11 +25,12 @@ final class SessionManager: SessionManaging { self._sessionInfo = SessionInfo() self.broadcaster = Broadcaster() - Task(priority: .background) { [weak self] in - guard let self else { return } - - for await event in await appLifecycleManager.events() { - transition(to: event) + Task(priority: .background) { [weak self, weak appLifecycleManager] in + guard let self, let appLifecycleManager else { return } + + let eventsStream = await appLifecycleManager.events() + for await event in eventsStream { + self.transition(to: event) } } } diff --git a/Sources/SessionReplay/ScreenCapture/SnapshotTaker.swift b/Sources/SessionReplay/ScreenCapture/SnapshotTaker.swift index fa5dfe9..620de40 100644 --- a/Sources/SessionReplay/ScreenCapture/SnapshotTaker.swift +++ b/Sources/SessionReplay/ScreenCapture/SnapshotTaker.swift @@ -16,19 +16,19 @@ final class SnapshotTaker: EventSource { self.yield = yield self.appLifecycleManager = appLifecycleManager - let _appLifecycleManager = appLifecycleManager - Task(priority: .background) { [weak self] in - guard let self else { return } - - for await event in await _appLifecycleManager.events() { + Task(priority: .background) { [weak self, weak appLifecycleManager] in + guard let self, let appLifecycleManager else { return } + + let eventsStream = await appLifecycleManager.events() + for await event in eventsStream { switch event { case .didBecomeActive: await MainActor.run { [weak self] in - start() + self?.start() } case .willResignActive, .willTerminate: await MainActor.run { [weak self] in - stop() + self?.stop() } case .didFinishLaunching, .willEnterForeground, .didEnterBackground: () // NO-OP From 2ddcfca63533aab565ab40b317eeb9c6684f8890 Mon Sep 17 00:00:00 2001 From: Andrey Belonogov Date: Fri, 24 Oct 2025 11:06:24 -0700 Subject: [PATCH 6/7] Adress coments --- Sources/Common/Broadcaster.swift | 6 +++--- .../SessionReplay/Operations/SessionReplayAPIService.swift | 1 - Sources/SessionReplay/Queries/payloadEncoded.txt | 1 - Sources/SessionReplay/ScreenCapture/SnapshotTaker.swift | 2 +- Sources/SessionReplay/SessionReplayService.swift | 1 + 5 files changed, 5 insertions(+), 6 deletions(-) delete mode 100644 Sources/SessionReplay/Queries/payloadEncoded.txt diff --git a/Sources/Common/Broadcaster.swift b/Sources/Common/Broadcaster.swift index 297d707..533be6d 100644 --- a/Sources/Common/Broadcaster.swift +++ b/Sources/Common/Broadcaster.swift @@ -2,7 +2,7 @@ import Foundation public actor Broadcaster { private var continuations = [Int: AsyncStream.Continuation]() - private var nextIdentifier = 0 + private var streamIdentifier = 0 private var finished = false public init() { } @@ -10,8 +10,8 @@ public actor Broadcaster { public func stream( bufferingPolicy: AsyncStream.Continuation.BufferingPolicy = .unbounded ) -> AsyncStream { - let id = nextIdentifier - nextIdentifier &+= 1 + let id = streamIdentifier + streamIdentifier &+= 1 var continuationRef: AsyncStream.Continuation? let stream = AsyncStream(bufferingPolicy: bufferingPolicy) { continuation in diff --git a/Sources/SessionReplay/Operations/SessionReplayAPIService.swift b/Sources/SessionReplay/Operations/SessionReplayAPIService.swift index c7adfde..492e2c7 100644 --- a/Sources/SessionReplay/Operations/SessionReplayAPIService.swift +++ b/Sources/SessionReplay/Operations/SessionReplayAPIService.swift @@ -18,5 +18,4 @@ public final class SessionReplayAPIService { network: networkClient, defaultHeaders: headers)) } - } diff --git a/Sources/SessionReplay/Queries/payloadEncoded.txt b/Sources/SessionReplay/Queries/payloadEncoded.txt deleted file mode 100644 index 906c074..0000000 --- a/Sources/SessionReplay/Queries/payloadEncoded.txt +++ /dev/null @@ -1 +0,0 @@ -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 diff --git a/Sources/SessionReplay/ScreenCapture/SnapshotTaker.swift b/Sources/SessionReplay/ScreenCapture/SnapshotTaker.swift index 620de40..b664a28 100644 --- a/Sources/SessionReplay/ScreenCapture/SnapshotTaker.swift +++ b/Sources/SessionReplay/ScreenCapture/SnapshotTaker.swift @@ -31,7 +31,7 @@ final class SnapshotTaker: EventSource { self?.stop() } case .didFinishLaunching, .willEnterForeground, .didEnterBackground: - () // NO-OP + continue } } } diff --git a/Sources/SessionReplay/SessionReplayService.swift b/Sources/SessionReplay/SessionReplayService.swift index a042a42..33555da 100644 --- a/Sources/SessionReplay/SessionReplayService.swift +++ b/Sources/SessionReplay/SessionReplayService.swift @@ -53,6 +53,7 @@ public final class SessionReplayService { appLifecycleManager: context.appLifecycleManager) { exportImage in await context.transportService.eventQueue.send(EventQueueItem(payload: ScreenImageItem(exportImage: exportImage))) } + snapshotTaker.start() let sessionReplayContext = SessionReplayContext( sdkKey: context.sdkKey, From 49146f1c76af8a7e81d4a49a75b73b4b0d7bfef3 Mon Sep 17 00:00:00 2001 From: Andrey Belonogov Date: Fri, 24 Oct 2025 11:38:18 -0700 Subject: [PATCH 7/7] fix unit tests add SessionManagerMemoryTests --- Sources/Common/GraphQL/GraphQLClient.swift | 2 + .../Session/SessionManager.swift | 4 +- .../Exporter/SessionReplayExporter.swift | 16 ++++-- .../Masking/MaskingElementsViewModel.swift | 2 +- .../ScreenshotTester/SnapshotButton.swift | 2 +- .../Session/SessionManagerMemoryTests.swift | 54 +++++++++++++++++++ 6 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 Tests/ObservabilityTests/Session/SessionManagerMemoryTests.swift diff --git a/Sources/Common/GraphQL/GraphQLClient.swift b/Sources/Common/GraphQL/GraphQLClient.swift index 35ade84..7ba26c8 100644 --- a/Sources/Common/GraphQL/GraphQLClient.swift +++ b/Sources/Common/GraphQL/GraphQLClient.swift @@ -62,6 +62,8 @@ public final class GraphQLClient { return value } catch let error as GraphQLClientError { throw error + } catch let error as NetworkError { + throw error } catch { throw GraphQLClientError.decoding(error) } diff --git a/Sources/Observability/Session/SessionManager.swift b/Sources/Observability/Session/SessionManager.swift index ec44d1e..eb99676 100644 --- a/Sources/Observability/Session/SessionManager.swift +++ b/Sources/Observability/Session/SessionManager.swift @@ -9,12 +9,12 @@ public protocol SessionManaging { } final class SessionManager: SessionManaging { - let appLifecycleManager: AppLifecycleManaging + private let appLifecycleManager: AppLifecycleManaging private let options: SessionOptions private let broadcaster: Broadcaster private var _sessionInfo = SessionInfo() private var backgroundTime: DispatchTime? - + private let stateQueue = DispatchQueue( label: "com.launchdarkly.observability.state-queue", attributes: .concurrent) diff --git a/Sources/SessionReplay/Exporter/SessionReplayExporter.swift b/Sources/SessionReplay/Exporter/SessionReplayExporter.swift index eb21a90..3016517 100644 --- a/Sources/SessionReplay/Exporter/SessionReplayExporter.swift +++ b/Sources/SessionReplay/Exporter/SessionReplayExporter.swift @@ -12,6 +12,7 @@ actor SessionReplayExporter: EventExporting { var log: OSLog var initializedSession: InitializeSessionResponse? var sessionInfo: SessionInfo? + private var sessionChangesTask: Task? var payloadId = 0 var nextPayloadId: Int { @@ -29,11 +30,14 @@ actor SessionReplayExporter: EventExporting { self.log = context.log self.sessionInfo = sessionManager.sessionInfo - Task(priority: .background) { - for await newSessionInfo in await self.sessionManager.sessionChanges() { - await updateSessionInfo(newSessionInfo) + self.sessionChangesTask = Task(priority: .background) { [weak self, sessionManager] in + let stream = await sessionManager.sessionChanges() + for await newSessionInfo in stream { + guard let self else { break } + await self.updateSessionInfo(newSessionInfo) + if Task.isCancelled { break } } - } + } } func updateSessionInfo(_ sessionInfo: SessionInfo) async { @@ -99,4 +103,8 @@ actor SessionReplayExporter: EventExporting { "feature_flag.provider.name":"LaunchDarkly", "key":"test"]) } + + deinit { + sessionChangesTask?.cancel() + } } diff --git a/TestApp/Sources/SessionReplay/Masking/MaskingElementsViewModel.swift b/TestApp/Sources/SessionReplay/Masking/MaskingElementsViewModel.swift index facdb0d..60cc41e 100644 --- a/TestApp/Sources/SessionReplay/Masking/MaskingElementsViewModel.swift +++ b/TestApp/Sources/SessionReplay/Masking/MaskingElementsViewModel.swift @@ -8,7 +8,7 @@ final class MaskingElementsViewModel: ObservableObject { var capturedImage: CapturedImage? @Published var isImagePresented: Bool = false - func captureScreenShot() { + func captureShapShot() { guard let image = screenCaptureService.captureUIImage() else { return } diff --git a/TestApp/Sources/SessionReplay/ScreenshotTester/SnapshotButton.swift b/TestApp/Sources/SessionReplay/ScreenshotTester/SnapshotButton.swift index e784c3e..6102223 100644 --- a/TestApp/Sources/SessionReplay/ScreenshotTester/SnapshotButton.swift +++ b/TestApp/Sources/SessionReplay/ScreenshotTester/SnapshotButton.swift @@ -5,7 +5,7 @@ struct SnapshotButton: View { var body: some View { Button { - viewModel.captureScreenShot() + viewModel.captureShapShot() } label: { Image(systemName: "camera") }.sheet(isPresented: $viewModel.isImagePresented) { diff --git a/Tests/ObservabilityTests/Session/SessionManagerMemoryTests.swift b/Tests/ObservabilityTests/Session/SessionManagerMemoryTests.swift new file mode 100644 index 0000000..b4c4a72 --- /dev/null +++ b/Tests/ObservabilityTests/Session/SessionManagerMemoryTests.swift @@ -0,0 +1,54 @@ +import Testing +import OSLog +@testable import Observability + +private final class TestLifecycleManager: AppLifecycleManaging { + private var continuation: AsyncStream.Continuation? + + func events() async -> AsyncStream { + AsyncStream { continuation in + self.continuation = continuation + continuation.onTermination = { [weak self] _ in + self?.continuation = nil + } + } + } + + func send(_ event: AppLifeCycleEvent) { + continuation?.yield(event) + } + + func finish() { + continuation?.finish() + } +} + +struct SessionManagerMemoryTests { + @Test("SessionManager deallocates after release (no memory leak)") + func sessionManagerDeallocatesAfterRelease() { + // Given + let lifecycle = TestLifecycleManager() + let options = SessionOptions(timeout: 0.1, isDebug: false, log: OSLog(subsystem: "test", category: "SessionManagerMemoryTests")) + + weak var weakManager: SessionManager? + + autoreleasepool { + let manager = SessionManager(options: options, appLifecycleManager: lifecycle) + weakManager = manager + + // Drive at least one event through the loop to ensure the Task starts iterating + lifecycle.send(.didBecomeActive) + } + + // When: release strong references and give the runtime a moment to clean up + let deadline = Date().addingTimeInterval(2.0) + while weakManager != nil && Date() < deadline { + RunLoop.current.run(mode: .default, before: Date().addingTimeInterval(0.01)) + } + + // Then + #expect(weakManager == nil) + } +} + +