From c65b2cd217cf55e120ae60aed3ebdfcb4026f2d9 Mon Sep 17 00:00:00 2001 From: maxep <6815992+maxep@users.noreply.github.com> Date: Tue, 19 May 2026 18:16:35 +0200 Subject: [PATCH 1/4] [chain-pr 3/10] DatadogCore: CoreMessageBus implementation and rename --- Datadog/Datadog.xcodeproj/project.pbxproj | 44 +-- ...{MessageBus.swift => CoreMessageBus.swift} | 96 ++++++- DatadogCore/Sources/Core/DatadogCore.swift | 11 +- .../ContextSharingTransformer.swift | 18 +- .../Extensions/CrossPlatformExtension.swift | 4 +- .../DatadogCore/CoreMessageBusTests.swift | 266 ++++++++++++++++++ .../ContextSharingTransformerTests.swift | 24 +- .../DatadogURLSessionHandler.swift | 9 +- .../NetworkContextProvider.swift | 9 +- .../NetworkInstrumentationFeatureTests.swift | 14 +- .../Tests/Telemetry/TelemetryTests.swift | 15 +- 11 files changed, 422 insertions(+), 88 deletions(-) rename DatadogCore/Sources/Core/{MessageBus.swift => CoreMessageBus.swift} (52%) create mode 100644 DatadogCore/Tests/Datadog/DatadogCore/CoreMessageBusTests.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index a7dbc98c48..1e48501fab 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -644,7 +644,6 @@ 61BB2B1B244A185D009F3F56 /* PerformancePreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61BB2B1A244A185D009F3F56 /* PerformancePreset.swift */; }; 61BBD19724ED50040023E65F /* DatadogConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61BBD19624ED50040023E65F /* DatadogConfigurationTests.swift */; }; 61C363802436164B00C4D4E6 /* ObjcExceptionHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C3637F2436164B00C4D4E6 /* ObjcExceptionHandlerTests.swift */; }; - 61C4534A2C0A0BBF00CC4C17 /* TelemetryInterceptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C453492C0A0BBF00CC4C17 /* TelemetryInterceptorTests.swift */; }; 61C5A89624509BF600DA608C /* TracerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C5A89524509BF600DA608C /* TracerTests.swift */; }; 61C713A32A3B78F900FA735A /* RUMMonitorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C713A02A3B78F900FA735A /* RUMMonitorProtocol.swift */; }; 61C713A52A3B78F900FA735A /* RUMMonitorProtocol+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C713A12A3B78F900FA735A /* RUMMonitorProtocol+Internal.swift */; }; @@ -681,7 +680,6 @@ 61DA8CB828647A500074A606 /* InternalLoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DA8CB728647A500074A606 /* InternalLoggerTests.swift */; }; 61DB33B225DEDFC200F7EA71 /* CustomObjcViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 61DB33B125DEDFC200F7EA71 /* CustomObjcViewController.m */; }; 61DCC8472C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DCC8462C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift */; }; - 61DCC84E2C071DCD00CB59E5 /* TelemetryInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DCC84D2C071DCD00CB59E5 /* TelemetryInterceptor.swift */; }; 61E262D42EB2592C0041E70F /* DatadogFlags.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BA8C2ED2E784B3C00B1DA80 /* DatadogFlags.framework */; }; 61E5333824B84EE2003D6C4E /* DebugRUMViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E5333724B84EE2003D6C4E /* DebugRUMViewController.swift */; }; 61ED39D426C2A36B002C0F26 /* DataUploadStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61ED39D326C2A36B002C0F26 /* DataUploadStatus.swift */; }; @@ -859,12 +857,16 @@ D21A94F22B8397CA00AC4256 /* WebViewMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21A94F12B8397CA00AC4256 /* WebViewMessage.swift */; }; D21AE6BC29E5EDAF0064BF29 /* TelemetryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21AE6BB29E5EDAF0064BF29 /* TelemetryTests.swift */; }; D21C26C528A3B49C005DD405 /* FeatureStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21C26C428A3B49C005DD405 /* FeatureStorage.swift */; }; - D21C26D128A64599005DD405 /* MessageBusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21C26D028A64599005DD405 /* MessageBusTests.swift */; }; + D21C26D128A64599005DD405 /* CoreMessageBusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21C26D028A64599005DD405 /* CoreMessageBusTests.swift */; }; D22442C52CA301DA002E71E4 /* UIColor+SessionReplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22442C42CA301DA002E71E4 /* UIColor+SessionReplay.swift */; }; D224430429E9588100274EC7 /* TelemetryReceiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D214DAA729E54CB4004D0AE8 /* TelemetryReceiver.swift */; }; - D224430629E95C2C00274EC7 /* MessageBus.swift in Sources */ = {isa = PBXBuildFile; fileRef = D214DAA429E072D7004D0AE8 /* MessageBus.swift */; }; + D224430629E95C2C00274EC7 /* CoreMessageBus.swift in Sources */ = {isa = PBXBuildFile; fileRef = D214DAA429E072D7004D0AE8 /* CoreMessageBus.swift */; }; D224430D29E95D6700274EC7 /* CrashReportReceiverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D224430C29E95D6600274EC7 /* CrashReportReceiverTests.swift */; }; D224430F29E9779F00274EC7 /* TelemetryReceiverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D248ED4728081B9B00B315B4 /* TelemetryReceiverTests.swift */; }; + D224DFA82FAB8C4B000AED53 /* MessageBus.swift in Sources */ = {isa = PBXBuildFile; fileRef = D224DFA72FAB8C4B000AED53 /* MessageBus.swift */; }; + D224DFAA2FAB9DCE000AED53 /* MessageBusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D224DFA92FAB9DCE000AED53 /* MessageBusTests.swift */; }; + D224DFAE2FACFA43000AED53 /* RUMDataModels+BusMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D224DFAD2FACFA43000AED53 /* RUMDataModels+BusMessage.swift */; }; + D224DFB02FB1BBCA000AED53 /* WebViewBusMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = D224DFAF2FB1BBCA000AED53 /* WebViewBusMessages.swift */; }; D22689B92EB12C3100875E44 /* Filters in Frameworks */ = {isa = PBXBuildFile; productRef = D22689B82EB12C3100875E44 /* Filters */; }; D22689BB2EB12C3100875E44 /* Recording in Frameworks */ = {isa = PBXBuildFile; productRef = D22689BA2EB12C3100875E44 /* Recording */; }; D22689C32EB12D3D00875E44 /* KSCrashPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22689C22EB12D3D00875E44 /* KSCrashPlugin.swift */; }; @@ -1208,7 +1210,6 @@ D2C5D52B2B84F6AB00B63F36 /* WebViewRecordReceiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C5D52A2B84F6AB00B63F36 /* WebViewRecordReceiver.swift */; }; D2C5D52D2B84F6D800B63F36 /* WebViewRecordReceiverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C5D52C2B84F6D800B63F36 /* WebViewRecordReceiverTests.swift */; }; D2C7E3AB28F97DCF0023B2CC /* BatteryStatusPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C7E3AA28F97DCF0023B2CC /* BatteryStatusPublisherTests.swift */; }; - D2CC33DB2FBCC81D00377194 /* MessageBus.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2CC33DA2FBCC81D00377194 /* MessageBus.swift */; }; D2D0E1F22E3CE59700BA4113 /* mach_profiler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D2D0E1F12E3CE59700BA4113 /* mach_profiler.cpp */; }; D2D0E1F82E3CE73700BA4113 /* mach_sampling_profiler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D2D0E1F72E3CE73700BA4113 /* mach_sampling_profiler.cpp */; }; D2D0E2072E3CEF1200BA4113 /* MachSamplingProfilerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D0E2022E3CEF1200BA4113 /* MachSamplingProfilerTests.swift */; }; @@ -2467,7 +2468,6 @@ 61C3E63824BF19B4008053F2 /* RUMContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMContext.swift; sourceTree = ""; }; 61C3E63A24BF1A4B008053F2 /* RUMCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMCommand.swift; sourceTree = ""; }; 61C3E63D24BF1B91008053F2 /* RUMApplicationScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMApplicationScope.swift; sourceTree = ""; }; - 61C453492C0A0BBF00CC4C17 /* TelemetryInterceptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryInterceptorTests.swift; sourceTree = ""; }; 61C5A87824509A0C00DA608C /* DDSpan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DDSpan.swift; sourceTree = ""; }; 61C5A87924509A0C00DA608C /* DDNoOps.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DDNoOps.swift; sourceTree = ""; }; 61C5A87C24509A0C00DA608C /* Casting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Casting.swift; sourceTree = ""; }; @@ -2517,7 +2517,6 @@ 61DB33B125DEDFC200F7EA71 /* CustomObjcViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CustomObjcViewController.m; sourceTree = ""; }; 61DCC8462C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionEndedMetricControllerTests.swift; sourceTree = ""; }; 61DCC8492C05D4D600CB59E5 /* RUMSessionEndedMetricIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMSessionEndedMetricIntegrationTests.swift; sourceTree = ""; }; - 61DCC84D2C071DCD00CB59E5 /* TelemetryInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryInterceptor.swift; sourceTree = ""; }; 61DE333525C8278A008E3EC2 /* CrashReportingPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReportingPlugin.swift; sourceTree = ""; }; 61E141EE2D42BC74008C8851 /* RUMViewEndedMetricIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMViewEndedMetricIntegrationTests.swift; sourceTree = ""; }; 61E45BCE2450A6EC00F2C652 /* TraceIDTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceIDTests.swift; sourceTree = ""; }; @@ -2713,7 +2712,7 @@ D20FD9D52ACC0934004D3569 /* WebLogIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebLogIntegrationTests.swift; sourceTree = ""; }; D21331C02D132F0600E4A6A1 /* SwiftUIWireframesBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIWireframesBuilderTests.swift; sourceTree = ""; }; D213532F270CA722000315AD /* DataCompressionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataCompressionTests.swift; sourceTree = ""; }; - D214DAA429E072D7004D0AE8 /* MessageBus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBus.swift; sourceTree = ""; }; + D214DAA429E072D7004D0AE8 /* CoreMessageBus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreMessageBus.swift; sourceTree = ""; }; D214DAA729E54CB4004D0AE8 /* TelemetryReceiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryReceiver.swift; sourceTree = ""; }; D215ED6A29D2E1080046B721 /* ErrorMessageReceiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMessageReceiver.swift; sourceTree = ""; }; D215F0932F6D5DE000E86466 /* ThirdPartyDelegateProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdPartyDelegateProxy.swift; sourceTree = ""; }; @@ -2737,11 +2736,15 @@ D21A94F12B8397CA00AC4256 /* WebViewMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewMessage.swift; sourceTree = ""; }; D21AE6BB29E5EDAF0064BF29 /* TelemetryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryTests.swift; sourceTree = ""; }; D21C26C428A3B49C005DD405 /* FeatureStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureStorage.swift; sourceTree = ""; }; - D21C26D028A64599005DD405 /* MessageBusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBusTests.swift; sourceTree = ""; }; + D21C26D028A64599005DD405 /* CoreMessageBusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreMessageBusTests.swift; sourceTree = ""; }; D21C26EA28AFA11E005DD405 /* LogMessageReceiverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogMessageReceiverTests.swift; sourceTree = ""; }; D21C26ED28AFB65B005DD405 /* ErrorMessageReceiverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMessageReceiverTests.swift; sourceTree = ""; }; D22442C42CA301DA002E71E4 /* UIColor+SessionReplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+SessionReplay.swift"; sourceTree = ""; }; D224430C29E95D6600274EC7 /* CrashReportReceiverTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrashReportReceiverTests.swift; sourceTree = ""; }; + D224DFA72FAB8C4B000AED53 /* MessageBus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBus.swift; sourceTree = ""; }; + D224DFA92FAB9DCE000AED53 /* MessageBusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBusTests.swift; sourceTree = ""; }; + D224DFAD2FACFA43000AED53 /* RUMDataModels+BusMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RUMDataModels+BusMessage.swift"; sourceTree = ""; }; + D224DFAF2FB1BBCA000AED53 /* WebViewBusMessages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewBusMessages.swift; sourceTree = ""; }; D22689C22EB12D3D00875E44 /* KSCrashPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KSCrashPlugin.swift; sourceTree = ""; }; D22689C62EB2151F00875E44 /* KSCrashPluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KSCrashPluginTests.swift; sourceTree = ""; }; D2268AD12EB4DA4100875E44 /* DatadogTypeSafeFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatadogTypeSafeFilter.swift; sourceTree = ""; }; @@ -2955,7 +2958,6 @@ D2C7E3AA28F97DCF0023B2CC /* BatteryStatusPublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryStatusPublisherTests.swift; sourceTree = ""; }; D2CBC26A294383F200134409 /* WebViewEventReceiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewEventReceiver.swift; sourceTree = ""; }; D2CBC26D294395A300134409 /* RUMContextAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMContextAttributes.swift; sourceTree = ""; }; - D2CC33DA2FBCC81D00377194 /* MessageBus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBus.swift; sourceTree = ""; }; D2D0E1F12E3CE59700BA4113 /* mach_profiler.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = mach_profiler.cpp; sourceTree = ""; }; D2D0E1F72E3CE73700BA4113 /* mach_sampling_profiler.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = mach_sampling_profiler.cpp; sourceTree = ""; }; D2D0E2012E3CEF1200BA4113 /* MachProfilerCAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MachProfilerCAPITests.swift; sourceTree = ""; }; @@ -4530,7 +4532,7 @@ isa = PBXGroup; children = ( D2B3F04C282A85FD00C2B5EE /* DatadogCore.swift */, - D214DAA429E072D7004D0AE8 /* MessageBus.swift */, + D214DAA429E072D7004D0AE8 /* CoreMessageBus.swift */, D2EFA866286DA82700F1FAA6 /* Context */, 61133BA62423979B00786299 /* Storage */, 6128F56C2BA223A100D35B08 /* DataStore */, @@ -5018,7 +5020,6 @@ D236BE2729520FED00676E67 /* CrashReportReceiver.swift */, D215ED6A29D2E1080046B721 /* ErrorMessageReceiver.swift */, D214DAA729E54CB4004D0AE8 /* TelemetryReceiver.swift */, - 61DCC84D2C071DCD00CB59E5 /* TelemetryInterceptor.swift */, D2D748222DC0FF7E00C61353 /* FatalErrorContextNotifier.swift */, 5B1D02842E8EB78600AB2391 /* FlagEvaluationReceiver.swift */, ); @@ -5352,6 +5353,7 @@ D28FB6952DB7D3F000CD76D0 /* RUMDataModels.swift */, D2D9A9DB2DBFD507005DB31D /* RUMPayloadMessages.swift */, D2E8A8E62DCBBA5100CF7C63 /* RUMCoreContext.swift */, + D224DFAD2FACFA43000AED53 /* RUMDataModels+BusMessage.swift */, ); path = RUM; sourceTree = ""; @@ -6172,7 +6174,7 @@ children = ( D23039C1298D5235001A1FA3 /* FeatureMessageReceiver.swift */, D23039C2298D5235001A1FA3 /* FeatureMessage.swift */, - D2CC33DA2FBCC81D00377194 /* MessageBus.swift */, + D224DFA72FAB8C4B000AED53 /* MessageBus.swift */, ); path = MessageBus; sourceTree = ""; @@ -6258,7 +6260,6 @@ D21C26ED28AFB65B005DD405 /* ErrorMessageReceiverTests.swift */, 9E53889B2773C4B300A7DC42 /* WebViewEventReceiverTests.swift */, D248ED4728081B9B00B315B4 /* TelemetryReceiverTests.swift */, - 61C453492C0A0BBF00CC4C17 /* TelemetryInterceptorTests.swift */, 5B1D02932E8ED6BE00AB2391 /* FlagEvaluationReceiverTests.swift */, ); path = Integrations; @@ -6321,6 +6322,7 @@ isa = PBXGroup; children = ( D21A94F12B8397CA00AC4256 /* WebViewMessage.swift */, + D224DFAF2FB1BBCA000AED53 /* WebViewBusMessages.swift */, ); path = WebViewTracking; sourceTree = ""; @@ -6565,6 +6567,7 @@ isa = PBXGroup; children = ( D2DA23A0298D58F400C6C7E6 /* FeatureMessageReceiverTests.swift */, + D224DFA92FAB9DCE000AED53 /* MessageBusTests.swift */, ); path = MessageBus; sourceTree = ""; @@ -6812,7 +6815,7 @@ 614B78EA296D7B63009C6B92 /* DatadogCoreTests.swift */, 6167E70D2B83502200C3CA2D /* DatadogCore+FeatureDirectoriesTests.swift */, 613F9C172BAC3527007C7606 /* DatadogCore+FeatureDataStoreTests.swift */, - D21C26D028A64599005DD405 /* MessageBusTests.swift */, + D21C26D028A64599005DD405 /* CoreMessageBusTests.swift */, ); path = DatadogCore; sourceTree = ""; @@ -7969,7 +7972,7 @@ 6128F5742BA3280300D35B08 /* DataStoreFileReader.swift in Sources */, 11F59BC22EAE2180009F8579 /* LaunchInfoPublisher.swift in Sources */, D2553829288F0B2400727FAD /* LowPowerModePublisher.swift in Sources */, - D224430629E95C2C00274EC7 /* MessageBus.swift in Sources */, + D224430629E95C2C00274EC7 /* CoreMessageBus.swift in Sources */, 61F930BE2BA1ACAC005F0EE2 /* Storage+TLV.swift in Sources */, 6128F5772BA32DE500D35B08 /* DataStoreFileWriter.swift in Sources */, 6139CD712589FAFD007E8BB7 /* Retrying.swift in Sources */, @@ -8163,7 +8166,7 @@ 61345613244756E300E7DA6B /* PerformancePresetTests.swift in Sources */, D28F836C29C9E7A300EF8EA2 /* TracingURLSessionHandlerTests.swift in Sources */, F603F12B2CAEA4FA0088E6B7 /* DDInternalLoggerTests.swift in Sources */, - D21C26D128A64599005DD405 /* MessageBusTests.swift in Sources */, + D21C26D128A64599005DD405 /* CoreMessageBusTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -8573,7 +8576,6 @@ D21A94F22B8397CA00AC4256 /* WebViewMessage.swift in Sources */, D23039EC298D5236001A1FA3 /* LaunchInfo.swift in Sources */, 6175C3512BCE66DB006FAAB0 /* TraceContext.swift in Sources */, - D2CC33DB2FBCC81D00377194 /* MessageBus.swift in Sources */, D227A0A42C7622EA00C83324 /* BenchmarkProfiler.swift in Sources */, D23039EE298D5236001A1FA3 /* FeatureMessageReceiver.swift in Sources */, D23039DE298D5235001A1FA3 /* Writer.swift in Sources */, @@ -8638,6 +8640,7 @@ 3CA8525F2BF2073800B52CBA /* TraceContextInjection.swift in Sources */, D23039F6298D5236001A1FA3 /* Attributes.swift in Sources */, D20731CB29A52E6000ECBF94 /* Sampler.swift in Sources */, + D224DFA82FAB8C4B000AED53 /* MessageBus.swift in Sources */, D2EBEE2029BA160F00B15732 /* TracePropagationHeadersWriter.swift in Sources */, 267134902D688D280048CB54 /* AccountInfo.swift in Sources */, D23039EF298D5236001A1FA3 /* FeatureMessage.swift in Sources */, @@ -8649,6 +8652,7 @@ D23039E0298D5235001A1FA3 /* DatadogCoreProtocol.swift in Sources */, D27465812E7B1C4D00C47FE2 /* ProfilingMessages.swift in Sources */, 5BFDD7A92E28FDD3009A2CEE /* RUMWebViewContext.swift in Sources */, + D224DFAE2FACFA43000AED53 /* RUMDataModels+BusMessage.swift in Sources */, D23039FD298D5236001A1FA3 /* DataCompression.swift in Sources */, D2C179E12DD2388800556F68 /* TraceCoreContext.swift in Sources */, B3E46CAE2D91B40000BABF66 /* NetworkContext.swift in Sources */, @@ -8668,6 +8672,7 @@ E996B2C5B9B268490842F924 /* HeatmapAttributes.swift in Sources */, 9D0C56F6A6472E38F662D6D7 /* HeatmapIdentifier.swift in Sources */, 13124CCC91AF2DC5EC18DDA6 /* HeatmapIdentifierRegistry.swift in Sources */, + D224DFB02FB1BBCA000AED53 /* WebViewBusMessages.swift in Sources */, A01D384CBDE4FFDC49CCD424 /* HeatmapIdentifierRegistryFeature.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -8990,7 +8995,6 @@ 11A2F24A2E70CC08006EDC52 /* FrameInfoProvider.swift in Sources */, 11A2F24B2E70CC08006EDC52 /* MediaTimeProvider.swift in Sources */, D29A9F5229DD85BB005C54A4 /* RUMUUIDGenerator.swift in Sources */, - 61DCC84E2C071DCD00CB59E5 /* TelemetryInterceptor.swift in Sources */, 4062CCAEB02C9EEF6761F4F8 /* HeatmapIdentifierStore.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -9002,7 +9006,6 @@ 6188697C2A4376F700E8996B /* RUMConfigurationTests.swift in Sources */, 61DCC8472C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift in Sources */, D29A9FA629DDB483005C54A4 /* RUMOffViewEventsHandlingRuleTests.swift in Sources */, - 61C4534A2C0A0BBF00CC4C17 /* TelemetryInterceptorTests.swift in Sources */, D29A9FBD29DDB483005C54A4 /* RUMSessionScopeTests.swift in Sources */, 0904F9F62EE1DA6800ED9A22 /* UIKitExtensionsTests.swift in Sources */, 3C4CF9982C47CC91006DE1C0 /* MemoryWarningMonitorTests.swift in Sources */, @@ -9102,6 +9105,7 @@ D21AE6BC29E5EDAF0064BF29 /* TelemetryTests.swift in Sources */, D2DA23A3298D58F400C6C7E6 /* AnyEncodableTests.swift in Sources */, 3CCECDAF2BC688120013C125 /* SpanIDGeneratorTests.swift in Sources */, + D224DFAA2FAB9DCE000AED53 /* MessageBusTests.swift in Sources */, D263BCB429DB014900FA0E21 /* FixedWidthInteger+ConvenienceTests.swift in Sources */, D233E7D22E4627D900E7CFDE /* MultipartFormDataTests.swift in Sources */, 09F8243A2FB48F5B005D1DCE /* WebViewSessionRolloverHandlerTests.swift in Sources */, diff --git a/DatadogCore/Sources/Core/MessageBus.swift b/DatadogCore/Sources/Core/CoreMessageBus.swift similarity index 52% rename from DatadogCore/Sources/Core/MessageBus.swift rename to DatadogCore/Sources/Core/CoreMessageBus.swift index e8d687de5d..350371b4e1 100644 --- a/DatadogCore/Sources/Core/MessageBus.swift +++ b/DatadogCore/Sources/Core/CoreMessageBus.swift @@ -10,7 +10,7 @@ import DatadogInternal /// The message-bus sends messages to a set of registered receivers. /// /// The bus dispatches messages on a serial queue. -internal final class MessageBus { +internal final class CoreMessageBus: @unchecked Sendable { /// The message bus GDC queue. let queue = DispatchQueue( label: "com.datadoghq.ios-sdk-message-bus", @@ -27,6 +27,25 @@ internal final class MessageBus { /// The bus **must** be accessed within the queue. private var bus: [String: FeatureMessageReceiver] = [:] + /// A closure that delivers a `BusMessage` to a single subscriber. + /// + /// Captures the receiver strongly and performs the runtime cast from `Any` + /// to the receiver's expected `Message` type. + private typealias Dispatch = (Any, DatadogCoreProtocol) -> Void + + /// Typed `BusMessageReceiver` subscribers grouped by `BusMessage.key`. + /// + /// The outer key is the `BusMessage.key` of the message kind a subscriber + /// is registered for; the inner key is the receiver's object identity. The + /// keyed layout means dispatch only iterates receivers for the matching + /// message kind instead of the full subscriber set. + /// + /// Each `Dispatch` captures its receiver strongly, so the bus retains + /// subscribers until they are explicitly removed via `unsubscribe(receiver:)`. + /// + /// Must be accessed within the queue. + private var receivers: [String: [ObjectIdentifier: Dispatch]] = [:] + /// The current configuration. /// /// The message-bus wil accumulate configuration by merge. A message @@ -42,11 +61,18 @@ internal final class MessageBus { /// - Parameter configurationDispatchTime: The delay to dispatch the /// configuration telemetry init(configurationDispatchTime: DispatchTimeInterval = .seconds(5)) { - queue.asyncAfter(deadline: .now() + configurationDispatchTime) { - guard let core = self.core, let configuration = self.configuration else { + queue.asyncAfter(deadline: .now() + configurationDispatchTime) { [weak self] in + guard let self = self, let core = self.core, let configuration = self.configuration else { return } + // Dispatch via typed bus to TelemetryMessage subscribers. + if let bucket = self.receivers[TelemetryMessage.key] { + let message = TelemetryMessage.configuration(configuration) + bucket.values.forEach { dispatch in dispatch(message, core) } + } + + // Dispatch via legacy bus for receivers still on the legacy bus. self.bus.values.forEach { $0.receive(message: .telemetry(.configuration(configuration)), from: core) } @@ -125,7 +151,69 @@ internal final class MessageBus { } } -extension MessageBus: Flushable { +extension CoreMessageBus: MessageBus { + /// Adds `receiver` to the bucket for `Receiver.Message.key`. + /// + /// The receiver is retained by the bus (via the captured closure) until + /// `unsubscribe(receiver:)` is called. Re-subscribing the same instance for + /// the same message kind replaces the previous subscription — entries are + /// keyed by object identity. + func subscribe(receiver: Receiver) where Receiver: BusMessageReceiver { + queue.async { + let id = ObjectIdentifier(receiver) + self.receivers[Receiver.Message.key, default: [:]][id] = { message, core in + guard let typed = message as? Receiver.Message else { + return + } + receiver.receive(message: typed, from: core) + } + } + } + + /// Removes `receiver` from the bucket for `Receiver.Message.key`. + /// + /// No-op if `receiver` is not currently subscribed. Empty buckets are + /// pruned to keep the registry tidy. + func unsubscribe(receiver: Receiver) where Receiver: BusMessageReceiver { + queue.async { + let key = Receiver.Message.key + let id = ObjectIdentifier(receiver) + self.receivers[key]?.removeValue(forKey: id) + if self.receivers[key]?.isEmpty == true { + self.receivers.removeValue(forKey: key) + } + } + } + + /// Publishes `message` to every receiver in the bucket for `Message.key`. + /// + /// Configuration telemetry messages are intercepted and accumulated for deferred + /// batch dispatch; they are never routed to subscribers immediately. + /// + /// `fallback` is invoked when the bus has no core, or when the bucket is + /// empty. Delivery is dispatched on the bus's serial queue. + func send(message: Message, else fallback: @escaping () -> Void) where Message: BusMessage { + // Intercept configuration telemetry for deferred accumulated dispatch. + if let telemetry = message as? TelemetryMessage, case .configuration(let config) = telemetry { + save(configuration: config) + return + } + + queue.async { + guard let core = self.core else { + return fallback() + } + guard let bucket = self.receivers[Message.key], !bucket.isEmpty else { + return fallback() + } + bucket.values.forEach { dispatch in + dispatch(message, core) + } + } + } +} + +extension CoreMessageBus: Flushable { /// Awaits completion of all asynchronous operations. /// /// **blocks the caller thread** diff --git a/DatadogCore/Sources/Core/DatadogCore.swift b/DatadogCore/Sources/Core/DatadogCore.swift index c7b8738e2b..24a376b67c 100644 --- a/DatadogCore/Sources/Core/DatadogCore.swift +++ b/DatadogCore/Sources/Core/DatadogCore.swift @@ -53,7 +53,7 @@ internal final class DatadogCore { let applicationVersionPublisher: ApplicationVersionPublisher /// The message-bus instance. - let bus = MessageBus() + let bus = CoreMessageBus() /// Registry for Features. @ReadWriteLock @@ -119,9 +119,9 @@ internal final class DatadogCore { // the bus will keep a weak ref to the core. bus.connect(core: self) - // forward any context change on the message-bus + // forward any context change on the typed message-bus self.contextProvider.publish { [weak self] context in - self?.send(message: .context(context)) + self?.bus.send(message: context) } } @@ -264,8 +264,9 @@ internal final class DatadogCore { /// - key: The key associated with the receiver. private func add(messageReceiver: FeatureMessageReceiver, forKey key: String) { bus.connect(messageReceiver, forKey: key) + // Push current context to typed-bus DatadogContext subscribers. contextProvider.read { context in - self.bus.queue.async { messageReceiver.receive(message: .context(context), from: self) } + self.bus.send(message: context) } } @@ -323,6 +324,8 @@ internal final class DatadogCore { } extension DatadogCore: DatadogCoreProtocol { + var messageBus: MessageBus { bus } + /// Registers a Feature instance. /// /// A Feature collects and transfers data to a Datadog Product (e.g. Logs, RUM, ...). A registered Feature can diff --git a/DatadogCore/Sources/Extensions/ContextSharing/ContextSharingTransformer.swift b/DatadogCore/Sources/Extensions/ContextSharing/ContextSharingTransformer.swift index bb9c3f3c0e..bf37f3b489 100644 --- a/DatadogCore/Sources/Extensions/ContextSharing/ContextSharingTransformer.swift +++ b/DatadogCore/Sources/Extensions/ContextSharing/ContextSharingTransformer.swift @@ -6,24 +6,16 @@ import DatadogInternal -internal final class ContextSharingTransformer: FeatureMessageReceiver, ContextValuePublisher { +internal final class ContextSharingTransformer: BusMessageReceiver, ContextValuePublisher { @ReadWriteLock private var sharedContext: SharedContext? = nil @ReadWriteLock private var receiver: ContextValueReceiver? = nil - // MARK: - FeatureMessageReceiver - - func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool { - switch message { - case .context(let context): - let newContext = SharedContext(datadogContext: context) - sharedContext = newContext - receiver?(newContext) - return true - default: - return false - } + func receive(message context: DatadogContext, from core: DatadogCoreProtocol) { + let newContext = SharedContext(datadogContext: context) + sharedContext = newContext + receiver?(newContext) } // MARK: - ContextValuePublisher diff --git a/DatadogCore/Sources/Extensions/CrossPlatformExtension.swift b/DatadogCore/Sources/Extensions/CrossPlatformExtension.swift index 15bad05648..a487f6e187 100644 --- a/DatadogCore/Sources/Extensions/CrossPlatformExtension.swift +++ b/DatadogCore/Sources/Extensions/CrossPlatformExtension.swift @@ -29,7 +29,9 @@ public final class CrossPlatformExtension: NSObject { if Self.contextSharingTransformer == nil { let core = CoreRegistry.default let contextSharingTransformer = ContextSharingTransformer() - try? core.register(feature: ContextSharingFeature(messageReceiver: contextSharingTransformer)) + // Subscribe before registration so initial context push is received: + core.messageBus.subscribe(receiver: contextSharingTransformer) + try? core.register(feature: ContextSharingFeature(messageReceiver: NOPFeatureMessageReceiver())) Self.contextSharingTransformer = contextSharingTransformer } contextSharingTransformer?.publish(to: toSharedContext) diff --git a/DatadogCore/Tests/Datadog/DatadogCore/CoreMessageBusTests.swift b/DatadogCore/Tests/Datadog/DatadogCore/CoreMessageBusTests.swift new file mode 100644 index 0000000000..d4cc24aeff --- /dev/null +++ b/DatadogCore/Tests/Datadog/DatadogCore/CoreMessageBusTests.swift @@ -0,0 +1,266 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import XCTest +import TestUtilities +import DatadogInternal + +@testable import DatadogCore + +class CoreMessageBusTests: XCTestCase { + func testCoreMessageBus() throws { + let expectation = XCTestExpectation(description: "dispatch message") + expectation.expectedFulfillmentCount = 2 + + // Given + let core = PassthroughCoreMock() + + let receiver = FeatureMessageReceiverMock { message in + // Then + switch message { + case let .payload(payload as String) where payload == "value": + expectation.fulfill() + default: + XCTFail("wrong message case") + } + } + + let bus = CoreMessageBus() + bus.connect(core: core) + + bus.connect(receiver, forKey: "receiver 1") + bus.connect(receiver, forKey: "receiver 2") + + // When + bus.send(message: .payload("value")) + + // Then + wait(for: [expectation], timeout: 0.5) + bus.flush() + } + + func testItForwardConfigurationAfterDispatch() throws { + let expectation = XCTestExpectation(description: "dispatch configuration") + let receiver = FeatureMessageReceiverMock { message in + guard + case .telemetry(let telemetry) = message, + case .configuration(let configuration) = telemetry + else { + return XCTFail("Message bus should send configuration telemetry") + } + + XCTAssertEqual(configuration.batchSize, 1) + XCTAssertTrue(configuration.trackErrors ?? false) + expectation.fulfill() + } + + // Given + let core = PassthroughCoreMock() + let bus = CoreMessageBus(configurationDispatchTime: .milliseconds(90)) + bus.connect(core: core) + bus.connect(receiver, forKey: "test") + + // When + bus.configuration(batchSize: 1) + bus.configuration(trackErrors: true) + + // Then + wait(for: [expectation], timeout: 0.5) + bus.flush() + } + + // MARK: - typed bus (subscribe / unsubscribe / send) + + func testSubscribe_deliversMessageToReceiver() throws { + let expectation = XCTestExpectation(description: "receiver invoked") + + // Given + let core = PassthroughCoreMock() + let bus = CoreMessageBus() + bus.connect(core: core) + + let receiver = AlphaReceiver { message, _ in + XCTAssertEqual(message.value, "value") + expectation.fulfill() + } + bus.subscribe(receiver: receiver) + + // When + bus.send(message: AlphaMessage(value: "value")) + + // Then + wait(for: [expectation], timeout: 0.5) + bus.flush() + } + + func testSubscribe_routesByMessageType() throws { + let expectation = XCTestExpectation(description: "matching receiver invoked") + expectation.assertForOverFulfill = true + + // Given + let core = PassthroughCoreMock() + let bus = CoreMessageBus() + bus.connect(core: core) + + let alpha = AlphaReceiver { _, _ in expectation.fulfill() } + let beta = BetaReceiver { _, _ in + XCTFail("BetaReceiver must not receive AlphaMessage") + } + bus.subscribe(receiver: alpha) + bus.subscribe(receiver: beta) + + // When + bus.send(message: AlphaMessage(value: "value")) + + // Then + wait(for: [expectation], timeout: 0.5) + bus.flush() + } + + func testSubscribe_deliversToMultipleReceiversOfSameType() throws { + let expectation = XCTestExpectation(description: "all receivers invoked") + expectation.expectedFulfillmentCount = 3 + + // Given + let core = PassthroughCoreMock() + let bus = CoreMessageBus() + bus.connect(core: core) + + let receivers = (0..<3).map { _ in + AlphaReceiver { _, _ in expectation.fulfill() } + } + receivers.forEach { bus.subscribe(receiver: $0) } + + // When + bus.send(message: AlphaMessage(value: "value")) + + // Then + wait(for: [expectation], timeout: 0.5) + bus.flush() + } + + func testUnsubscribe_stopsDelivery() throws { + let fallbackExpectation = XCTestExpectation(description: "fallback invoked") + + // Given + let core = PassthroughCoreMock() + let bus = CoreMessageBus() + bus.connect(core: core) + + let receiver = AlphaReceiver { _, _ in + XCTFail("receiver must not be invoked after unsubscribe") + } + bus.subscribe(receiver: receiver) + bus.unsubscribe(receiver: receiver) + + // When + bus.send(message: AlphaMessage(value: "value")) { fallbackExpectation.fulfill() } + + // Then + wait(for: [fallbackExpectation], timeout: 0.5) + bus.flush() + } + + func testSend_callsFallbackWhenNoSubscribers() throws { + let expectation = XCTestExpectation(description: "fallback invoked") + + // Given + let core = PassthroughCoreMock() + let bus = CoreMessageBus() + bus.connect(core: core) + + // When + bus.send(message: AlphaMessage(value: "value")) { expectation.fulfill() } + + // Then + wait(for: [expectation], timeout: 0.5) + bus.flush() + } + + func testSend_callsFallbackWhenCoreNotConnected() throws { + let expectation = XCTestExpectation(description: "fallback invoked") + + // Given (no `connect(core:)` call) + let bus = CoreMessageBus() + let receiver = AlphaReceiver { _, _ in + XCTFail("receiver must not be invoked without a connected core") + } + bus.subscribe(receiver: receiver) + + // When + bus.send(message: AlphaMessage(value: "value")) { expectation.fulfill() } + + // Then + wait(for: [expectation], timeout: 0.5) + bus.flush() + } + + func testSend_doesNotCallFallbackWhenDelivered() throws { + let delivery = XCTestExpectation(description: "receiver invoked") + let fallback = XCTestExpectation(description: "fallback NOT invoked") + fallback.isInverted = true + + // Given + let core = PassthroughCoreMock() + let bus = CoreMessageBus() + bus.connect(core: core) + + let receiver = AlphaReceiver { _, _ in delivery.fulfill() } + bus.subscribe(receiver: receiver) + + // When + bus.send(message: AlphaMessage(value: "value")) { fallback.fulfill() } + + // Then + wait(for: [delivery, fallback], timeout: 0.5) + bus.flush() + } +} + +// MARK: - typed-bus fixtures + +private struct AlphaMessage: BusMessage { + static let key = "test.alpha" + let value: String +} + +private struct BetaMessage: BusMessage { + static let key = "test.beta" +} + +private final class AlphaReceiver: BusMessageReceiver { + typealias Message = AlphaMessage + + let onReceive: (AlphaMessage, DatadogCoreProtocol) -> Void + + init(_ onReceive: @escaping (AlphaMessage, DatadogCoreProtocol) -> Void) { + self.onReceive = onReceive + } + + func receive(message: AlphaMessage, from core: DatadogCoreProtocol) { + onReceive(message, core) + } +} + +private final class BetaReceiver: BusMessageReceiver { + typealias Message = BetaMessage + + let onReceive: (BetaMessage, DatadogCoreProtocol) -> Void + + init(_ onReceive: @escaping (BetaMessage, DatadogCoreProtocol) -> Void) { + self.onReceive = onReceive + } + + func receive(message: BetaMessage, from core: DatadogCoreProtocol) { + onReceive(message, core) + } +} + +extension CoreMessageBus: @retroactive Telemetry { + public func send(telemetry: DatadogInternal.TelemetryMessage) { + send(message: .telemetry(telemetry)) + } +} diff --git a/DatadogCore/Tests/Datadog/Extensions/ContextSharing/ContextSharingTransformerTests.swift b/DatadogCore/Tests/Datadog/Extensions/ContextSharing/ContextSharingTransformerTests.swift index b5df47d5c6..82a14b0fcb 100644 --- a/DatadogCore/Tests/Datadog/Extensions/ContextSharing/ContextSharingTransformerTests.swift +++ b/DatadogCore/Tests/Datadog/Extensions/ContextSharing/ContextSharingTransformerTests.swift @@ -27,25 +27,11 @@ class ContextSharingTransformerTests: XCTestCase { func testReceiveContextMessage_transformsToSharedContext() throws { // Given let transformer = ContextSharingTransformer() - let message = FeatureMessage.context(.mockRandom()) // When - let handled = transformer.receive(message: message, from: core) + transformer.receive(message: .mockRandom(), from: core) - // Then - XCTAssertTrue(handled) - } - - func testReceiveNonContextMessage_returnsNotHandled() throws { - // Given - let transformer = ContextSharingTransformer() - let customMessage = FeatureMessage.payload("") - - // When - let handled = transformer.receive(message: customMessage, from: core) - - // Then - XCTAssertFalse(handled) + // Then — no assertion needed; just checking it doesn't crash } func testPublish_callsReceiverImmediately() throws { @@ -78,8 +64,7 @@ class ContextSharingTransformerTests: XCTestCase { let userInfo = UserInfo(id: "user-456") let accountInfo = AccountInfo(id: "account-789") let context = DatadogContext.mockWith(userInfo: userInfo, accountInfo: accountInfo) - let message = FeatureMessage.context(context) - _ = transformer.receive(message: message, from: core) + transformer.receive(message: context, from: core) // Then XCTAssertEqual(receivedContexts.count, 2) @@ -101,8 +86,7 @@ class ContextSharingTransformerTests: XCTestCase { transformer.cancel() let context = DatadogContext.mockWith(userInfo: UserInfo(id: "user-789")) - let message = FeatureMessage.context(context) - _ = transformer.receive(message: message, from: core) + transformer.receive(message: context, from: core) // Then - receiver must not be called after cancel XCTAssertEqual(callCount, 1) // Only initial call from publish diff --git a/DatadogInternal/Sources/NetworkInstrumentation/DatadogURLSessionHandler.swift b/DatadogInternal/Sources/NetworkInstrumentation/DatadogURLSessionHandler.swift index 5adc207788..41c566cd69 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/DatadogURLSessionHandler.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/DatadogURLSessionHandler.swift @@ -46,8 +46,13 @@ extension DatadogCoreProtocol { /// /// - Parameter urlSessionHandler: The `URLSession` handler to register. public func register(urlSessionHandler: DatadogURLSessionHandler) throws { - let contextProvider = NetworkContextCoreProvider() - let feature = get(feature: NetworkInstrumentationFeature.self) ?? .init(networkContextProvider: contextProvider, messageReceiver: contextProvider) + let feature = get(feature: NetworkInstrumentationFeature.self) ?? { + let contextProvider = NetworkContextCoreProvider() + let feature = NetworkInstrumentationFeature(networkContextProvider: contextProvider, messageReceiver: NOPFeatureMessageReceiver()) + // Subscribe typed-bus receiver before registration so initial context push is received: + messageBus.subscribe(receiver: contextProvider) + return feature + }() feature.handlers.append(urlSessionHandler) try register(feature: feature) } diff --git a/DatadogInternal/Sources/NetworkInstrumentation/NetworkContextProvider.swift b/DatadogInternal/Sources/NetworkInstrumentation/NetworkContextProvider.swift index 86d658e437..e178d2b602 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/NetworkContextProvider.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/NetworkContextProvider.swift @@ -19,12 +19,8 @@ internal class NetworkContextCoreProvider: NetworkContextProvider { var currentNetworkContext: NetworkContext? } -extension NetworkContextCoreProvider: FeatureMessageReceiver { - func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool { - guard case let .context(context) = message else { - return false - } - +extension NetworkContextCoreProvider: BusMessageReceiver { + func receive(message context: DatadogContext, from core: DatadogCoreProtocol) { let userConfigurationContext: UserConfigurationContext? = { guard let userInfo = context.userInfo else { return nil @@ -45,6 +41,5 @@ extension NetworkContextCoreProvider: FeatureMessageReceiver { userConfigurationContext: userConfigurationContext, accountConfigurationContext: accountConfigurationContext ) - return true } } diff --git a/DatadogInternal/Tests/NetworkInstrumentation/NetworkInstrumentationFeatureTests.swift b/DatadogInternal/Tests/NetworkInstrumentation/NetworkInstrumentationFeatureTests.swift index 734a1bffd9..c150ce9f81 100644 --- a/DatadogInternal/Tests/NetworkInstrumentation/NetworkInstrumentationFeatureTests.swift +++ b/DatadogInternal/Tests/NetworkInstrumentation/NetworkInstrumentationFeatureTests.swift @@ -2190,10 +2190,9 @@ class NetworkInstrumentationFeatureTests: XCTestCase { ) // When - let result = provider.receive(message: .context(context), from: core) + provider.receive(message: context, from: core) // Then - XCTAssertTrue(result) let networkContext = try XCTUnwrap(provider.currentNetworkContext) // Verify RUM context @@ -2222,10 +2221,9 @@ class NetworkInstrumentationFeatureTests: XCTestCase { ) // When - let result = provider.receive(message: .context(context), from: core) + provider.receive(message: context, from: core) // Then - XCTAssertTrue(result) let networkContext = try XCTUnwrap(provider.currentNetworkContext) // Verify RUM context is still available @@ -2237,15 +2235,11 @@ class NetworkInstrumentationFeatureTests: XCTestCase { XCTAssertNil(networkContext.accountConfigurationContext) } - func testWhenReceivingNonContextMessage_itReturnsFalse() { + func testNetworkContextIsNilUntilContextMessageIsReceived() { // Given let provider = NetworkContextCoreProvider() - // When - let result = provider.receive(message: .payload("some data"), from: core) - - // Then - XCTAssertFalse(result) + // Then — no DatadogContext delivered yet XCTAssertNil(provider.currentNetworkContext) } diff --git a/DatadogInternal/Tests/Telemetry/TelemetryTests.swift b/DatadogInternal/Tests/Telemetry/TelemetryTests.swift index 6515d3c7b7..f0f98593c2 100644 --- a/DatadogInternal/Tests/Telemetry/TelemetryTests.swift +++ b/DatadogInternal/Tests/Telemetry/TelemetryTests.swift @@ -187,23 +187,24 @@ class TelemetryTests: XCTestCase { // MARK: - Integration with Core func testWhenUsingCoreTelemetry_itSendsTelemetryToMessageReceiver() throws { - let receiver = FeatureMessageReceiverMock() - let core = PassthroughCoreMock(messageReceiver: receiver) + let receiver = TelemetryReceiverMock() + let core = PassthroughCoreMock() + core.messageBus.subscribe(receiver: receiver) core.telemetry.debug("debug message") - XCTAssertEqual(receiver.messages.lastTelemetry?.asDebug?.message, "debug message") + XCTAssertEqual(receiver.messages.firstDebug()?.message, "debug message") core.telemetry.error("error message") - XCTAssertEqual(receiver.messages.lastTelemetry?.asError?.message, "error message") + XCTAssertEqual(receiver.messages.firstError()?.message, "error message") core.telemetry.configuration(batchSize: 123) - XCTAssertEqual(receiver.messages.lastTelemetry?.asConfiguration?.batchSize, 123) + XCTAssertEqual(receiver.messages.firstConfiguration()?.batchSize, 123) core.telemetry.metric(name: "metric name", attributes: [:], sampleRate: 15) - XCTAssertEqual(receiver.messages.lastTelemetry?.asMetric?.name, "metric name") + XCTAssertEqual(receiver.messages.firstMetric(named: "metric name")?.name, "metric name") let metricTrace = core.telemetry.startMethodCalled(operationName: .mockAny(), callerClass: .mockAny(), headSampleRate: 100) core.telemetry.stopMethodCalled(metricTrace) - XCTAssertEqual(receiver.messages.lastTelemetry?.asMetric?.name, MethodCalledMetric.name) + XCTAssertEqual(receiver.messages.lastMetric(named: MethodCalledMetric.name)?.name, MethodCalledMetric.name) } } From d71830d006d235734ddf2958fbbf0431c10ae50b Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Tue, 16 Jun 2026 14:25:07 +0200 Subject: [PATCH 2/4] [RUM-16486] Fix extension of protocol 'MessageBus' cannot have an inheritance clause --- DatadogCore/Sources/Core/CoreMessageBus.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DatadogCore/Sources/Core/CoreMessageBus.swift b/DatadogCore/Sources/Core/CoreMessageBus.swift index 350371b4e1..9576c987dd 100644 --- a/DatadogCore/Sources/Core/CoreMessageBus.swift +++ b/DatadogCore/Sources/Core/CoreMessageBus.swift @@ -222,7 +222,7 @@ extension CoreMessageBus: Flushable { } } -extension MessageBus: Telemetry { +extension CoreMessageBus: Telemetry { func send(telemetry: TelemetryMessage) { send(message: .telemetry(telemetry)) } From 49daeb797e242e30d98c90eb6e38d7978477c6ec Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Tue, 16 Jun 2026 14:52:07 +0200 Subject: [PATCH 3/4] [RUM-16486] Restore TelemetryInterceptor.swift and TelemetryInterceptorTests.swift to Xcode project --- Datadog/Datadog.xcodeproj/project.pbxproj | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 1e48501fab..ba06fa902a 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -1268,6 +1268,8 @@ D2F448E22D43A3DC007BB995 /* CompletionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2F448E02D43A3DC007BB995 /* CompletionHandler.swift */; }; D2F44FB8299AA1DA0074B0D9 /* DataCompressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D213532F270CA722000315AD /* DataCompressionTests.swift */; }; D2F44FC2299BD5600074B0D9 /* UIViewController+KeyboardControlling.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2F44FC1299BD5600074B0D9 /* UIViewController+KeyboardControlling.swift */; }; + D2F597612FE17E4500146E62 /* TelemetryInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2F597602FE17E4500146E62 /* TelemetryInterceptor.swift */; }; + D2F597632FE17E5F00146E62 /* TelemetryInterceptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2F597622FE17E5F00146E62 /* TelemetryInterceptorTests.swift */; }; D2F8235329915E12003C7E99 /* DatadogSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2F8235229915E12003C7E99 /* DatadogSite.swift */; }; D2FB1254292E0E96005B13F8 /* TrackingConsentPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FB1253292E0E92005B13F8 /* TrackingConsentPublisher.swift */; }; D2FB1257292E0F0E005B13F8 /* TrackingConsentPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FB1256292E0F0B005B13F8 /* TrackingConsentPublisherTests.swift */; }; @@ -3004,6 +3006,8 @@ D2F1B81426D8E5FF009F3293 /* DDNoopTracerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDNoopTracerTests.swift; sourceTree = ""; }; D2F448E02D43A3DC007BB995 /* CompletionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionHandler.swift; sourceTree = ""; }; D2F44FC1299BD5600074B0D9 /* UIViewController+KeyboardControlling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+KeyboardControlling.swift"; sourceTree = ""; }; + D2F597602FE17E4500146E62 /* TelemetryInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryInterceptor.swift; sourceTree = ""; }; + D2F597622FE17E5F00146E62 /* TelemetryInterceptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryInterceptorTests.swift; sourceTree = ""; }; D2F8235229915E12003C7E99 /* DatadogSite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatadogSite.swift; sourceTree = ""; }; D2FB1253292E0E92005B13F8 /* TrackingConsentPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackingConsentPublisher.swift; sourceTree = ""; }; D2FB1256292E0F0B005B13F8 /* TrackingConsentPublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackingConsentPublisherTests.swift; sourceTree = ""; }; @@ -5022,6 +5026,7 @@ D214DAA729E54CB4004D0AE8 /* TelemetryReceiver.swift */, D2D748222DC0FF7E00C61353 /* FatalErrorContextNotifier.swift */, 5B1D02842E8EB78600AB2391 /* FlagEvaluationReceiver.swift */, + D2F597602FE17E4500146E62 /* TelemetryInterceptor.swift */, ); path = Integrations; sourceTree = ""; @@ -6261,6 +6266,7 @@ 9E53889B2773C4B300A7DC42 /* WebViewEventReceiverTests.swift */, D248ED4728081B9B00B315B4 /* TelemetryReceiverTests.swift */, 5B1D02932E8ED6BE00AB2391 /* FlagEvaluationReceiverTests.swift */, + D2F597622FE17E5F00146E62 /* TelemetryInterceptorTests.swift */, ); path = Integrations; sourceTree = ""; @@ -8886,6 +8892,7 @@ 265496D42D81C5B10094B6E2 /* RUMAccount.swift in Sources */, D253EE962B988CA90010B589 /* ViewCache.swift in Sources */, 11EA5C272DC288DD00E8DFA2 /* RUM+objc.swift in Sources */, + D2F597612FE17E4500146E62 /* TelemetryInterceptor.swift in Sources */, D29A9F8429DD85BB005C54A4 /* RUMResourceScope.swift in Sources */, D29A9F7329DD85BB005C54A4 /* RUMApplicationScope.swift in Sources */, 3CFF4F972C09E64C006F191D /* WatchdogTerminationMonitor.swift in Sources */, @@ -9027,6 +9034,7 @@ A7E6EA852D314A9B00997201 /* AnonymousIdentifierManagerTests.swift in Sources */, D29A9FAE29DDB483005C54A4 /* SessionReplayDependencyTests.swift in Sources */, 61C713B62A3C600400FA735A /* RUMMonitorProtocol+ConvenienceTests.swift in Sources */, + D2F597632FE17E5F00146E62 /* TelemetryInterceptorTests.swift in Sources */, 965A1B282F8E6402006A4B4E /* Monitor+AttributeEncodingTests.swift in Sources */, D29A9FB829DDB483005C54A4 /* RUMViewScopeTests.swift in Sources */, D224430F29E9779F00274EC7 /* TelemetryReceiverTests.swift in Sources */, From 39dd976cf5c7e4b47ea1578687f38dd829cbadc9 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Tue, 16 Jun 2026 17:04:39 +0200 Subject: [PATCH 4/4] [RUM-16486] Fix DatadogContext not propagated to legacy FeatureMessageReceiver --- DatadogCore/Sources/Core/DatadogCore.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DatadogCore/Sources/Core/DatadogCore.swift b/DatadogCore/Sources/Core/DatadogCore.swift index 24a376b67c..a31a1b847b 100644 --- a/DatadogCore/Sources/Core/DatadogCore.swift +++ b/DatadogCore/Sources/Core/DatadogCore.swift @@ -119,9 +119,10 @@ internal final class DatadogCore { // the bus will keep a weak ref to the core. bus.connect(core: self) - // forward any context change on the typed message-bus + // forward any context change on the message-bus self.contextProvider.publish { [weak self] context in - self?.bus.send(message: context) + self?.bus.send(message: context) // typed bus (new receivers) + self?.send(message: .context(context)) // legacy bus (receivers pending migration) } } @@ -264,9 +265,8 @@ internal final class DatadogCore { /// - key: The key associated with the receiver. private func add(messageReceiver: FeatureMessageReceiver, forKey key: String) { bus.connect(messageReceiver, forKey: key) - // Push current context to typed-bus DatadogContext subscribers. contextProvider.read { context in - self.bus.send(message: context) + self.bus.queue.async { messageReceiver.receive(message: .context(context), from: self) } } }