From 9a8ab1b7c9aafe4255df5a2655f6c726d015abfc Mon Sep 17 00:00:00 2001 From: Siarhei Bakatsiuk Date: Wed, 22 Jul 2026 19:10:12 +0300 Subject: [PATCH 1/4] add e2e tests for RUM --- .../DatadogNet.iOS.DeviceTests/SmokeTests.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/DatadogNet.iOS.DeviceTests/SmokeTests.cs b/tests/DatadogNet.iOS.DeviceTests/SmokeTests.cs index 49b51ad..cc17733 100644 --- a/tests/DatadogNet.iOS.DeviceTests/SmokeTests.cs +++ b/tests/DatadogNet.iOS.DeviceTests/SmokeTests.cs @@ -65,6 +65,7 @@ public static class SmokeTests new("exposes WebView tracking", ExposesWebViewTracking), new("exposes PLCrashReporter", ExposesCrashReporter), new("drives RUM and Logs through the ergonomic overloads", ErgonomicOverloadsWork), + new("invokes a RUM event mapper", EventMapperIsInvoked), new("stops the RUM session and the SDK instance", StopsCleanly), ]; @@ -133,6 +134,43 @@ private static void ErgonomicOverloadsWork() Report("view scopes, attribute conversion, logger and consent helpers all behaved"); } + /// + /// Checks that the RUM event mappers actually fire, and that an event can be returned from + /// managed code back into Swift. + /// + /// + /// Mappers are how an app redacts or drops events before they are uploaded, so they are the + /// mechanism behind every "scrub the PII out of this" requirement. They are also the only part + /// of the binding that passes a managed delegate into Swift and returns a Swift object out of + /// it, so if block marshalling is wrong anywhere, it is wrong here - and the failure mode is a + /// crash inside the SDK's upload path, long after the call that registered the mapper. + /// + private static void EventMapperIsInvoked() + { + var seen = 0; + + // A second SDK instance cannot be configured, and RUM was already enabled with no mapper, + // so this registers one on a fresh configuration object to prove the binding marshals - + // the mapper is exercised directly below rather than through an upload. + var configuration = new DDRUMConfiguration(applicationID: RumApplicationId); + configuration.SetViewEventMapper(view => + { + seen++; + return view; + }); + + configuration.SetErrorEventMapper(error => + { + seen++; + // Returning null asks the SDK to drop the event entirely, which is what a mapper does + // when it decides an event must never leave the device. + return error; + }); + + Assert(configuration.Handle != IntPtr.Zero, "Configuration with mappers has a null handle."); + Report($"registered view and error mappers (invocations so far: {seen})"); + } + /// Every framework shipped as a dynamic framework, which is all but CrashReporter. private static readonly string[] DynamicFrameworks = [ From 51bda3e7f081734767e8760df78b016979e5acaa Mon Sep 17 00:00:00 2001 From: Siarhei Bakatsiuk Date: Wed, 22 Jul 2026 19:27:53 +0300 Subject: [PATCH 2/4] Add RUM logs usage --- .../DDURLSessionInstrumentation.Ergonomics.cs | 97 +++++++ src/DatadogNet.Objc.iOS/ApiDefinitions.cs | 247 ++++++++++++++++++ src/DatadogNet.Objc.iOS/StructsAndEnums.cs | 41 +++ .../DatadogNet.iOS.DeviceTests/SmokeTests.cs | 151 +++++++++-- 4 files changed, 513 insertions(+), 23 deletions(-) create mode 100644 src/DatadogNet.Objc.iOS/Additions/DDURLSessionInstrumentation.Ergonomics.cs diff --git a/src/DatadogNet.Objc.iOS/Additions/DDURLSessionInstrumentation.Ergonomics.cs b/src/DatadogNet.Objc.iOS/Additions/DDURLSessionInstrumentation.Ergonomics.cs new file mode 100644 index 0000000..ba7edf4 --- /dev/null +++ b/src/DatadogNet.Objc.iOS/Additions/DDURLSessionInstrumentation.Ergonomics.cs @@ -0,0 +1,97 @@ +// Nullable annotations are enabled per file rather than for the project: the generated +// binding sources are not written against a nullable context, and switching the whole +// project over would bury real warnings here under hundreds of generated ones. +#nullable enable + +using System; +using Foundation; +using ObjCRuntime; + +namespace DatadogObjc +{ + public partial class DDURLSessionInstrumentationConfiguration + { + /// + /// Creates a configuration that instruments the given NSUrlSessionDataDelegate type. + /// + /// + /// The delegate class to instrument. Must be an subclass that + /// implements and is registered with the + /// Objective-C runtime. + /// + /// + /// The native initializer takes an Objective-C Class, which reaches C# as a bare + /// . Passing the right value means knowing to call + /// Class.GetHandle (typeof (T)) - which nothing in the generated signature hints at, + /// and which silently yields for a type the runtime does not know, + /// leaving instrumentation quietly disabled rather than failing. + /// + /// + /// is not a registered Objective-C class. + /// + public static DDURLSessionInstrumentationConfiguration Create (Type delegateType) => + new DDURLSessionInstrumentationConfiguration (HandleFor (delegateType, nameof (delegateType))); + + /// + /// The delegate class to instrument. + public static DDURLSessionInstrumentationConfiguration Create () + where TDelegate : NSObject, INSUrlSessionDataDelegate => + Create (typeof (TDelegate)); + + /// The delegate class being instrumented. + /// + /// The typed view of , which is an holding + /// an Objective-C Class. Returns if the class does not map + /// back to a managed type. + /// + public Type? DelegateType { + get => DelegateClass == IntPtr.Zero ? null : Class.Lookup (new Class (DelegateClass)); + set => DelegateClass = value is null ? IntPtr.Zero : HandleFor (value, nameof (value)); + } + + internal static IntPtr HandleFor (Type delegateType, string parameterName) + { + if (delegateType is null) + throw new ArgumentNullException (parameterName); + + var handle = Class.GetHandle (delegateType); + if (handle == IntPtr.Zero) { + throw new ArgumentException ( + $"'{delegateType.FullName}' is not a registered Objective-C class. It must derive from " + + "NSObject, implement INSUrlSessionDataDelegate, and carry a [Register] attribute.", + parameterName); + } + + return handle; + } + } + + public partial class DDURLSessionInstrumentation + { + /// Starts instrumenting the given delegate type for RUM resource and trace collection. + /// + /// Shorthand for + /// EnableWithConfiguration (DDURLSessionInstrumentationConfiguration.Create (delegateType)). + /// Enable this once, before creating the NSUrlSession that uses the delegate. + /// + public static void Enable (Type delegateType) => + EnableWithConfiguration (DDURLSessionInstrumentationConfiguration.Create (delegateType)); + + /// + /// The delegate class to instrument. + public static void Enable () + where TDelegate : NSObject, INSUrlSessionDataDelegate => + Enable (typeof (TDelegate)); + + /// Stops instrumenting the given delegate type. + public static void Disable (Type delegateType) => + DisableWithDelegateClass ( + DDURLSessionInstrumentationConfiguration.HandleFor (delegateType, nameof (delegateType))); + + /// + /// The delegate class to stop instrumenting. + public static void Disable () + where TDelegate : NSObject, INSUrlSessionDataDelegate => + Disable (typeof (TDelegate)); + } +} diff --git a/src/DatadogNet.Objc.iOS/ApiDefinitions.cs b/src/DatadogNet.Objc.iOS/ApiDefinitions.cs index 2470d86..1621fd6 100644 --- a/src/DatadogNet.Objc.iOS/ApiDefinitions.cs +++ b/src/DatadogNet.Objc.iOS/ApiDefinitions.cs @@ -456,6 +456,13 @@ interface DDLogsConfiguration [Export ("initWithCustomEndpoint:")] [DesignatedInitializer] NativeHandle Constructor ([NullAllowed] NSUrl customEndpoint); + + // -(void)setEventMapper:(DDLogEvent * _Nullable (^ _Nonnull)(DDLogEvent * _Nonnull))mapper; + // Return the event to keep it, a modified event to rewrite it, or null to drop it entirely. + // This is the Logs counterpart of DDRUMConfiguration's five event mappers, and the only + // supported way to redact a log before it leaves the device. + [Export ("setEventMapper:")] + void SetEventMapper (Func mapper); } // @interface DDNSURLSessionDelegate : NSObject @@ -5643,4 +5650,244 @@ partial interface OTSpanContext [Export ("forEachBaggageItem:")] void ForEachBaggageItem (Func callback); } + + // --------------------------------------------------------------------------------------- + // Log event model, reached through DDLogsConfiguration.SetEventMapper. + // + // Objective Sharpie emitted none of this - not the ten DDLogEvent* classes, not the three + // enums, and not setEventMapper: itself - so the previous bindings could not redact or drop a + // log before it was uploaded, even though RUM had all five of its mappers. Transcribed by hand + // from DatadogObjc-Swift.h. + // + // Only the properties the header declares as writable are bound with setters; the rest are + // read-only there, and making them settable here would compile but silently do nothing. + // --------------------------------------------------------------------------------------- + + // @interface DDLogEventAttributes : NSObject + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc20DDLogEventAttributes")] + [DisableDefaultCtor] + interface DDLogEventAttributes + { + // @property (copy, nonatomic) NSDictionary * _Nonnull userAttributes; + [Export ("userAttributes", ArgumentSemantic.Copy)] + NSDictionary UserAttributes { get; set; } + } + + // @interface DDLogEventBinaryImage : NSObject + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc21DDLogEventBinaryImage")] + [DisableDefaultCtor] + interface DDLogEventBinaryImage + { + [NullAllowed, Export ("arch")] + string Arch { get; } + + [Export ("isSystem")] + bool IsSystem { get; } + + [NullAllowed, Export ("loadAddress")] + string LoadAddress { get; } + + [NullAllowed, Export ("maxAddress")] + string MaxAddress { get; } + + [Export ("name")] + string Name { get; } + + [Export ("uuid")] + string Uuid { get; } + } + + // @interface DDLogEventError : NSObject + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc15DDLogEventError")] + [DisableDefaultCtor] + interface DDLogEventError + { + [NullAllowed, Export ("kind")] + string Kind { get; set; } + + [NullAllowed, Export ("message")] + string Message { get; set; } + + [NullAllowed, Export ("stack")] + string Stack { get; set; } + + [Export ("sourceType")] + string SourceType { get; set; } + + [NullAllowed, Export ("fingerprint")] + string Fingerprint { get; set; } + + [NullAllowed, Export ("binaryImages", ArgumentSemantic.Copy)] + DDLogEventBinaryImage[] BinaryImages { get; set; } + } + + // @interface DDLogEventCarrierInfo : NSObject + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc21DDLogEventCarrierInfo")] + [DisableDefaultCtor] + interface DDLogEventCarrierInfo + { + [NullAllowed, Export ("carrierName")] + string CarrierName { get; } + + [NullAllowed, Export ("carrierISOCountryCode")] + string CarrierIsoCountryCode { get; } + + [Export ("carrierAllowsVOIP")] + bool CarrierAllowsVoip { get; } + + [Export ("radioAccessTechnology")] + DDLogEventRadioAccessTechnology RadioAccessTechnology { get; } + } + + // @interface DDLogEventDeviceInfo : NSObject + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc20DDLogEventDeviceInfo")] + [DisableDefaultCtor] + interface DDLogEventDeviceInfo + { + [Export ("brand")] + string Brand { get; } + + [Export ("name")] + string Name { get; } + + [Export ("model")] + string Model { get; } + + [Export ("architecture")] + string Architecture { get; } + } + + // @interface DDLogEventDd : NSObject + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc12DDLogEventDd")] + [DisableDefaultCtor] + interface DDLogEventDd + { + [Export ("device", ArgumentSemantic.Strong)] + DDLogEventDeviceInfo Device { get; } + } + + // @interface DDLogEventNetworkConnectionInfo : NSObject + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc31DDLogEventNetworkConnectionInfo")] + [DisableDefaultCtor] + interface DDLogEventNetworkConnectionInfo + { + [Export ("reachability")] + DDLogEventReachability Reachability { get; } + + [NullAllowed, Export ("availableInterfaces", ArgumentSemantic.Copy)] + NSNumber[] AvailableInterfaces { get; } + + [NullAllowed, Export ("supportsIPv4", ArgumentSemantic.Strong)] + NSNumber SupportsIPv4 { get; } + + [NullAllowed, Export ("supportsIPv6", ArgumentSemantic.Strong)] + NSNumber SupportsIPv6 { get; } + + [NullAllowed, Export ("isExpensive", ArgumentSemantic.Strong)] + NSNumber IsExpensive { get; } + + [NullAllowed, Export ("isConstrained", ArgumentSemantic.Strong)] + NSNumber IsConstrained { get; } + } + + // @interface DDLogEventOperatingSystem : NSObject + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc25DDLogEventOperatingSystem")] + [DisableDefaultCtor] + interface DDLogEventOperatingSystem + { + [Export ("name")] + string Name { get; } + + [Export ("version")] + string Version { get; } + + [NullAllowed, Export ("build")] + string Build { get; } + } + + // @interface DDLogEventUserInfo : NSObject + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc18DDLogEventUserInfo")] + [DisableDefaultCtor] + interface DDLogEventUserInfo + { + [NullAllowed, Export ("id")] + string Id { get; } + + [NullAllowed, Export ("name")] + string Name { get; } + + [NullAllowed, Export ("email")] + string Email { get; } + + [Export ("extraInfo", ArgumentSemantic.Copy)] + NSDictionary ExtraInfo { get; set; } + } + + // @interface DDLogEvent : NSObject + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc10DDLogEvent")] + [DisableDefaultCtor] + interface DDLogEvent + { + [Export ("date", ArgumentSemantic.Copy)] + NSDate Date { get; } + + [Export ("status")] + DDLogEventStatus Status { get; } + + // One of the two properties worth writing to: redacting a message in place is the common + // reason to install a log mapper at all. + [Export ("message")] + string Message { get; set; } + + [NullAllowed, Export ("error", ArgumentSemantic.Strong)] + DDLogEventError Error { get; } + + [Export ("serviceName")] + string ServiceName { get; } + + [Export ("environment")] + string Environment { get; } + + [Export ("loggerName")] + string LoggerName { get; } + + [Export ("loggerVersion")] + string LoggerVersion { get; } + + [NullAllowed, Export ("threadName")] + string ThreadName { get; } + + [Export ("applicationVersion")] + string ApplicationVersion { get; } + + [Export ("applicationBuildNumber")] + string ApplicationBuildNumber { get; } + + [NullAllowed, Export ("buildId")] + string BuildId { get; } + + [NullAllowed, Export ("variant")] + string Variant { get; } + + [Export ("dd", ArgumentSemantic.Strong)] + DDLogEventDd Dd { get; } + + [Export ("os", ArgumentSemantic.Strong)] + DDLogEventOperatingSystem Os { get; } + + [Export ("userInfo", ArgumentSemantic.Strong)] + DDLogEventUserInfo UserInfo { get; } + + [NullAllowed, Export ("networkConnectionInfo", ArgumentSemantic.Strong)] + DDLogEventNetworkConnectionInfo NetworkConnectionInfo { get; } + + [NullAllowed, Export ("mobileCarrierInfo", ArgumentSemantic.Strong)] + DDLogEventCarrierInfo MobileCarrierInfo { get; } + + [Export ("attributes", ArgumentSemantic.Strong)] + DDLogEventAttributes Attributes { get; } + + [NullAllowed, Export ("tags", ArgumentSemantic.Copy)] + string[] Tags { get; set; } + } } diff --git a/src/DatadogNet.Objc.iOS/StructsAndEnums.cs b/src/DatadogNet.Objc.iOS/StructsAndEnums.cs index 9267697..446cee9 100644 --- a/src/DatadogNet.Objc.iOS/StructsAndEnums.cs +++ b/src/DatadogNet.Objc.iOS/StructsAndEnums.cs @@ -1033,4 +1033,45 @@ public enum DDUploadFrequency : long Average = 1, Rare = 2 } + + // The log-event model reached by DDLogsConfiguration.SetEventMapper. Objective Sharpie omitted + // the whole family, so the previous bindings had no way to inspect or redact a log before + // upload; these are transcribed from DatadogObjc-Swift.h. + + [Native] + public enum DDLogEventStatus : long + { + Debug = 0, + Info = 1, + Notice = 2, + Warn = 3, + Error = 4, + Critical = 5, + Emergency = 6 + } + + [Native] + public enum DDLogEventReachability : long + { + Yes = 0, + Maybe = 1, + No = 2 + } + + [Native] + public enum DDLogEventRadioAccessTechnology : long + { + Gprs = 0, + Edge = 1, + Wcdma = 2, + Hsdpa = 3, + Hsupa = 4, + Cdma1x = 5, + CdmaEvdoRev0 = 6, + CdmaEvdoRevA = 7, + CdmaEvdoRevB = 8, + Ehrpd = 9, + Lte = 10, + Unknown = 11 + } } diff --git a/tests/DatadogNet.iOS.DeviceTests/SmokeTests.cs b/tests/DatadogNet.iOS.DeviceTests/SmokeTests.cs index cc17733..fd406ba 100644 --- a/tests/DatadogNet.iOS.DeviceTests/SmokeTests.cs +++ b/tests/DatadogNet.iOS.DeviceTests/SmokeTests.cs @@ -66,6 +66,8 @@ public static class SmokeTests new("exposes PLCrashReporter", ExposesCrashReporter), new("drives RUM and Logs through the ergonomic overloads", ErgonomicOverloadsWork), new("invokes a RUM event mapper", EventMapperIsInvoked), + new("invokes a Logs event mapper and redacts a message", LogEventMapperRedacts), + new("instruments a URLSession delegate by type", InstrumentsUrlSessionByType), new("stops the RUM session and the SDK instance", StopsCleanly), ]; @@ -134,41 +136,108 @@ private static void ErgonomicOverloadsWork() Report("view scopes, attribute conversion, logger and consent helpers all behaved"); } + /// Counts mapper invocations, incremented from the blocks registered when features are enabled. + private static int viewEventsMapped; + private static int actionEventsMapped; + private static int logEventsMapped; + private static int logEventsRedacted; + + /// + /// An NSUrlSession delegate for to instrument. + /// + /// + /// [Register] matters: DDURLSessionInstrumentation takes an Objective-C Class, so the type has + /// to be visible to the Objective-C runtime for the lookup to resolve to anything. + /// + [Register(nameof(InstrumentedSessionDelegate))] + private sealed class InstrumentedSessionDelegate : NSUrlSessionDataDelegate + { + } + /// - /// Checks that the RUM event mappers actually fire, and that an event can be returned from - /// managed code back into Swift. + /// Checks that the RUM event mappers actually fire, and that an event can be handed back to + /// Swift from managed code. /// /// /// Mappers are how an app redacts or drops events before they are uploaded, so they are the /// mechanism behind every "scrub the PII out of this" requirement. They are also the only part - /// of the binding that passes a managed delegate into Swift and returns a Swift object out of - /// it, so if block marshalling is wrong anywhere, it is wrong here - and the failure mode is a - /// crash inside the SDK's upload path, long after the call that registered the mapper. + /// of the binding that passes a managed delegate into Swift and gets a Swift object back out, + /// so if block marshalling is wrong anywhere it is wrong here - and the failure mode is a crash + /// inside the SDK's event-writing path, long after the call that registered the mapper. + /// + /// The mappers are registered on the real configuration in , before + /// RUM is enabled, because that is the only time they can be set. This check then runs after + /// has produced events and asserts the blocks were actually reached. + /// /// private static void EventMapperIsInvoked() { - var seen = 0; + // Events are mapped on the SDK's own queue as they are written, not synchronously with the + // call that produced them, so give that queue a moment before concluding anything. + var deadline = DateTime.UtcNow.AddSeconds(5); + while (DateTime.UtcNow < deadline && (viewEventsMapped == 0 || actionEventsMapped == 0)) + { + Thread.Sleep(100); + } - // A second SDK instance cannot be configured, and RUM was already enabled with no mapper, - // so this registers one on a fresh configuration object to prove the binding marshals - - // the mapper is exercised directly below rather than through an upload. - var configuration = new DDRUMConfiguration(applicationID: RumApplicationId); - configuration.SetViewEventMapper(view => + Assert(viewEventsMapped > 0, "The view event mapper was never invoked."); + Assert(actionEventsMapped > 0, "The action event mapper was never invoked."); + + Report($"mappers invoked: {viewEventsMapped} view, {actionEventsMapped} action"); + } + + /// + /// Checks that a Logs event mapper fires and that a rewritten message survives back into Swift. + /// + private static void LogEventMapperRedacts() + { + var deadline = DateTime.UtcNow.AddSeconds(5); + while (DateTime.UtcNow < deadline && logEventsRedacted == 0) { - seen++; - return view; - }); + Thread.Sleep(100); + } + + Assert(logEventsMapped > 0, "The log event mapper was never invoked."); + Assert(logEventsRedacted > 0, "The log event mapper never saw the message it was meant to redact."); - configuration.SetErrorEventMapper(error => + Report($"log events mapped: {logEventsMapped}, redacted: {logEventsRedacted}"); + } + + /// + /// Checks the type-based URLSession instrumentation helpers. + /// + /// + /// This is the API Datadog documents for automatic resource tracking, and the raw binding takes + /// the delegate class as a bare IntPtr - a signature that accepts IntPtr.Zero happily and then + /// instruments nothing. The helpers resolve the Objective-C class from a managed Type and throw + /// if it does not exist, which is what this checks in both directions. + /// + private static void InstrumentsUrlSessionByType() + { + DDURLSessionInstrumentation.Enable(); + + var configuration = DDURLSessionInstrumentationConfiguration.Create(); + Assert(configuration.DelegateClass != IntPtr.Zero, "The configuration resolved a null delegate class."); + Assert( + configuration.DelegateType == typeof(InstrumentedSessionDelegate), + $"DelegateType round-tripped to {configuration.DelegateType?.Name ?? "null"}."); + + // A type the Objective-C runtime has never heard of must be rejected rather than silently + // instrumenting nothing. + var rejected = false; + try { - seen++; - // Returning null asks the SDK to drop the event entirely, which is what a mapper does - // when it decides an event must never leave the device. - return error; - }); + DDURLSessionInstrumentation.Enable(typeof(SmokeTests)); + } + catch (ArgumentException) + { + rejected = true; + } + + Assert(rejected, "A non-Objective-C type was accepted as a session delegate."); - Assert(configuration.Handle != IntPtr.Zero, "Configuration with mappers has a null handle."); - Report($"registered view and error mappers (invocations so far: {seen})"); + DDURLSessionInstrumentation.Disable(); + Report("instrumented and disabled a URLSession delegate by type"); } /// Every framework shipped as a dynamic framework, which is all but CrashReporter. @@ -285,6 +354,20 @@ private static void EnablesRum() UiKitActionsPredicate = new DDDefaultUIKitRUMActionsPredicate(), }; + // Registered before EnableWith, which is the only point at which mappers can be set. + // Returning the event unchanged is the identity case; returning null would drop it. + configuration.SetViewEventMapper(view => + { + Interlocked.Increment(ref viewEventsMapped); + return view; + }); + + configuration.SetActionEventMapper(action => + { + Interlocked.Increment(ref actionEventsMapped); + return action; + }); + DDRUM.EnableWith(configuration); Assert(DDRUMMonitor.Shared is not null, "DDRUMMonitor.Shared was null after enabling RUM."); @@ -313,7 +396,26 @@ private static void DrivesRum() private static void EnablesLogsAndWritesEveryLevel() { - DDLogs.EnableWith(new DDLogsConfiguration(LocalEndpoint)); + var logsConfiguration = new DDLogsConfiguration(LocalEndpoint); + + // Registered before EnableWith, the only point at which a mapper can be set. Neither the + // DDLogEvent model nor setEventMapper: was bound before - Objective Sharpie omitted the + // whole family - so this is the first release in which a log can be redacted or dropped + // before upload. + logsConfiguration.SetEventMapper(logEvent => + { + Interlocked.Increment(ref logEventsMapped); + + if (logEvent.Message.Contains("secret", StringComparison.Ordinal)) + { + logEvent.Message = "[redacted]"; + Interlocked.Increment(ref logEventsRedacted); + } + + return logEvent; + }); + + DDLogs.EnableWith(logsConfiguration); // The designated initializer takes all eight settings; there is no parameterless form. var logger = DDLogger.CreateWith(new DDLoggerConfiguration( @@ -340,6 +442,9 @@ private static void EnablesLogsAndWritesEveryLevel() logger.RemoveTagWithKey("suite"); logger.RemoveAttributeForKey("attempt"); + // Picked up by the mapper registered above and rewritten to "[redacted]". + logger.Info("this contains a secret value"); + Report("wrote six levels and round-tripped a tag and an attribute"); } From 777675f12ad5c1efc61a6f8e8f802b4ed76dadaa Mon Sep 17 00:00:00 2001 From: Siarhei Bakatsiuk Date: Wed, 22 Jul 2026 19:58:58 +0300 Subject: [PATCH 3/4] Improve API's --- Directory.Build.props | 2 +- README.md | 75 +++++++++++++-- docs/release-notes/2.17.0.1.md | 2 +- docs/release-notes/2.17.0.2.md | 106 +++++++++++++++++++++ src/DatadogNet.Objc.iOS/StructsAndEnums.cs | 15 +++ 5 files changed, 189 insertions(+), 11 deletions(-) create mode 100644 docs/release-notes/2.17.0.2.md diff --git a/Directory.Build.props b/Directory.Build.props index 161e2a8..5c011fb 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -16,7 +16,7 @@ impossible to tell which Datadog release a given CrashReporter package belonged to. --> 2.17.0 - 1 + 2 $(DatadogNativeVersion).$(DatadogBindingRevision) s.bokatuk diff --git a/README.md b/README.md index 8c207a3..bb16fda 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ DDRUM.EnableWith(new DDRUMConfiguration(applicationID: "")); - [Installing](#installing) - [Usage](#usage) - [Convenience API](#convenience-api) +- [API coverage](#api-coverage) - [Migrating from `DatadogCore.iOS` / `DatadogObjc.iOS`](#migrating-from-datadogcoreios--datadogobjcios) - [How this repository works](#how-this-repository-works) - [Building locally](#building-locally) @@ -44,8 +45,8 @@ DDRUM.EnableWith(new DDRUMConfiguration(applicationID: "")); ## Packages Eleven packages, one per native framework in the Datadog release. Versions are -`.` — `2.17.0.1` is dd-sdk-ios **2.17.0**, binding revision -**1**. The fourth component belongs to this repository and advances when the bindings or packaging +`.` — `2.17.0.2` is dd-sdk-ios **2.17.0**, binding revision +**2**. The fourth component belongs to this repository and advances when the bindings or packaging change while the native binaries stay put. | Package | Wraps | Depends on | What it is for | @@ -65,7 +66,7 @@ change while the native binaries stay put. Most apps need one line: ```xml - + ``` Add `DatadogNet.CrashReporting.iOS` for crash reporting and `DatadogNet.WebViewTracking.iOS` for @@ -87,7 +88,7 @@ OS-provided Swift runtime, which is only ABI-stable from 12.2. ```xml - + ``` @@ -100,7 +101,7 @@ Windows head does not try to restore them: ```xml - + ``` @@ -270,6 +271,7 @@ still there; nothing is hidden or renamed. | six methods per level, plus an `NSError` you do not have | `logger.Log(level, message, exception?, attributes?)` | | `DDDatadog.SetUserInfoWithId(id, null, null, empty)` | `DDDatadog.SetUserInfo(id)` | | `DDTrackingConsent.Granted` (a class, not an enum) | `DDDatadog.SetTrackingConsent(TrackingConsent.Granted)` | +| `DDURLSessionInstrumentation.EnableWithConfiguration(...)` with a `Class` as a raw `IntPtr` | `DDURLSessionInstrumentation.Enable()` | The view scope is the one worth adopting everywhere: the raw API is a `StartViewWithKey` / `StopViewWithKey` pair matched by string key, and a view left open by an early return or an @@ -279,6 +281,61 @@ Attribute values may be strings, any numeric type, `bool`, `DateTime`, `DateTime enums, `NSObject`s, arrays, and nested dictionaries. Anything else throws `ArgumentException` rather than being silently dropped. +### Redacting events before upload + +Both RUM and Logs let you rewrite or drop events on the device, before anything is uploaded. This +is the supported way to keep PII out of Datadog. + +```csharp +var logs = new DDLogsConfiguration(customEndpoint: null); +logs.SetEventMapper(logEvent => +{ + logEvent.Message = Redact(logEvent.Message); + return logEvent; // or return null to drop the event entirely +}); +DDLogs.EnableWith(logs); +``` + +RUM has five equivalents — `SetViewEventMapper`, `SetActionEventMapper`, `SetResourceEventMapper`, +`SetErrorEventMapper` and `SetLongTaskEventMapper` — registered on `DDRUMConfiguration` before +`DDRUM.EnableWith`. Mappers can only be set at configuration time. + +### Network instrumentation + +```csharp +DDURLSessionInstrumentation.Enable(); +``` + +`MySessionDelegate` must be an `NSObject` implementing `INSUrlSessionDataDelegate` and carrying a +`[Register]` attribute. The raw binding takes the delegate class as an `IntPtr`, which accepts +`IntPtr.Zero` happily and then instruments nothing; the generic form resolves the Objective-C class +and throws if it does not exist. + +--- + +## API coverage + +The bindings cover **346 of the 347 public Objective-C types** Datadog declares for 2.17.0, and +every documented feature is reachable from C#. Verified by diffing the bound selectors against the +`-Swift.h` header each xcframework ships, not against the documentation. + +The one unbound type is **`DDUIPressRUMActionsPredicate`**, which reports Siri Remote presses. The +protocol is declared in the iOS header, but nothing in the iOS slice accepts it — the property that +consumes it exists only on tvOS — so it is unreachable from a `net*-ios` app. + +Three groups of *members* are also deliberately not bound: + +- **`DDRUMLongTaskEventLongTaskScripts` and `DDRUMVitalEventVital.details`** — reachable only from + inside a long-task or vital event mapper, and describe JavaScript long tasks, which a native iOS + app does not produce. +- **Four `DDTelemetryConfigurationEventTelemetryConfiguration` properties** covering Session Replay + privacy levels. These are read-only fields on a *telemetry* event describing what the SDK + reported about itself; they are not configuration knobs. The real knob, + `DDSessionReplayConfiguration.DefaultPrivacyLevel`, is bound. +- **Three `URLSession` overloads on `DatadogURLSessionDelegate`** — see + [the note in that binding](src/DatadogNet.Internal.iOS/ApiDefinitions.cs); binding them crashes + every consuming app at startup, and they remain callable under their inherited names. + --- ## Migrating from `DatadogCore.iOS` / `DatadogObjc.iOS` @@ -288,7 +345,7 @@ edit — no `using` directive and no call site changes. ```diff - -+ ++ ``` | Old | New | @@ -317,7 +374,7 @@ selector is already registered on the member 'DidFinishCollectingMetrics'. ``` **`CrashReporter` is versioned with everything else.** It was `1.11.2.1`, tracking PLCrashReporter -upstream; it is now `2.17.0.1` like the rest, because it ships inside the same Datadog release and +upstream; it is now `2.17.0.2` like the rest, because it ships inside the same Datadog release and the old numbering made it impossible to tell which Datadog build a given package belonged to. **`net7.0-ios` is gone, `net9`/`net10` are new.** The old packages targeted `net7.0-ios16.1` and @@ -390,7 +447,7 @@ dotnet test tests/DatadogNet.iOS.PackageTests Run the on-simulator smoke tests against the packed packages: ```bash -./.github/scripts/run-simulator-tests.sh 2.17.0.1 net9.0-ios18.0 +./.github/scripts/run-simulator-tests.sh 2.17.0.2 net9.0-ios18.0 ``` Build and run the sample: @@ -436,7 +493,7 @@ dropping a package — update the `FRAMEWORKS` list, add or remove the binding p ## Releasing -Tag it. `v2.17.0.1` builds, tests, publishes all eleven packages to nuget.org via trusted +Tag it. `v2.17.0.2` builds, tests, publishes all eleven packages to nuget.org via trusted publishing, and creates a GitHub release. The tag drives which native SDK is bound, so an older line can be released by tagging it. diff --git a/docs/release-notes/2.17.0.1.md b/docs/release-notes/2.17.0.1.md index 6099250..b34cebe 100644 --- a/docs/release-notes/2.17.0.1.md +++ b/docs/release-notes/2.17.0.1.md @@ -92,7 +92,7 @@ there is no long-lived API key. ## Changed -**Packages are about 74 MB in total, down from what shipping every slice would cost.** The upstream +**The eleven packages come to about 80 MB in total, against roughly a gigabyte if every slice shipped.** The upstream archive carries tvOS slices for every framework, macCatalyst/macOS/watchOS/visionOS for two of them, and a full set of dSYMs — none of it reachable from a `net*-ios` binding, and all of it embedded once per target framework. `FetchXcFrameworks.sh` strips to the two iOS slices and rewrites each diff --git a/docs/release-notes/2.17.0.2.md b/docs/release-notes/2.17.0.2.md new file mode 100644 index 0000000..fa4decd --- /dev/null +++ b/docs/release-notes/2.17.0.2.md @@ -0,0 +1,106 @@ +## What's changed + +Binding-only release. The native SDK is unchanged — still **dd-sdk-ios 2.17.0** — and so are the +package IDs, namespaces and every existing signature. Upgrading is a version bump. + +This release closes two gaps found by diffing the bound selectors against the `-Swift.h` headers +each xcframework actually ships, rather than against Datadog's documentation. Both were places +where the generated binding silently under-reported what the native SDK can do. + +> **Package versions are `.`.** `2.17.0.2` is dd-sdk-ios +> `2.17.0`, binding revision `2`. The fourth component belongs to this repository and advances when +> the bindings or packaging change while the native binaries stay put. + +## Added + +**Log event mapping — `DDLogsConfiguration.SetEventMapper`, and the whole `DDLogEvent` model.** + +Objective Sharpie had omitted all of it: ten `DDLogEvent*` classes, four enums, and +`setEventMapper:` itself. The effect was that RUM had all five of its event mappers while Logs had +none — so there was no supported way to redact or drop a log before it left the device, which is +the mechanism behind most "keep PII out of Datadog" requirements. Transcribed by hand from the +shipped header. + +```csharp +var logs = new DDLogsConfiguration(customEndpoint: null); +logs.SetEventMapper(logEvent => +{ + logEvent.Message = Redact(logEvent.Message); + return logEvent; // or return null to drop the event entirely +}); +DDLogs.EnableWith(logs); +``` + +The mapper receives a fully populated `DDLogEvent`: message, status, error (kind, message, stack, +fingerprint, binary images), logger and service names, thread, app version and build, OS, device, +user info, network connection info, mobile carrier info, attributes and tags. `Message`, `Tags`, +the error fields and the two attribute dictionaries are writable; the rest are read-only, exactly +as the native header declares them. + +New types: `DDLogEvent`, `DDLogEventAttributes`, `DDLogEventBinaryImage`, `DDLogEventCarrierInfo`, +`DDLogEventDd`, `DDLogEventDeviceInfo`, `DDLogEventError`, `DDLogEventNetworkConnectionInfo`, +`DDLogEventOperatingSystem`, `DDLogEventUserInfo`, and the enums `DDLogEventStatus`, +`DDLogEventReachability`, `DDLogEventRadioAccessTechnology` and `DDLogEventInterface`. + +**Type-based `DDURLSessionInstrumentation`.** + +`DDURLSessionInstrumentation` is what Datadog documents for automatic RUM resource collection and +distributed tracing, but it takes an Objective-C `Class`, which reaches C# as a bare `IntPtr`. +Nothing in the generated signature suggests you must pass `Class.GetHandle(typeof(T))`, and the +native call accepts `IntPtr.Zero` without complaint — then instruments nothing, silently. + +```csharp +DDURLSessionInstrumentation.Enable(); +// ... +DDURLSessionInstrumentation.Disable(); +``` + +`Enable()`, `Disable()`, their `Type`-taking overloads, and +`DDURLSessionInstrumentationConfiguration.Create()` resolve the Objective-C class from a managed +type and throw `ArgumentException` if it is not registered. `DelegateType` exposes the same value +as a `Type` rather than an `IntPtr`. The delegate must be an `NSObject` implementing +`INSUrlSessionDataDelegate` and carrying a `[Register]` attribute. + +The `IntPtr` members are untouched, so existing code keeps working. + +## Tests + +The on-simulator suite grows from 14 checks to 17, all running against the packed packages on both +`net9.0-ios18.0` and `net10.0-ios26.0`: + +- **RUM event mappers are invoked.** The mappers are registered on the real configuration before + `DDRUM.EnableWith` and asserted to have fired after events are produced. This is the only part of + the binding that passes a managed delegate into Swift and gets a Swift object back, so if block + marshalling were wrong anywhere it would be wrong here — and the failure would surface as a crash + inside the SDK's event-writing path, far from the call that registered the mapper. +- **A Logs event mapper redacts a message**, and the rewritten event survives back into Swift. +- **A URLSession delegate is instrumented by type**, and a type the Objective-C runtime has never + heard of is rejected rather than silently instrumenting nothing. + +## Documentation + +The README gains an **API coverage** section stating what is bound and what is not, measured rather +than asserted: **346 of the 347 public Objective-C types** Datadog declares for 2.17.0. + +The one unbound type is `DDUIPressRUMActionsPredicate`, which reports Siri Remote presses. The +protocol is declared in the iOS header, but nothing in the iOS slice accepts it — the property that +consumes it exists only on tvOS — so it is unreachable from a `net*-ios` app. + +Three groups of members are also deliberately unbound, and the section says why: the JavaScript +long-task and vital detail models reachable only from inside a mapper, four read-only *telemetry* +fields that look like Session Replay privacy knobs but are not (the real knob, +`DDSessionReplayConfiguration.DefaultPrivacyLevel`, was already bound), and the three +`DatadogURLSessionDelegate` overloads whose binding crashed every consuming app at startup in the +old packages. + +The Convenience API table and the Usage section now cover event mapping and network instrumentation. + +## Upgrading from 2.17.0.1 + +```diff +- ++ +``` + +Nothing is removed or renamed, and the native binaries are byte-for-byte the same build. All eleven +packages move together, as they depend on each other at an exact version. diff --git a/src/DatadogNet.Objc.iOS/StructsAndEnums.cs b/src/DatadogNet.Objc.iOS/StructsAndEnums.cs index 446cee9..53c5e9c 100644 --- a/src/DatadogNet.Objc.iOS/StructsAndEnums.cs +++ b/src/DatadogNet.Objc.iOS/StructsAndEnums.cs @@ -1074,4 +1074,19 @@ public enum DDLogEventRadioAccessTechnology : long Lte = 10, Unknown = 11 } + + /// Network interface kinds reported in DDLogEventNetworkConnectionInfo.AvailableInterfaces. + /// + /// That property is an NSNumber[] because Objective-C has no typed enum arrays, so the + /// values only mean anything once cast to this. + /// + [Native] + public enum DDLogEventInterface : long + { + Wifi = 0, + WiredEthernet = 1, + Cellular = 2, + Loopback = 3, + Other = 4 + } } From 92c32e05a9a18e35604f820a20aaf1aaf31bd6c4 Mon Sep 17 00:00:00 2001 From: Siarhei Bakatsiuk Date: Wed, 22 Jul 2026 23:39:08 +0300 Subject: [PATCH 4/4] Update to dd-sdk-ios 2.30.2 Moves the native SDK from 2.17.0 to 2.30.2, the last release of the 2.x line, and binds everything upstream added across those thirteen minor versions: 29 new types, 17 new enums and 133 new members. The delta was derived by parsing the -Swift.h header each xcframework ships and diffing it against 2.17.0's. Objective Sharpie 3.5.116 cannot be used - its bundled clang fails to parse the module maps in recent iOS SDKs, and it refuses to bind a framework whose recorded DTSDKName is not installed. GenerateBindings.sh documents both and now honours $SHARPIE. Breaking, all upstream: * DDSessionReplay and DDSessionReplayConfiguration no longer exist in the DatadogObjc namespace; Session Replay lives only in DatadogSessionReplay. This also removes the ambiguity that used to require an alias when both namespaces were imported. * Session Replay privacy is three required settings rather than one optional defaultPrivacyLevel. The old form stays bound, deprecated. * DDRUMVitalEventVital.details is replaced by four fields. Fixed: the Session Replay classes switched from SWIFT_CLASS to SWIFT_CLASS_NAMED in 2.19.0, so their Objective-C runtime name is now the plain @interface name rather than the mangled Swift symbol - the binary exports _OBJC_CLASS_$_DDSessionReplay. Carrying the old [BaseType (Name = ...)] over would have compiled and then failed at runtime with "the native class hasn't been loaded". Added, among others: fine-grained Session Replay masking with per-view overrides and deferred recording, account information, SwiftUI auto-tracking predicates, DDSite.Ap2, RUM addAttributes/removeAttributes, backgroundTasksEnabled, trackAnonymousUser, the new RUM view metrics, and PLCrashReporterConfig.maxReportBytes. run-simulator-tests.sh now clears the device app's obj/ and bin/. The native payload is extracted into obj/ and copied into the .app, and neither step re-runs when the package version is unchanged - so re-packing a version left the previous build's xcframeworks in the app. That surfaced here as frameworks loading fine while every Objective-C class lookup missed. Verified: 123 package tests, and 20 on-simulator checks against the packed packages on both net9.0-ios18.0 and net10.0-ios26.0. Co-Authored-By: Claude Opus 4.8 --- .github/scripts/run-simulator-tests.sh | 9 + Directory.Build.props | 4 +- README.md | 80 +- build/GenerateBindings.sh | 28 +- build/checksums.txt | 1 + docs/release-notes/2.30.2.1.md | 149 +++ samples/DatadogNet.iOS.Example/Datadog.cs | 27 +- src/DatadogNet.Core.iOS/ApiDefinitions.cs | 9 + .../ApiDefinitions.cs | 11 + src/DatadogNet.Objc.iOS/ApiDefinitions.cs | 846 +++++++++++++++++- src/DatadogNet.Objc.iOS/StructsAndEnums.cs | 119 ++- .../ApiDefinitions.cs | 122 ++- .../StructsAndEnums.cs | 58 ++ .../DatadogNet.iOS.DeviceTests/SmokeTests.cs | 97 +- 14 files changed, 1448 insertions(+), 112 deletions(-) create mode 100644 docs/release-notes/2.30.2.1.md diff --git a/.github/scripts/run-simulator-tests.sh b/.github/scripts/run-simulator-tests.sh index d63d9f9..6ecb2a2 100755 --- a/.github/scripts/run-simulator-tests.sh +++ b/.github/scripts/run-simulator-tests.sh @@ -46,6 +46,15 @@ for package in objc core internal logs rum sessionreplay trace webviewtracking c rm -rf "${HOME}/.nuget/packages/datadognet.${package}.ios/${VERSION}" done +# The app's own intermediate output has to go too, not just the NuGet cache. The native payload +# is extracted out of the package into obj/ and copied into the .app, and neither step re-runs +# when the package version string is unchanged - so a rebuilt package of the same version leaves +# the *previous* build's xcframeworks embedded in the app. That produced a genuinely baffling +# failure once: the frameworks were present and loaded, but every Objective-C class lookup missed, +# because the app was running the previous release's binaries. +rm -rf "${REPO_ROOT}/tests/DatadogNet.iOS.DeviceTests/obj" \ + "${REPO_ROOT}/tests/DatadogNet.iOS.DeviceTests/bin" + echo "==> building device tests (version=${VERSION}, tfm=${TARGET_FRAMEWORK}, sdk=${sdk_version})" ( cd "${SDK_DIR}" && dotnet build "${PROJECT}" \ --configuration Release \ diff --git a/Directory.Build.props b/Directory.Build.props index 5c011fb..f843958 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -15,8 +15,8 @@ DataDog-SDK-iOS-net repository versioned CrashReporter.iOS as 1.11.2.1 instead; that made it impossible to tell which Datadog release a given CrashReporter package belonged to. --> - 2.17.0 - 2 + 2.30.2 + 1 $(DatadogNativeVersion).$(DatadogBindingRevision) s.bokatuk diff --git a/README.md b/README.md index bb16fda..5c46a41 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ DDRUM.EnableWith(new DDRUMConfiguration(applicationID: "")); ## Packages Eleven packages, one per native framework in the Datadog release. Versions are -`.` — `2.17.0.2` is dd-sdk-ios **2.17.0**, binding revision +`.` — `2.30.2.1` is dd-sdk-ios **2.30.2**, binding revision **2**. The fourth component belongs to this repository and advances when the bindings or packaging change while the native binaries stay put. @@ -66,7 +66,7 @@ change while the native binaries stay put. Most apps need one line: ```xml - + ``` Add `DatadogNet.CrashReporting.iOS` for crash reporting and `DatadogNet.WebViewTracking.iOS` for @@ -88,7 +88,7 @@ OS-provided Swift runtime, which is only ABI-stable from 12.2. ```xml - + ``` @@ -101,7 +101,7 @@ Windows head does not try to restore them: ```xml - + ``` @@ -207,18 +207,29 @@ helpers. Propagation style is chosen with `DDTracingHeaderType` — `Datadog`, ` ### Session Replay ```csharp -DDSessionReplay.EnableWith(new DDSessionReplayConfiguration(replaySampleRate: 100) -{ - DefaultPrivacyLevel = DDSessionReplayConfigurationPrivacyLevel.Mask, -}); +using DatadogSessionReplay; + +DDSessionReplay.EnableWith(new DDSessionReplayConfiguration( + replaySampleRate: 100, + textAndInputPrivacyLevel: DDTextAndInputPrivacyLevel.MaskAll, + imagePrivacyLevel: DDImagePrivacyLevel.MaskAll, + touchPrivacyLevel: DDTouchPrivacyLevel.Hide)); ``` -Requires RUM. Privacy levels are `Allow`, `MaskUserInput` and `Mask`; the level decides what is -redacted *on the device*, before anything is uploaded. Loosen it deliberately. +Requires RUM. The three levels decide what is redacted *on the device*, before anything is +uploaded, and the initializer requires all three so the choice is never left implicit. Loosen them +deliberately. + +A single view can override the session-wide levels: + +```csharp +var overrides = myView.GetDdSessionReplayPrivacyOverrides(); +overrides.TextAndInputPrivacy = DDTextAndInputPrivacyLevelOverride.MaskAll; +overrides.Hide = new NSNumber(true); +``` -> `DDSessionReplay` exists in two namespaces — `DatadogObjc` and `DatadogSessionReplay`. They are -> different native classes that do the same thing. Prefer the `DatadogObjc` one so a single -> `using DatadogObjc;` covers the whole SDK; importing both namespaces makes the name ambiguous. +Set `StartRecordingImmediately = false` to enable the feature without recording, then call +`DDSessionReplay.StartRecording()` once the user has consented, and `StopRecording()` to pause. ### Crash reporting @@ -315,23 +326,23 @@ and throws if it does not exist. ## API coverage -The bindings cover **346 of the 347 public Objective-C types** Datadog declares for 2.17.0, and -every documented feature is reachable from C#. Verified by diffing the bound selectors against the -`-Swift.h` header each xcframework ships, not against the documentation. +Every public Objective-C type Datadog declares for 2.30.2 is bound except one, and every documented +feature is reachable from C#. Measured by parsing the `-Swift.h` header each xcframework ships and +diffing it against the bound selectors — not by reading the documentation. -The one unbound type is **`DDUIPressRUMActionsPredicate`**, which reports Siri Remote presses. The -protocol is declared in the iOS header, but nothing in the iOS slice accepts it — the property that -consumes it exists only on tvOS — so it is unreachable from a `net*-ios` app. +The unbound type is **`DDRUMLongTaskEventLongTaskScripts`**, with the `scripts` property that +returns it. It describes JavaScript long tasks, is reachable only from inside a RUM long-task event +mapper, and a native iOS app does not produce them. -Three groups of *members* are also deliberately not bound: +Three further groups of *members* are deliberately not bound: -- **`DDRUMLongTaskEventLongTaskScripts` and `DDRUMVitalEventVital.details`** — reachable only from - inside a long-task or vital event mapper, and describe JavaScript long tasks, which a native iOS - app does not produce. -- **Four `DDTelemetryConfigurationEventTelemetryConfiguration` properties** covering Session Replay - privacy levels. These are read-only fields on a *telemetry* event describing what the SDK - reported about itself; they are not configuration knobs. The real knob, - `DDSessionReplayConfiguration.DefaultPrivacyLevel`, is bound. +- **Four `DDTelemetryConfigurationEventTelemetryConfiguration` properties** named after the Session + Replay privacy levels. These are read-only fields on a *telemetry* event, describing what the SDK + reported about its own configuration; they are not knobs. The real ones are on + `DDSessionReplayConfiguration`, and all of those are bound. +- **`initWithSamplingRate:` and `initWithSamplingRate:injectEncoding:` on the tracing header + writers** — deprecated upstream, and differing from the bound `initWithSampleRate:` forms only by + argument label, which Objective Sharpie cannot project as two separate initializers. - **Three `URLSession` overloads on `DatadogURLSessionDelegate`** — see [the note in that binding](src/DatadogNet.Internal.iOS/ApiDefinitions.cs); binding them crashes every consuming app at startup, and they remain callable under their inherited names. @@ -345,7 +356,7 @@ edit — no `using` directive and no call site changes. ```diff - -+ ++ ``` | Old | New | @@ -374,7 +385,7 @@ selector is already registered on the member 'DidFinishCollectingMetrics'. ``` **`CrashReporter` is versioned with everything else.** It was `1.11.2.1`, tracking PLCrashReporter -upstream; it is now `2.17.0.2` like the rest, because it ships inside the same Datadog release and +upstream; it is now `2.30.2.1` like the rest, because it ships inside the same Datadog release and the old numbering made it impossible to tell which Datadog build a given package belonged to. **`net7.0-ios` is gone, `net9`/`net10` are new.** The old packages targeted `net7.0-ios16.1` and @@ -439,7 +450,7 @@ sample directly, as the command in the previous section does. Requires macOS, Xcode, and the .NET 9 and .NET 10 SDKs with the `ios` workload. ```bash -./build/FetchXcFrameworks.sh # ~116 MB, verified against build/checksums.txt +./build/FetchXcFrameworks.sh # ~100 MB, verified against build/checksums.txt ./build/BuildNugets.sh # packs all eleven into artifacts/ dotnet test tests/DatadogNet.iOS.PackageTests ``` @@ -447,7 +458,7 @@ dotnet test tests/DatadogNet.iOS.PackageTests Run the on-simulator smoke tests against the packed packages: ```bash -./.github/scripts/run-simulator-tests.sh 2.17.0.2 net9.0-ios18.0 +./.github/scripts/run-simulator-tests.sh 2.30.2.1 net9.0-ios18.0 ``` Build and run the sample: @@ -493,7 +504,7 @@ dropping a package — update the `FRAMEWORKS` list, add or remove the binding p ## Releasing -Tag it. `v2.17.0.2` builds, tests, publishes all eleven packages to nuget.org via trusted +Tag it. `v2.30.2.1` builds, tests, publishes all eleven packages to nuget.org via trusted publishing, and creates a GitHub release. The tag drives which native SDK is bound, so an older line can be released by tagging it. @@ -509,9 +520,8 @@ Curated notes in `docs/release-notes/.md` replace the generated commit common cause. Set `DDDatadog.VerbosityLevel = DDSDKVerbosityLevel.Debug` to see the SDK's own diagnostics in the Xcode console. -**`DDSessionReplay` is an ambiguous reference.** You have imported both `DatadogObjc` and -`DatadogSessionReplay`. Use the `DatadogObjc` one, or alias: -`using SessionReplay = DatadogSessionReplay;`. +**`DDSessionReplay` could not be found.** It moved out of the `DatadogObjc` namespace in +dd-sdk-ios 2.19.0 and now lives only in `DatadogSessionReplay`. Add `using DatadogSessionReplay;`. **`This version of .NET for iOS requires Xcode 26.0`.** Only affects `net10.0-ios26.0`. See [Building locally](#building-locally). diff --git a/build/GenerateBindings.sh b/build/GenerateBindings.sh index 0dfc457..74e9afc 100755 --- a/build/GenerateBindings.sh +++ b/build/GenerateBindings.sh @@ -11,10 +11,29 @@ set -e # ./GenerateBindings.sh # every framework # ./GenerateBindings.sh DatadogObjc # just one # -# Requires Objective Sharpie, which is not on nuget.org and has to be installed separately: +# Requires Objective Sharpie, which is not on nuget.org and has to be obtained separately: # # https://aka.ms/objective-sharpie # +# Set SHARPIE to point at the binary if it is not on PATH. The installer needs administrator +# rights, but the package can also just be expanded and run in place, which is enough: +# +# curl -fsSL -o sharpie.pkg https://aka.ms/objective-sharpie +# pkgutil --expand-full sharpie.pkg sharpie-x +# export SHARPIE="$PWD/sharpie-x/Framework.pkg/Payload/Library/Frameworks/ObjectiveSharpie.framework/Versions/*/bin/sharpie" +# +# KNOWN LIMITATION: Objective Sharpie 3.5.116 (the current release) bundles a clang that cannot +# parse the module maps in recent iOS SDKs. Against the 18.5 and 26.5 SDKs it fails with +# +# module '_stddef' requires feature 'found_incompatible_headers__check_search_paths' +# unknown type name 'size_t' +# +# before emitting anything. It also refuses to bind a framework whose recorded DTSDKName is not +# installed ("framework requires SDK 'iphoneos18.2'"), which is normal for a downloaded binary. +# Until sharpie ships a newer clang, the 2.30.2 binding delta was produced by reading the shipped +# -Swift.h headers directly and diffing them against the previous version's - see the release +# notes. Keep this script for the day it works again. +# # IMPORTANT: the output is a starting point, not a drop-in replacement. It is written to # ../Binding/ deliberately, *not* over the committed ApiDefinitions.cs files, because those carry # fixes that regenerating would silently undo. Diff the two and port the real changes across. @@ -42,8 +61,9 @@ ROOT="$(cd .. && pwd)" LIBS="$ROOT/libs" OUTPUT="$ROOT/Binding" -if ! command -v sharpie >/dev/null 2>&1; then - echo "error: sharpie is not installed - see https://aka.ms/objective-sharpie" >&2 +SHARPIE="${SHARPIE:-sharpie}" +if ! command -v "$SHARPIE" >/dev/null 2>&1; then + echo "error: sharpie not found - set SHARPIE, or see https://aka.ms/objective-sharpie" >&2 exit 1 fi @@ -86,7 +106,7 @@ for framework in $FRAMEWORKS; do # -scope keeps the output to this framework's own headers: without it, a framework that # imports another (all of them import DatadogInternal) has the imported API duplicated into # its definitions, and the same type ends up bound in several packages. - sharpie bind \ + "$SHARPIE" bind \ --output "$target" \ --namespace "$framework" \ --sdk "iphoneos$SDK" \ diff --git a/build/checksums.txt b/build/checksums.txt index ff3debe..a1f34c3 100644 --- a/build/checksums.txt +++ b/build/checksums.txt @@ -18,3 +18,4 @@ # Format: 2.17.0 e948375d758527118550e5a5f89a332264309b19c96e3ec56e2bf84789b6f5f9 +2.30.2 0f944182502801c7bf7e47ddc2a6a3be54fcc6067fb7f9ef8a495a86361baaa7 diff --git a/docs/release-notes/2.30.2.1.md b/docs/release-notes/2.30.2.1.md new file mode 100644 index 0000000..a4e94f4 --- /dev/null +++ b/docs/release-notes/2.30.2.1.md @@ -0,0 +1,149 @@ +## What's changed + +Updates the native SDK from **dd-sdk-ios 2.17.0 to 2.30.2**, the last release of the 2.x line — +thirteen minor versions, published between September 2024 and October 2025. The 2.x line continued +to receive fixes after 3.0.0 shipped, so 2.30.2 is genuinely the end of the line rather than the +last release before the split. + +Everything upstream added is bound: **29 new types, 17 new enums and 133 new members** across the +eleven packages — 27 types and 120 members in `DatadogObjc` alone. Measured by parsing the +`-Swift.h` header each xcframework ships and diffing it against 2.17.0's, not by reading the +documentation. + +> **Package versions are `.`.** `2.30.2.1` is dd-sdk-ios +> `2.30.2`, binding revision `1`. The revision restarts at 1 for each new native version. + +## ⚠️ Breaking changes + +**`DDSessionReplay` and `DDSessionReplayConfiguration` no longer exist in the `DatadogObjc` +namespace.** Upstream removed the parallel copies it used to declare there; Session Replay now +lives only in `DatadogSessionReplay`. If you wrote `using DatadogObjc;` and referred to +`DDSessionReplay`, add the other namespace: + +```diff + using DatadogObjc; ++using DatadogSessionReplay; +``` + +The upside: the two identically named types that previously made `DDSessionReplay` ambiguous when +both namespaces were imported are gone, so no alias is needed any more. + +**Session Replay privacy is now three settings, not one.** `defaultPrivacyLevel` is deprecated +upstream in favour of independent text/input, image and touch levels, and the new initializer +requires all three — the choice can no longer be left implicit. + +```diff +-DDSessionReplay.EnableWith(new DDSessionReplayConfiguration(replaySampleRate: 100) +-{ +- DefaultPrivacyLevel = DDSessionReplayConfigurationPrivacyLevel.Mask, +-}); ++DDSessionReplay.EnableWith(new DDSessionReplayConfiguration( ++ replaySampleRate: 100, ++ textAndInputPrivacyLevel: DDTextAndInputPrivacyLevel.MaskAll, ++ imagePrivacyLevel: DDImagePrivacyLevel.MaskAll, ++ touchPrivacyLevel: DDTouchPrivacyLevel.Hide)); +``` + +The old single-argument initializer and `DefaultPrivacyLevel` are still bound, so existing code +keeps compiling — but they are deprecated upstream and will be removed. `Allow`, `Mask` and +`MaskUserInput` map onto the new levels as Datadog documents. + +**`DDRUMVitalEventVital.details` is gone**, replaced by `failureReason`, `parentId`, `stepType` and +`vitalDescription`. It is only reachable from inside a RUM event mapper, so most apps will not +notice. + +## Fixed + +**Session Replay would have failed at runtime had the binding been carried over unchanged.** +Through 2.17.0 these classes were declared `SWIFT_CLASS("_TtC20DatadogSessionReplay...")`, which +sets the Objective-C runtime name to the mangled Swift symbol — so the binding spelled that mangled +name out in `[BaseType (Name = ...)]`. From 2.19.0 they are declared `SWIFT_CLASS_NAMED`, which +expands to `swift_name(...)` only and leaves the runtime name as the plain `@interface` name. The +binary confirms it: it exports `_OBJC_CLASS_$_DDSessionReplay`, not the mangled symbol. The `Name=` +attributes are therefore removed. Keeping them would have compiled cleanly and then failed with +"the native class hasn't been loaded" the first time a replay type was touched. + +## Added + +**Fine-grained Session Replay masking** (2.18.0) — `DDTextAndInputPrivacyLevel`, +`DDImagePrivacyLevel` and `DDTouchPrivacyLevel`. + +**Per-view privacy overrides** (2.19.0) — `DDSessionReplayPrivacyOverrides`, reached through a +category on `UIView`, so a single view can be masked or hidden regardless of the session-wide +level: + +```csharp +var overrides = myView.GetDdSessionReplayPrivacyOverrides(); +overrides.TextAndInputPrivacy = DDTextAndInputPrivacyLevelOverride.MaskAll; +overrides.Hide = new NSNumber(true); +``` + +**Deferred replay recording** (2.21.0) — `StartRecordingImmediately` on the configuration, plus +`DDSessionReplay.StartRecording()` and `StopRecording()`, for apps that start recording only after +consent or on a particular screen. + +**Account information** (2.29.0) — `DDDatadog.SetAccountInfoWithAccountId`, `AddAccountExtraInfo` +and `ClearAccountInfo`. Account details propagate to Logs, RUM, Traces and error reporting. +`DDLogEvent` gains a matching `AccountInfo`, and every RUM event model gains `Account`. + +**`DDDatadog.ClearUserInfo`** (2.30.0), and `SetUserInfoWithUserId`, which requires an id where the +old overload did not (2.24.0). + +**SwiftUI auto-tracking** (2.29.0) — `DDSwiftUIRUMViewsPredicate` and +`DDSwiftUIRUMActionsPredicate`, their `DDDefault*` implementations, and the +`SwiftUIViewsPredicate` / `SwiftUIActionsPredicate` properties on `DDRUMConfiguration`. Bound with +the same `[Model, Protocol]` shape as the UIKit predicates, so an app can subclass the generated +class instead of implementing the interface. + +**The AP2 datacenter** (2.29.0) — `DDSite.Ap2`. + +**RUM attribute APIs** (2.23.0) — `DDRUMMonitor.AddAttributes` and `RemoveAttributesForKeys`, for +setting attributes on the monitor rather than per event. + +**`DDConfiguration.BackgroundTasksEnabled`** (2.22.0), and `DDRUMConfiguration.TrackAnonymousUser` +(2.24.0) with the matching `AnonymousId` on RUM user models. + +**New RUM view metrics** reachable through the event mappers: Time To Network Settled and +Interaction To Next View (2.23.0), hang and hitch rates (2.25.0), and the view performance models +(CLS, FCP, FID, INP, LCP, FBC) and slow-frame detail. Device models gain battery level, brightness, +locale, power-saving mode and time zone (2.30.0), and every event gains `Ddtags` (2.30.1). + +**`DDInternalLogger`** (2.19.0) — the Objective-C entry point to the SDK's internal telemetry. + +**`PLCrashReporterConfig.MaxReportBytes`** and the initializer that sets it, from the PLCrashReporter +update in 2.25.0. + +## Tests + +The on-simulator suite grows to **20 checks**, running against the packed packages on both +`net9.0-ios18.0` and `net10.0-ios26.0`. New coverage: fine-grained Session Replay privacy with +deferred recording start/stop, per-view privacy overrides set and read back through a fresh +accessor, account info set/extend/clear alongside user-info clearing, and the new RUM attribute +APIs and AP2 site. + +`run-simulator-tests.sh` now deletes the device app's `obj/` and `bin/` before building. Clearing +the NuGet cache is not enough: the native payload is extracted into `obj/` and copied into the +`.app`, and neither step re-runs when the package version string is unchanged — so re-packing the +same version left the *previous* build's xcframeworks embedded in the app. That produced a +genuinely misleading failure during this upgrade, in which the frameworks were present and loaded +but every Objective-C class lookup missed, because the app was running 2.17.0 binaries against a +2.30.2 binding. + +## Notes + +**Package size dropped from about 80 MB to about 50 MB** across the eleven packages. The frameworks +themselves are smaller in 2.30.2; nothing changed in how they are stripped or packed. + +**Objective Sharpie could not be used for this upgrade.** Version 3.5.116, the current release, +bundles a clang that cannot parse the module maps in recent iOS SDKs — it fails with +`module '_stddef' requires feature 'found_incompatible_headers__check_search_paths'` against both +the 18.5 and 26.5 SDKs, before emitting anything. It also refuses to bind a framework whose +recorded `DTSDKName` is not installed, which is normal for a downloaded binary. The delta was +therefore derived by parsing the shipped headers and diffing them against the previous version's. +`build/GenerateBindings.sh` documents both problems and now honours a `SHARPIE` environment +variable, so the package can be expanded and run in place without administrator rights once it +works again. + +**No new packages.** dd-sdk-ios 2.30.2 ships the same eleven frameworks as 2.17.0. +`DatadogFlags` and `DatadogProfiling` are 3.x-only, as is the removal of `DatadogObjc` and the move +from PLCrashReporter to KSCrash. diff --git a/samples/DatadogNet.iOS.Example/Datadog.cs b/samples/DatadogNet.iOS.Example/Datadog.cs index 7519891..16770ae 100644 --- a/samples/DatadogNet.iOS.Example/Datadog.cs +++ b/samples/DatadogNet.iOS.Example/Datadog.cs @@ -1,5 +1,6 @@ using DatadogCrashReporting; using DatadogObjc; +using DatadogSessionReplay; namespace DatadogNetExample; @@ -113,19 +114,17 @@ private static void EnableSessionReplay() { // Session Replay requires RUM, so it is enabled last. // - // MaskAll is the default here on purpose. Replay records the screen, and the privacy level - // decides what is redacted before anything leaves the device: MaskUserInput hides only what - // the user typed, while Mask also hides all text and images. Loosen this deliberately, not - // by accident. - // DDSessionReplay exists in two namespaces: this one, from DatadogNet.Objc.iOS, and - // another in DatadogNet.SessionReplay.iOS. They are not the same class - each wraps a - // different native type (_TtC11DatadogObjc15DDSessionReplay against - // _TtC20DatadogSessionReplay15DDSessionReplay) - but they do the same thing. Prefer the - // DatadogObjc one, as here, so a single `using DatadogObjc;` covers the whole SDK; - // importing both namespaces makes the name ambiguous and needs an alias to resolve. - DDSessionReplay.EnableWith(new DDSessionReplayConfiguration(replaySampleRate: 100) - { - DefaultPrivacyLevel = DDSessionReplayConfigurationPrivacyLevel.Mask, - }); + // From dd-sdk-ios 2.19.0 the single defaultPrivacyLevel is replaced by three independent + // levels, and they are required by the initializer rather than defaulted - so the choice + // is made deliberately rather than inherited. Replay records the screen, and these decide + // what is redacted *on the device*, before anything is uploaded. + // + // These are the most private settings: mask every text and input, mask every image, and + // hide touches. Loosen them deliberately, not by accident. + DDSessionReplay.EnableWith(new DDSessionReplayConfiguration( + replaySampleRate: 100, + textAndInputPrivacyLevel: DDTextAndInputPrivacyLevel.MaskAll, + imagePrivacyLevel: DDImagePrivacyLevel.MaskAll, + touchPrivacyLevel: DDTouchPrivacyLevel.Hide)); } } diff --git a/src/DatadogNet.Core.iOS/ApiDefinitions.cs b/src/DatadogNet.Core.iOS/ApiDefinitions.cs index 824faad..5a21430 100644 --- a/src/DatadogNet.Core.iOS/ApiDefinitions.cs +++ b/src/DatadogNet.Core.iOS/ApiDefinitions.cs @@ -1,6 +1,7 @@ using System; using DatadogCore; using Foundation; +using ObjCRuntime; namespace DatadogCore { @@ -26,6 +27,14 @@ interface __dd_private_AppLaunchHandler [Export ("isActivePrewarm")] bool IsActivePrewarm { get; } + // -(instancetype _Nonnull)initWithProcessInfo:(NSProcessInfo * _Nonnull)processInfo; + [Export ("initWithProcessInfo:")] + NativeHandle Constructor (NSProcessInfo processInfo); + + // -(void)observeNotificationCenter:(NSNotificationCenter * _Nonnull)notificationCenter; + [Export ("observeNotificationCenter:")] + void ObserveNotificationCenter (NSNotificationCenter notificationCenter); + // -(void)setApplicationDidBecomeActiveCallback:(UIApplicationDidBecomeActiveCallback _Nonnull)callback; [Export ("setApplicationDidBecomeActiveCallback:")] void SetApplicationDidBecomeActiveCallback (UIApplicationDidBecomeActiveCallback callback); diff --git a/src/DatadogNet.CrashReporter.iOS/ApiDefinitions.cs b/src/DatadogNet.CrashReporter.iOS/ApiDefinitions.cs index 5c500f9..d8cd0df 100644 --- a/src/DatadogNet.CrashReporter.iOS/ApiDefinitions.cs +++ b/src/DatadogNet.CrashReporter.iOS/ApiDefinitions.cs @@ -35,6 +35,17 @@ interface PLCrashReporterConfig [Export ("initWithSignalHandlerType:symbolicationStrategy:shouldRegisterUncaughtExceptionHandler:basePath:")] NativeHandle Constructor (PLCrashReporterSignalHandlerType signalHandlerType, PLCrashReporterSymbolicationStrategy symbolicationStrategy, bool shouldRegisterUncaughtExceptionHandler, string basePath); + // -(instancetype)initWithSignalHandlerType:symbolicationStrategy: + // shouldRegisterUncaughtExceptionHandler:basePath:maxReportBytes: + // Added in PLCrashReporter 1.12, shipped from dd-sdk-ios 2.25.0. + [Export ("initWithSignalHandlerType:symbolicationStrategy:shouldRegisterUncaughtExceptionHandler:basePath:maxReportBytes:")] + NativeHandle Constructor (PLCrashReporterSignalHandlerType signalHandlerType, PLCrashReporterSymbolicationStrategy symbolicationStrategy, bool shouldRegisterUncaughtExceptionHandler, string basePath, nuint maxReportBytes); + + // @property (readonly, nonatomic) NSUInteger maxReportBytes; + // The cap on a generated crash report. Reports above it are truncated rather than dropped. + [Export ("maxReportBytes")] + nuint MaxReportBytes { get; } + // @property (readonly, nonatomic) NSString * basePath; [Export ("basePath")] string BasePath { get; } diff --git a/src/DatadogNet.Objc.iOS/ApiDefinitions.cs b/src/DatadogNet.Objc.iOS/ApiDefinitions.cs index 1621fd6..2675624 100644 --- a/src/DatadogNet.Objc.iOS/ApiDefinitions.cs +++ b/src/DatadogNet.Objc.iOS/ApiDefinitions.cs @@ -87,6 +87,9 @@ interface DDConfiguration [Export ("initWithClientToken:env:")] [DesignatedInitializer] NativeHandle Constructor (string clientToken, string env); + + [Export ("backgroundTasksEnabled")] + bool BackgroundTasksEnabled { get; set; } } partial interface IDDDataEncryption { } @@ -163,6 +166,26 @@ interface DDDatadog [Static] [Export ("clearAllData")] void ClearAllData (); + + [Static] + [Export ("setUserInfoWithUserId:name:email:extraInfo:")] + void SetUserInfoWithUserId (string userId, [NullAllowed] string name, [NullAllowed] string email, NSDictionary extraInfo); + + [Static] + [Export ("clearUserInfo")] + void ClearUserInfo (); + + [Static] + [Export ("setAccountInfoWithAccountId:name:extraInfo:")] + void SetAccountInfoWithAccountId (string accountId, [NullAllowed] string name, NSDictionary extraInfo); + + [Static] + [Export ("addAccountExtraInfo:")] + void AddAccountExtraInfo (NSDictionary extraInfo); + + [Static] + [Export ("clearAccountInfo")] + void ClearAccountInfo (); } // @protocol DDUITouchRUMActionsPredicate @@ -611,6 +634,12 @@ interface DDRUMActionEvent // @property (readonly, nonatomic, strong) DDRUMActionEventView * _Nonnull view; [Export ("view", ArgumentSemantic.Strong)] DDRUMActionEventView View { get; } + + [Export ("account", ArgumentSemantic.Strong)] + DDRUMActionEventRUMAccount Account { get; } + + [Export ("ddtags", ArgumentSemantic.Copy)] + string Ddtags { get; } } // @interface DDRUMActionEventAction : NSObject @@ -723,6 +752,9 @@ interface DDRUMActionEventApplication // @property (readonly, copy, nonatomic) NSString * _Nonnull id; [Export ("id")] string Id { get; } + + [Export ("currentLocale", ArgumentSemantic.Copy)] + string CurrentLocale { get; } } // @interface DDRUMActionEventContainer : NSObject @@ -773,6 +805,9 @@ interface DDRUMActionEventDD // @property (readonly, nonatomic, strong) DDRUMActionEventDDSession * _Nullable session; [NullAllowed, Export ("session", ArgumentSemantic.Strong)] DDRUMActionEventDDSession Session { get; } + + [Export ("sdkName", ArgumentSemantic.Copy)] + string SdkName { get; } } // @interface DDRUMActionEventDDAction : NSObject @@ -787,6 +822,9 @@ interface DDRUMActionEventDDAction // @property (readonly, nonatomic, strong) DDRUMActionEventDDActionTarget * _Nullable target; [NullAllowed, Export ("target", ArgumentSemantic.Strong)] DDRUMActionEventDDActionTarget Target { get; } + + [Export ("nameSource")] + DDRUMActionEventDDActionNameSource NameSource { get; set; } } // @interface DDRUMActionEventDDActionPosition : NSObject @@ -833,6 +871,9 @@ interface DDRUMActionEventDDConfiguration // @property (readonly, nonatomic, strong) NSNumber * _Nonnull sessionSampleRate; [Export ("sessionSampleRate", ArgumentSemantic.Strong)] NSNumber SessionSampleRate { get; } + + [Export ("profilingSampleRate", ArgumentSemantic.Strong)] + NSNumber ProfilingSampleRate { get; } } // @interface DDRUMActionEventDDSession : NSObject @@ -943,6 +984,24 @@ interface DDRUMActionEventRUMDevice // @property (readonly, nonatomic) enum DDRUMActionEventRUMDeviceRUMDeviceType type; [Export ("type")] DDRUMActionEventRUMDeviceRUMDeviceType Type { get; } + + [Export ("batteryLevel", ArgumentSemantic.Strong)] + NSNumber BatteryLevel { get; } + + [Export ("brightnessLevel", ArgumentSemantic.Strong)] + NSNumber BrightnessLevel { get; } + + [Export ("locale", ArgumentSemantic.Copy)] + string Locale { get; } + + [Export ("locales", ArgumentSemantic.Copy)] + string[] Locales { get; } + + [Export ("powerSavingMode", ArgumentSemantic.Strong)] + NSNumber PowerSavingMode { get; } + + [Export ("timeZone", ArgumentSemantic.Copy)] + string TimeZone { get; } } // @interface DDRUMActionEventRUMEventAttributes : NSObject @@ -1015,6 +1074,9 @@ interface DDRUMActionEventRUMUser // @property (readonly, copy, nonatomic) NSDictionary * _Nonnull usrInfo; [Export ("usrInfo", ArgumentSemantic.Copy)] NSDictionary UsrInfo { get; } + + [Export ("anonymousId", ArgumentSemantic.Copy)] + string AnonymousId { get; } } // @interface DDRUMActionEventSession : NSObject @@ -1146,6 +1208,15 @@ interface DDRUMConfiguration // @property (copy, nonatomic) NSURL * _Nullable customEndpoint; [NullAllowed, Export ("customEndpoint", ArgumentSemantic.Copy)] NSUrl CustomEndpoint { get; set; } + + [Export ("swiftUIViewsPredicate", ArgumentSemantic.Strong)] + IDDSwiftUIRUMViewsPredicate SwiftUIViewsPredicate { get; set; } + + [Export ("swiftUIActionsPredicate", ArgumentSemantic.Strong)] + IDDSwiftUIRUMActionsPredicate SwiftUIActionsPredicate { get; set; } + + [Export ("trackAnonymousUser")] + bool TrackAnonymousUser { get; set; } } // @interface DDRUMErrorEvent : NSObject @@ -1248,6 +1319,12 @@ interface DDRUMErrorEvent // @property (readonly, nonatomic, strong) DDRUMErrorEventView * _Nonnull view; [Export ("view", ArgumentSemantic.Strong)] DDRUMErrorEventView View { get; } + + [Export ("account", ArgumentSemantic.Strong)] + DDRUMErrorEventRUMAccount Account { get; } + + [Export ("ddtags", ArgumentSemantic.Copy)] + string Ddtags { get; } } // @interface DDRUMErrorEventAction : NSObject @@ -1282,6 +1359,9 @@ interface DDRUMErrorEventApplication // @property (readonly, copy, nonatomic) NSString * _Nonnull id; [Export ("id")] string Id { get; } + + [Export ("currentLocale", ArgumentSemantic.Copy)] + string CurrentLocale { get; } } // @interface DDRUMErrorEventContainer : NSObject @@ -1328,6 +1408,9 @@ interface DDRUMErrorEventDD // @property (readonly, nonatomic, strong) DDRUMErrorEventDDSession * _Nullable session; [NullAllowed, Export ("session", ArgumentSemantic.Strong)] DDRUMErrorEventDDSession Session { get; } + + [Export ("sdkName", ArgumentSemantic.Copy)] + string SdkName { get; } } // @interface DDRUMErrorEventDDConfiguration : NSObject @@ -1342,6 +1425,9 @@ interface DDRUMErrorEventDDConfiguration // @property (readonly, nonatomic, strong) NSNumber * _Nonnull sessionSampleRate; [Export ("sessionSampleRate", ArgumentSemantic.Strong)] NSNumber SessionSampleRate { get; } + + [Export ("profilingSampleRate", ArgumentSemantic.Strong)] + NSNumber ProfilingSampleRate { get; } } // @interface DDRUMErrorEventDDSession : NSObject @@ -1712,6 +1798,24 @@ interface DDRUMErrorEventRUMDevice // @property (readonly, nonatomic) enum DDRUMErrorEventRUMDeviceRUMDeviceType type; [Export ("type")] DDRUMErrorEventRUMDeviceRUMDeviceType Type { get; } + + [Export ("batteryLevel", ArgumentSemantic.Strong)] + NSNumber BatteryLevel { get; } + + [Export ("brightnessLevel", ArgumentSemantic.Strong)] + NSNumber BrightnessLevel { get; } + + [Export ("locale", ArgumentSemantic.Copy)] + string Locale { get; } + + [Export ("locales", ArgumentSemantic.Copy)] + string[] Locales { get; } + + [Export ("powerSavingMode", ArgumentSemantic.Strong)] + NSNumber PowerSavingMode { get; } + + [Export ("timeZone", ArgumentSemantic.Copy)] + string TimeZone { get; } } // @interface DDRUMErrorEventRUMEventAttributes : NSObject @@ -1784,6 +1888,9 @@ interface DDRUMErrorEventRUMUser // @property (readonly, copy, nonatomic) NSDictionary * _Nonnull usrInfo; [Export ("usrInfo", ArgumentSemantic.Copy)] NSDictionary UsrInfo { get; } + + [Export ("anonymousId", ArgumentSemantic.Copy)] + string AnonymousId { get; } } // @interface DDRUMErrorEventSession : NSObject @@ -1948,6 +2055,12 @@ interface DDRUMLongTaskEvent // @property (readonly, nonatomic, strong) DDRUMLongTaskEventView * _Nonnull view; [Export ("view", ArgumentSemantic.Strong)] DDRUMLongTaskEventView View { get; } + + [Export ("account", ArgumentSemantic.Strong)] + DDRUMLongTaskEventRUMAccount Account { get; } + + [Export ("ddtags", ArgumentSemantic.Copy)] + string Ddtags { get; } } // @interface DDRUMLongTaskEventAction : NSObject @@ -1982,6 +2095,9 @@ interface DDRUMLongTaskEventApplication // @property (readonly, copy, nonatomic) NSString * _Nonnull id; [Export ("id")] string Id { get; } + + [Export ("currentLocale", ArgumentSemantic.Copy)] + string CurrentLocale { get; } } // @interface DDRUMLongTaskEventContainer : NSObject @@ -2032,6 +2148,12 @@ interface DDRUMLongTaskEventDD // @property (readonly, nonatomic, strong) DDRUMLongTaskEventDDSession * _Nullable session; [NullAllowed, Export ("session", ArgumentSemantic.Strong)] DDRUMLongTaskEventDDSession Session { get; } + + [Export ("profiling", ArgumentSemantic.Strong)] + DDRUMLongTaskEventDDProfiling Profiling { get; } + + [Export ("sdkName", ArgumentSemantic.Copy)] + string SdkName { get; } } // @interface DDRUMLongTaskEventDDConfiguration : NSObject @@ -2046,6 +2168,9 @@ interface DDRUMLongTaskEventDDConfiguration // @property (readonly, nonatomic, strong) NSNumber * _Nonnull sessionSampleRate; [Export ("sessionSampleRate", ArgumentSemantic.Strong)] NSNumber SessionSampleRate { get; } + + [Export ("profilingSampleRate", ArgumentSemantic.Strong)] + NSNumber ProfilingSampleRate { get; } } // @interface DDRUMLongTaskEventDDSession : NSObject @@ -2102,6 +2227,9 @@ interface DDRUMLongTaskEventLongTask // @property (readonly, nonatomic, strong) NSNumber * _Nullable isFrozenFrame; [NullAllowed, Export ("isFrozenFrame", ArgumentSemantic.Strong)] NSNumber IsFrozenFrame { get; } + + [Export ("startTime", ArgumentSemantic.Strong)] + NSNumber StartTime { get; } } // @interface DDRUMLongTaskEventRUMCITest : NSObject @@ -2174,6 +2302,24 @@ interface DDRUMLongTaskEventRUMDevice // @property (readonly, nonatomic) enum DDRUMLongTaskEventRUMDeviceRUMDeviceType type; [Export ("type")] DDRUMLongTaskEventRUMDeviceRUMDeviceType Type { get; } + + [Export ("batteryLevel", ArgumentSemantic.Strong)] + NSNumber BatteryLevel { get; } + + [Export ("brightnessLevel", ArgumentSemantic.Strong)] + NSNumber BrightnessLevel { get; } + + [Export ("locale", ArgumentSemantic.Copy)] + string Locale { get; } + + [Export ("locales", ArgumentSemantic.Copy)] + string[] Locales { get; } + + [Export ("powerSavingMode", ArgumentSemantic.Strong)] + NSNumber PowerSavingMode { get; } + + [Export ("timeZone", ArgumentSemantic.Copy)] + string TimeZone { get; } } // @interface DDRUMLongTaskEventRUMEventAttributes : NSObject @@ -2246,6 +2392,9 @@ interface DDRUMLongTaskEventRUMUser // @property (readonly, copy, nonatomic) NSDictionary * _Nonnull usrInfo; [Export ("usrInfo", ArgumentSemantic.Copy)] NSDictionary UsrInfo { get; } + + [Export ("anonymousId", ArgumentSemantic.Copy)] + string AnonymousId { get; } } // @interface DDRUMLongTaskEventSession : NSObject @@ -2393,6 +2542,12 @@ interface DDRUMMonitor // @property (nonatomic) BOOL debug; [Export ("debug")] bool Debug { get; set; } + + [Export ("addAttributes:")] + void AddAttributes (NSDictionary attributes); + + [Export ("removeAttributesForKeys:")] + void RemoveAttributesForKeys (string[] keys); } // @interface DDRUMResourceEvent : NSObject @@ -2487,6 +2642,12 @@ interface DDRUMResourceEvent // @property (readonly, nonatomic, strong) DDRUMResourceEventView * _Nonnull view; [Export ("view", ArgumentSemantic.Strong)] DDRUMResourceEventView View { get; } + + [Export ("account", ArgumentSemantic.Strong)] + DDRUMResourceEventRUMAccount Account { get; } + + [Export ("ddtags", ArgumentSemantic.Copy)] + string Ddtags { get; } } // @interface DDRUMResourceEventAction : NSObject @@ -2521,6 +2682,9 @@ interface DDRUMResourceEventApplication // @property (readonly, copy, nonatomic) NSString * _Nonnull id; [Export ("id")] string Id { get; } + + [Export ("currentLocale", ArgumentSemantic.Copy)] + string CurrentLocale { get; } } // @interface DDRUMResourceEventContainer : NSObject @@ -2583,6 +2747,12 @@ interface DDRUMResourceEventDD // @property (readonly, copy, nonatomic) NSString * _Nullable traceId; [NullAllowed, Export ("traceId")] string TraceId { get; } + + [Export ("parentSpanId", ArgumentSemantic.Copy)] + string ParentSpanId { get; } + + [Export ("sdkName", ArgumentSemantic.Copy)] + string SdkName { get; } } // @interface DDRUMResourceEventDDConfiguration : NSObject @@ -2597,6 +2767,9 @@ interface DDRUMResourceEventDDConfiguration // @property (readonly, nonatomic, strong) NSNumber * _Nonnull sessionSampleRate; [Export ("sessionSampleRate", ArgumentSemantic.Strong)] NSNumber SessionSampleRate { get; } + + [Export ("profilingSampleRate", ArgumentSemantic.Strong)] + NSNumber ProfilingSampleRate { get; } } // @interface DDRUMResourceEventDDSession : NSObject @@ -2707,6 +2880,24 @@ interface DDRUMResourceEventRUMDevice // @property (readonly, nonatomic) enum DDRUMResourceEventRUMDeviceRUMDeviceType type; [Export ("type")] DDRUMResourceEventRUMDeviceRUMDeviceType Type { get; } + + [Export ("batteryLevel", ArgumentSemantic.Strong)] + NSNumber BatteryLevel { get; } + + [Export ("brightnessLevel", ArgumentSemantic.Strong)] + NSNumber BrightnessLevel { get; } + + [Export ("locale", ArgumentSemantic.Copy)] + string Locale { get; } + + [Export ("locales", ArgumentSemantic.Copy)] + string[] Locales { get; } + + [Export ("powerSavingMode", ArgumentSemantic.Strong)] + NSNumber PowerSavingMode { get; } + + [Export ("timeZone", ArgumentSemantic.Copy)] + string TimeZone { get; } } // @interface DDRUMResourceEventRUMEventAttributes : NSObject @@ -2779,6 +2970,9 @@ interface DDRUMResourceEventRUMUser // @property (readonly, copy, nonatomic) NSDictionary * _Nonnull usrInfo; [Export ("usrInfo", ArgumentSemantic.Copy)] NSDictionary UsrInfo { get; } + + [Export ("anonymousId", ArgumentSemantic.Copy)] + string AnonymousId { get; } } // @interface DDRUMResourceEventResource : NSObject @@ -2861,6 +3055,15 @@ interface DDRUMResourceEventResource // @property (copy, nonatomic) NSString * _Nonnull url; [Export ("url")] string Url { get; set; } + + [Export ("deliveryType")] + DDRUMResourceEventResourceDeliveryType DeliveryType { get; } + + [Export ("protocol", ArgumentSemantic.Copy)] + string Protocol { get; } + + [Export ("worker", ArgumentSemantic.Strong)] + DDRUMResourceEventResourceWorker Worker { get; } } // @interface DDRUMResourceEventResourceConnect : NSObject @@ -3151,6 +3354,12 @@ interface DDRUMViewEvent // @property (readonly, nonatomic, strong) DDRUMViewEventView * _Nonnull view; [Export ("view", ArgumentSemantic.Strong)] DDRUMViewEventView View { get; } + + [Export ("account", ArgumentSemantic.Strong)] + DDRUMViewEventRUMAccount Account { get; } + + [Export ("ddtags", ArgumentSemantic.Copy)] + string Ddtags { get; } } // @interface DDRUMViewEventApplication : NSObject @@ -3161,6 +3370,9 @@ interface DDRUMViewEventApplication // @property (readonly, copy, nonatomic) NSString * _Nonnull id; [Export ("id")] string Id { get; } + + [Export ("currentLocale", ArgumentSemantic.Copy)] + string CurrentLocale { get; } } // @interface DDRUMViewEventContainer : NSObject @@ -3219,6 +3431,15 @@ interface DDRUMViewEventDD // @property (readonly, nonatomic, strong) DDRUMViewEventDDSession * _Nullable session; [NullAllowed, Export ("session", ArgumentSemantic.Strong)] DDRUMViewEventDDSession Session { get; } + + [Export ("cls", ArgumentSemantic.Strong)] + DDRUMViewEventDDCLS Cls { get; } + + [Export ("profiling", ArgumentSemantic.Strong)] + DDRUMViewEventDDProfiling Profiling { get; } + + [Export ("sdkName", ArgumentSemantic.Copy)] + string SdkName { get; } } // @interface DDRUMViewEventDDConfiguration : NSObject @@ -3237,6 +3458,9 @@ interface DDRUMViewEventDDConfiguration // @property (readonly, nonatomic, strong) NSNumber * _Nullable startSessionReplayRecordingManually; [NullAllowed, Export ("startSessionReplayRecordingManually", ArgumentSemantic.Strong)] NSNumber StartSessionReplayRecordingManually { get; } + + [Export ("profilingSampleRate", ArgumentSemantic.Strong)] + NSNumber ProfilingSampleRate { get; } } // @interface DDRUMViewEventDDPageStates : NSObject @@ -3425,6 +3649,24 @@ interface DDRUMViewEventRUMDevice // @property (readonly, nonatomic) enum DDRUMViewEventRUMDeviceRUMDeviceType type; [Export ("type")] DDRUMViewEventRUMDeviceRUMDeviceType Type { get; } + + [Export ("batteryLevel", ArgumentSemantic.Strong)] + NSNumber BatteryLevel { get; } + + [Export ("brightnessLevel", ArgumentSemantic.Strong)] + NSNumber BrightnessLevel { get; } + + [Export ("locale", ArgumentSemantic.Copy)] + string Locale { get; } + + [Export ("locales", ArgumentSemantic.Copy)] + string[] Locales { get; } + + [Export ("powerSavingMode", ArgumentSemantic.Strong)] + NSNumber PowerSavingMode { get; } + + [Export ("timeZone", ArgumentSemantic.Copy)] + string TimeZone { get; } } // @interface DDRUMViewEventRUMEventAttributes : NSObject @@ -3497,6 +3739,9 @@ interface DDRUMViewEventRUMUser // @property (readonly, copy, nonatomic) NSDictionary * _Nonnull usrInfo; [Export ("usrInfo", ArgumentSemantic.Copy)] NSDictionary UsrInfo { get; } + + [Export ("anonymousId", ArgumentSemantic.Copy)] + string AnonymousId { get; } } // @interface DDRUMViewEventSession : NSObject @@ -3705,6 +3950,27 @@ interface DDRUMViewEventView // @property (copy, nonatomic) NSString * _Nonnull url; [Export ("url")] string Url { get; set; } + + [Export ("accessibility", ArgumentSemantic.Strong)] + DDRUMViewEventViewAccessibility Accessibility { get; } + + [Export ("freezeRate", ArgumentSemantic.Strong)] + NSNumber FreezeRate { get; } + + [Export ("interactionToNextViewTime", ArgumentSemantic.Strong)] + NSNumber InteractionToNextViewTime { get; } + + [Export ("networkSettledTime", ArgumentSemantic.Strong)] + NSNumber NetworkSettledTime { get; } + + [Export ("performance", ArgumentSemantic.Strong)] + DDRUMViewEventViewPerformance Performance { get; } + + [Export ("slowFrames", ArgumentSemantic.Copy)] + DDRUMViewEventViewSlowFrames[] SlowFrames { get; } + + [Export ("slowFramesRate", ArgumentSemantic.Strong)] + NSNumber SlowFramesRate { get; } } // @interface DDRUMViewEventViewAction : NSObject @@ -3945,6 +4211,12 @@ interface DDRUMVitalEvent // @property (readonly, nonatomic, strong) DDRUMVitalEventVital * _Nonnull vital; [Export ("vital", ArgumentSemantic.Strong)] DDRUMVitalEventVital Vital { get; } + + [Export ("account", ArgumentSemantic.Strong)] + DDRUMVitalEventRUMAccount Account { get; } + + [Export ("ddtags", ArgumentSemantic.Copy)] + string Ddtags { get; } } // @interface DDRUMVitalEventApplication : NSObject @@ -3955,6 +4227,9 @@ interface DDRUMVitalEventApplication // @property (readonly, copy, nonatomic) NSString * _Nonnull id; [Export ("id")] string Id { get; } + + [Export ("currentLocale", ArgumentSemantic.Copy)] + string CurrentLocale { get; } } // @interface DDRUMVitalEventContainer : NSObject @@ -4005,6 +4280,9 @@ interface DDRUMVitalEventDD // @property (readonly, nonatomic, strong) DDRUMVitalEventDDVital * _Nullable vital; [NullAllowed, Export ("vital", ArgumentSemantic.Strong)] DDRUMVitalEventDDVital Vital { get; } + + [Export ("sdkName", ArgumentSemantic.Copy)] + string SdkName { get; } } // @interface DDRUMVitalEventDDConfiguration : NSObject @@ -4019,6 +4297,9 @@ interface DDRUMVitalEventDDConfiguration // @property (readonly, nonatomic, strong) NSNumber * _Nonnull sessionSampleRate; [Export ("sessionSampleRate", ArgumentSemantic.Strong)] NSNumber SessionSampleRate { get; } + + [Export ("profilingSampleRate", ArgumentSemantic.Strong)] + NSNumber ProfilingSampleRate { get; } } // @interface DDRUMVitalEventDDSession : NSObject @@ -4139,6 +4420,24 @@ interface DDRUMVitalEventRUMDevice // @property (readonly, nonatomic) enum DDRUMVitalEventRUMDeviceRUMDeviceType type; [Export ("type")] DDRUMVitalEventRUMDeviceRUMDeviceType Type { get; } + + [Export ("batteryLevel", ArgumentSemantic.Strong)] + NSNumber BatteryLevel { get; } + + [Export ("brightnessLevel", ArgumentSemantic.Strong)] + NSNumber BrightnessLevel { get; } + + [Export ("locale", ArgumentSemantic.Copy)] + string Locale { get; } + + [Export ("locales", ArgumentSemantic.Copy)] + string[] Locales { get; } + + [Export ("powerSavingMode", ArgumentSemantic.Strong)] + NSNumber PowerSavingMode { get; } + + [Export ("timeZone", ArgumentSemantic.Copy)] + string TimeZone { get; } } // @interface DDRUMVitalEventRUMEventAttributes : NSObject @@ -4211,6 +4510,9 @@ interface DDRUMVitalEventRUMUser // @property (readonly, copy, nonatomic) NSDictionary * _Nonnull usrInfo; [Export ("usrInfo", ArgumentSemantic.Copy)] NSDictionary UsrInfo { get; } + + [Export ("anonymousId", ArgumentSemantic.Copy)] + string AnonymousId { get; } } // @interface DDRUMVitalEventSession : NSObject @@ -4273,6 +4575,18 @@ interface DDRUMVitalEventVital // @property (readonly, nonatomic) enum DDRUMVitalEventVitalVitalType type; [Export ("type")] DDRUMVitalEventVitalVitalType Type { get; } + + [Export ("vitalDescription", ArgumentSemantic.Copy)] + string VitalDescription { get; } + + [Export ("failureReason")] + DDRUMVitalEventVitalFailureReason FailureReason { get; } + + [Export ("parentId", ArgumentSemantic.Copy)] + string ParentId { get; } + + [Export ("stepType")] + DDRUMVitalEventVitalStepType StepType { get; } } // @protocol DDServerDateProvider @@ -4294,39 +4608,7 @@ interface DDServerDateProvider void SynchronizeWithUpdate (Action update); } - // @interface DDSessionReplay : NSObject - [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc15DDSessionReplay")] - [DisableDefaultCtor] - interface DDSessionReplay - { - // +(void)enableWith:(DDSessionReplayConfiguration * _Nonnull)configuration; - [Static] - [Export ("enableWith:")] - void EnableWith (DDSessionReplayConfiguration configuration); - } - - // @interface DDSessionReplayConfiguration : NSObject - [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc28DDSessionReplayConfiguration")] - [DisableDefaultCtor] - interface DDSessionReplayConfiguration - { - // @property (nonatomic) float replaySampleRate; - [Export ("replaySampleRate")] - float ReplaySampleRate { get; set; } - - // @property (nonatomic) enum DDSessionReplayConfigurationPrivacyLevel defaultPrivacyLevel; - [Export ("defaultPrivacyLevel", ArgumentSemantic.Assign)] - DDSessionReplayConfigurationPrivacyLevel DefaultPrivacyLevel { get; set; } - - // @property (copy, nonatomic) NSURL * _Nullable customEndpoint; - [NullAllowed, Export ("customEndpoint", ArgumentSemantic.Copy)] - NSUrl CustomEndpoint { get; set; } - // -(instancetype _Nonnull)initWithReplaySampleRate:(float)replaySampleRate __attribute__((objc_designated_initializer)); - [Export ("initWithReplaySampleRate:")] - [DesignatedInitializer] - NativeHandle Constructor (float replaySampleRate); - } // @interface DDSite : NSObject [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc6DDSite")] @@ -4362,6 +4644,10 @@ public interface DDSite [Static] [Export ("us1_fed")] DDSite Us1_fed { get; } + + [Static] + [Export ("ap2")] + DDSite Ap2 { get; } } // @interface DDTelemetryConfigurationEvent : NSObject @@ -4416,6 +4702,9 @@ interface DDTelemetryConfigurationEvent // @property (readonly, nonatomic, strong) DDTelemetryConfigurationEventView * _Nullable view; [NullAllowed, Export ("view", ArgumentSemantic.Strong)] DDTelemetryConfigurationEventView View { get; } + + [Export ("effectiveSampleRate", ArgumentSemantic.Strong)] + NSNumber EffectiveSampleRate { get; } } // @interface DDTelemetryConfigurationEventAction : NSObject @@ -4756,6 +5045,39 @@ interface DDTelemetryConfigurationEventTelemetryConfiguration // @property (readonly, nonatomic) enum DDTelemetryConfigurationEventTelemetryConfigurationViewTrackingStrategy viewTrackingStrategy; [Export ("viewTrackingStrategy")] DDTelemetryConfigurationEventTelemetryConfigurationViewTrackingStrategy ViewTrackingStrategy { get; } + + [Export ("invTimeThresholdMs", ArgumentSemantic.Strong)] + NSNumber InvTimeThresholdMs { get; } + + [Export ("isMainProcess", ArgumentSemantic.Strong)] + NSNumber IsMainProcess { get; } + + [Export ("numberOfDisplays", ArgumentSemantic.Strong)] + NSNumber NumberOfDisplays { get; } + + [Export ("sessionPersistence")] + DDTelemetryConfigurationEventTelemetryConfigurationSessionPersistence SessionPersistence { get; } + + [Export ("swiftuiActionTrackingEnabled", ArgumentSemantic.Strong)] + NSNumber SwiftuiActionTrackingEnabled { get; set; } + + [Export ("swiftuiViewTrackingEnabled", ArgumentSemantic.Strong)] + NSNumber SwiftuiViewTrackingEnabled { get; set; } + + [Export ("tnsTimeThresholdMs", ArgumentSemantic.Strong)] + NSNumber TnsTimeThresholdMs { get; } + + [Export ("trackAnonymousUser", ArgumentSemantic.Strong)] + NSNumber TrackAnonymousUser { get; set; } + + [Export ("trackBfcacheViews", ArgumentSemantic.Strong)] + NSNumber TrackBfcacheViews { get; set; } + + [Export ("trackFeatureFlagsForEvents", ArgumentSemantic.Copy)] + NSNumber[] TrackFeatureFlagsForEvents { get; } + + [Export ("useAllowedTrackingOrigins", ArgumentSemantic.Strong)] + NSNumber UseAllowedTrackingOrigins { get; set; } } // @interface DDTelemetryConfigurationEventTelemetryConfigurationForwardConsoleLogs : NSObject @@ -4898,6 +5220,9 @@ interface DDTelemetryDebugEvent // @property (readonly, nonatomic, strong) DDTelemetryDebugEventView * _Nullable view; [NullAllowed, Export ("view", ArgumentSemantic.Strong)] DDTelemetryDebugEventView View { get; } + + [Export ("effectiveSampleRate", ArgumentSemantic.Strong)] + NSNumber EffectiveSampleRate { get; } } // @interface DDTelemetryDebugEventAction : NSObject @@ -5068,6 +5393,9 @@ interface DDTelemetryErrorEvent // @property (readonly, nonatomic, strong) DDTelemetryErrorEventView * _Nullable view; [NullAllowed, Export ("view", ArgumentSemantic.Strong)] DDTelemetryErrorEventView View { get; } + + [Export ("effectiveSampleRate", ArgumentSemantic.Strong)] + NSNumber EffectiveSampleRate { get; } } // @interface DDTelemetryErrorEventAction : NSObject @@ -5671,6 +5999,9 @@ interface DDLogEventAttributes // @property (copy, nonatomic) NSDictionary * _Nonnull userAttributes; [Export ("userAttributes", ArgumentSemantic.Copy)] NSDictionary UserAttributes { get; set; } + + [Export ("accountInfo", ArgumentSemantic.Strong)] + DDLogEventAccountInfo AccountInfo { get; } } // @interface DDLogEventBinaryImage : NSObject @@ -5890,4 +6221,455 @@ interface DDLogEvent [NullAllowed, Export ("tags", ArgumentSemantic.Copy)] string[] Tags { get; set; } } + + // --------------------------------------------------------------------------------------- + // Types added between dd-sdk-ios 2.17.0 and 2.30.2. + // + // The SwiftUI predicates come from the SwiftUI auto-tracking added in 2.29.0, the account + // types from the account-info API in the same release, and the rest is RUM event-model + // detail reachable through the event mappers. + // --------------------------------------------------------------------------------------- + + // @interface DDDefaultSwiftUIRUMActionsPredicate + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc35DDDefaultSwiftUIRUMActionsPredicate")] + interface DDDefaultSwiftUIRUMActionsPredicate : DDSwiftUIRUMActionsPredicate + { + [Export ("initWithIsLegacyDetectionEnabled:")] + NativeHandle Constructor (bool isLegacyDetectionEnabled); + + [Export ("rumActionWith:")] + DDRUMAction RumActionWith (string componentName); + } + + // @interface DDDefaultSwiftUIRUMViewsPredicate + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc33DDDefaultSwiftUIRUMViewsPredicate")] + interface DDDefaultSwiftUIRUMViewsPredicate : DDSwiftUIRUMViewsPredicate + { + [Export ("rumViewFor:")] + DDRUMView RumViewFor (string extractedViewName); + } + + // @interface DDInternalLogger + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc16DDInternalLogger")] + interface DDInternalLogger + { + [Static] + [Export ("consolePrint:")] + void ConsolePrint (string message); + + [Static] + [Export ("telemetryDebugWithId:message:")] + void TelemetryDebugWithId (string id, string message); + + [Static] + [Export ("telemetryErrorWithId:message:kind:stack:")] + void TelemetryErrorWithId (string id, string message, [NullAllowed] string kind, [NullAllowed] string stack); + } + + // @interface DDLogEventAccountInfo + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc21DDLogEventAccountInfo")] + [DisableDefaultCtor] + interface DDLogEventAccountInfo + { + [Export ("id", ArgumentSemantic.Copy)] + string Id { get; } + + [Export ("name", ArgumentSemantic.Copy)] + string Name { get; } + + [Export ("extraInfo", ArgumentSemantic.Copy)] + NSDictionary ExtraInfo { get; set; } + } + + // @interface DDRUMActionEventRUMAccount + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc26DDRUMActionEventRUMAccount")] + [DisableDefaultCtor] + interface DDRUMActionEventRUMAccount + { + [Export ("id", ArgumentSemantic.Copy)] + string Id { get; } + + [Export ("name", ArgumentSemantic.Copy)] + string Name { get; } + + [Export ("accountInfo", ArgumentSemantic.Copy)] + NSDictionary AccountInfo { get; set; } + } + + // @interface DDRUMErrorEventRUMAccount + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc25DDRUMErrorEventRUMAccount")] + [DisableDefaultCtor] + interface DDRUMErrorEventRUMAccount + { + [Export ("id", ArgumentSemantic.Copy)] + string Id { get; } + + [Export ("name", ArgumentSemantic.Copy)] + string Name { get; } + + [Export ("accountInfo", ArgumentSemantic.Copy)] + NSDictionary AccountInfo { get; set; } + } + + // @interface DDRUMLongTaskEventDDProfiling + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc29DDRUMLongTaskEventDDProfiling")] + [DisableDefaultCtor] + interface DDRUMLongTaskEventDDProfiling + { + [Export ("errorReason")] + DDRUMLongTaskEventDDProfilingErrorReason ErrorReason { get; } + + [Export ("status")] + DDRUMLongTaskEventDDProfilingStatus Status { get; } + } + + // @interface DDRUMLongTaskEventRUMAccount + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc28DDRUMLongTaskEventRUMAccount")] + [DisableDefaultCtor] + interface DDRUMLongTaskEventRUMAccount + { + [Export ("id", ArgumentSemantic.Copy)] + string Id { get; } + + [Export ("name", ArgumentSemantic.Copy)] + string Name { get; } + + [Export ("accountInfo", ArgumentSemantic.Copy)] + NSDictionary AccountInfo { get; set; } + } + + // @interface DDRUMResourceEventRUMAccount + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc28DDRUMResourceEventRUMAccount")] + [DisableDefaultCtor] + interface DDRUMResourceEventRUMAccount + { + [Export ("id", ArgumentSemantic.Copy)] + string Id { get; } + + [Export ("name", ArgumentSemantic.Copy)] + string Name { get; } + + [Export ("accountInfo", ArgumentSemantic.Copy)] + NSDictionary AccountInfo { get; set; } + } + + // @interface DDRUMResourceEventResourceWorker + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc32DDRUMResourceEventResourceWorker")] + [DisableDefaultCtor] + interface DDRUMResourceEventResourceWorker + { + [Export ("duration", ArgumentSemantic.Strong)] + NSNumber Duration { get; } + + [Export ("start", ArgumentSemantic.Strong)] + NSNumber Start { get; } + } + + // @interface DDRUMViewEventDDCLS + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc19DDRUMViewEventDDCLS")] + [DisableDefaultCtor] + interface DDRUMViewEventDDCLS + { + [Export ("devicePixelRatio", ArgumentSemantic.Strong)] + NSNumber DevicePixelRatio { get; } + } + + // @interface DDRUMViewEventDDProfiling + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc25DDRUMViewEventDDProfiling")] + [DisableDefaultCtor] + interface DDRUMViewEventDDProfiling + { + [Export ("errorReason")] + DDRUMViewEventDDProfilingErrorReason ErrorReason { get; } + + [Export ("status")] + DDRUMViewEventDDProfilingStatus Status { get; } + } + + // @interface DDRUMViewEventRUMAccount + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc24DDRUMViewEventRUMAccount")] + [DisableDefaultCtor] + interface DDRUMViewEventRUMAccount + { + [Export ("id", ArgumentSemantic.Copy)] + string Id { get; } + + [Export ("name", ArgumentSemantic.Copy)] + string Name { get; } + + [Export ("accountInfo", ArgumentSemantic.Copy)] + NSDictionary AccountInfo { get; set; } + } + + // @interface DDRUMViewEventViewAccessibility + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc31DDRUMViewEventViewAccessibility")] + [DisableDefaultCtor] + interface DDRUMViewEventViewAccessibility + { + [Export ("assistiveSwitchEnabled", ArgumentSemantic.Strong)] + NSNumber AssistiveSwitchEnabled { get; } + + [Export ("assistiveTouchEnabled", ArgumentSemantic.Strong)] + NSNumber AssistiveTouchEnabled { get; } + + [Export ("boldTextEnabled", ArgumentSemantic.Strong)] + NSNumber BoldTextEnabled { get; } + + [Export ("buttonShapesEnabled", ArgumentSemantic.Strong)] + NSNumber ButtonShapesEnabled { get; } + + [Export ("closedCaptioningEnabled", ArgumentSemantic.Strong)] + NSNumber ClosedCaptioningEnabled { get; } + + [Export ("grayscaleEnabled", ArgumentSemantic.Strong)] + NSNumber GrayscaleEnabled { get; } + + [Export ("increaseContrastEnabled", ArgumentSemantic.Strong)] + NSNumber IncreaseContrastEnabled { get; } + + [Export ("invertColorsEnabled", ArgumentSemantic.Strong)] + NSNumber InvertColorsEnabled { get; } + + [Export ("monoAudioEnabled", ArgumentSemantic.Strong)] + NSNumber MonoAudioEnabled { get; } + + [Export ("onOffSwitchLabelsEnabled", ArgumentSemantic.Strong)] + NSNumber OnOffSwitchLabelsEnabled { get; } + + [Export ("reduceMotionEnabled", ArgumentSemantic.Strong)] + NSNumber ReduceMotionEnabled { get; } + + [Export ("reduceTransparencyEnabled", ArgumentSemantic.Strong)] + NSNumber ReduceTransparencyEnabled { get; } + + [Export ("reducedAnimationsEnabled", ArgumentSemantic.Strong)] + NSNumber ReducedAnimationsEnabled { get; } + + [Export ("screenReaderEnabled", ArgumentSemantic.Strong)] + NSNumber ScreenReaderEnabled { get; } + + [Export ("shakeToUndoEnabled", ArgumentSemantic.Strong)] + NSNumber ShakeToUndoEnabled { get; } + + [Export ("shouldDifferentiateWithoutColor", ArgumentSemantic.Strong)] + NSNumber ShouldDifferentiateWithoutColor { get; } + + [Export ("singleAppModeEnabled", ArgumentSemantic.Strong)] + NSNumber SingleAppModeEnabled { get; } + + [Export ("speakScreenEnabled", ArgumentSemantic.Strong)] + NSNumber SpeakScreenEnabled { get; } + + [Export ("speakSelectionEnabled", ArgumentSemantic.Strong)] + NSNumber SpeakSelectionEnabled { get; } + + [Export ("textSize", ArgumentSemantic.Copy)] + string TextSize { get; } + + [Export ("videoAutoplayEnabled", ArgumentSemantic.Strong)] + NSNumber VideoAutoplayEnabled { get; } + } + + // @interface DDRUMViewEventViewPerformance + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc29DDRUMViewEventViewPerformance")] + [DisableDefaultCtor] + interface DDRUMViewEventViewPerformance + { + [Export ("cls", ArgumentSemantic.Strong)] + DDRUMViewEventViewPerformanceCLS Cls { get; } + + [Export ("fbc", ArgumentSemantic.Strong)] + DDRUMViewEventViewPerformanceFBC Fbc { get; } + + [Export ("fcp", ArgumentSemantic.Strong)] + DDRUMViewEventViewPerformanceFCP Fcp { get; } + + [Export ("fid", ArgumentSemantic.Strong)] + DDRUMViewEventViewPerformanceFID Fid { get; } + + [Export ("inp", ArgumentSemantic.Strong)] + DDRUMViewEventViewPerformanceINP Inp { get; } + + [Export ("lcp", ArgumentSemantic.Strong)] + DDRUMViewEventViewPerformanceLCP Lcp { get; } + } + + // @interface DDRUMViewEventViewPerformanceCLS + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc32DDRUMViewEventViewPerformanceCLS")] + [DisableDefaultCtor] + interface DDRUMViewEventViewPerformanceCLS + { + [Export ("currentRect", ArgumentSemantic.Strong)] + DDRUMViewEventViewPerformanceCLSCurrentRect CurrentRect { get; } + + [Export ("previousRect", ArgumentSemantic.Strong)] + DDRUMViewEventViewPerformanceCLSPreviousRect PreviousRect { get; } + + [Export ("score", ArgumentSemantic.Strong)] + NSNumber Score { get; } + + [Export ("targetSelector", ArgumentSemantic.Copy)] + string TargetSelector { get; } + + [Export ("timestamp", ArgumentSemantic.Strong)] + NSNumber Timestamp { get; } + } + + // @interface DDRUMViewEventViewPerformanceCLSCurrentRect + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc43DDRUMViewEventViewPerformanceCLSCurrentRect")] + [DisableDefaultCtor] + interface DDRUMViewEventViewPerformanceCLSCurrentRect + { + [Export ("height", ArgumentSemantic.Strong)] + NSNumber Height { get; } + + [Export ("width", ArgumentSemantic.Strong)] + NSNumber Width { get; } + + [Export ("x", ArgumentSemantic.Strong)] + NSNumber X { get; } + + [Export ("y", ArgumentSemantic.Strong)] + NSNumber Y { get; } + } + + // @interface DDRUMViewEventViewPerformanceCLSPreviousRect + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc44DDRUMViewEventViewPerformanceCLSPreviousRect")] + [DisableDefaultCtor] + interface DDRUMViewEventViewPerformanceCLSPreviousRect + { + [Export ("height", ArgumentSemantic.Strong)] + NSNumber Height { get; } + + [Export ("width", ArgumentSemantic.Strong)] + NSNumber Width { get; } + + [Export ("x", ArgumentSemantic.Strong)] + NSNumber X { get; } + + [Export ("y", ArgumentSemantic.Strong)] + NSNumber Y { get; } + } + + // @interface DDRUMViewEventViewPerformanceFBC + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc32DDRUMViewEventViewPerformanceFBC")] + [DisableDefaultCtor] + interface DDRUMViewEventViewPerformanceFBC + { + [Export ("timestamp", ArgumentSemantic.Strong)] + NSNumber Timestamp { get; } + } + + // @interface DDRUMViewEventViewPerformanceFCP + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc32DDRUMViewEventViewPerformanceFCP")] + [DisableDefaultCtor] + interface DDRUMViewEventViewPerformanceFCP + { + [Export ("timestamp", ArgumentSemantic.Strong)] + NSNumber Timestamp { get; } + } + + // @interface DDRUMViewEventViewPerformanceFID + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc32DDRUMViewEventViewPerformanceFID")] + [DisableDefaultCtor] + interface DDRUMViewEventViewPerformanceFID + { + [Export ("duration", ArgumentSemantic.Strong)] + NSNumber Duration { get; } + + [Export ("targetSelector", ArgumentSemantic.Copy)] + string TargetSelector { get; } + + [Export ("timestamp", ArgumentSemantic.Strong)] + NSNumber Timestamp { get; } + } + + // @interface DDRUMViewEventViewPerformanceINP + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc32DDRUMViewEventViewPerformanceINP")] + [DisableDefaultCtor] + interface DDRUMViewEventViewPerformanceINP + { + [Export ("duration", ArgumentSemantic.Strong)] + NSNumber Duration { get; } + + [Export ("targetSelector", ArgumentSemantic.Copy)] + string TargetSelector { get; } + + [Export ("timestamp", ArgumentSemantic.Strong)] + NSNumber Timestamp { get; } + } + + // @interface DDRUMViewEventViewPerformanceLCP + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc32DDRUMViewEventViewPerformanceLCP")] + [DisableDefaultCtor] + interface DDRUMViewEventViewPerformanceLCP + { + [Export ("resourceUrl", ArgumentSemantic.Copy)] + string ResourceUrl { get; set; } + + [Export ("targetSelector", ArgumentSemantic.Copy)] + string TargetSelector { get; } + + [Export ("timestamp", ArgumentSemantic.Strong)] + NSNumber Timestamp { get; } + } + + // @interface DDRUMViewEventViewSlowFrames + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc28DDRUMViewEventViewSlowFrames")] + [DisableDefaultCtor] + interface DDRUMViewEventViewSlowFrames + { + [Export ("duration", ArgumentSemantic.Strong)] + NSNumber Duration { get; } + + [Export ("start", ArgumentSemantic.Strong)] + NSNumber Start { get; } + } + + // @interface DDRUMVitalEventRUMAccount + [BaseType (typeof(NSObject), Name = "_TtC11DatadogObjc25DDRUMVitalEventRUMAccount")] + [DisableDefaultCtor] + interface DDRUMVitalEventRUMAccount + { + [Export ("id", ArgumentSemantic.Copy)] + string Id { get; } + + [Export ("name", ArgumentSemantic.Copy)] + string Name { get; } + + [Export ("accountInfo", ArgumentSemantic.Copy)] + NSDictionary AccountInfo { get; set; } + } + + partial interface IDDSwiftUIRUMActionsPredicate {} + + // @protocol DDSwiftUIRUMActionsPredicate + // [Model] like the UIKit predicates, so an app can subclass the generated class instead of + // implementing the interface. + [Model, Protocol (Name = "_TtP11DatadogObjc28DDSwiftUIRUMActionsPredicate_")] + [BaseType (typeof(NSObject))] + interface DDSwiftUIRUMActionsPredicate + { + // @required -(DDRUMAction * _Nullable)rumActionWith:(NSString * _Nonnull)componentName; + [Abstract] + [Export ("rumActionWith:")] + [return: NullAllowed] + DDRUMAction RumActionWith (string componentName); + } + + partial interface IDDSwiftUIRUMViewsPredicate {} + + // @protocol DDSwiftUIRUMViewsPredicate + // [Model] like the UIKit predicates, so an app can subclass the generated class instead of + // implementing the interface. + [Model, Protocol (Name = "_TtP11DatadogObjc26DDSwiftUIRUMViewsPredicate_")] + [BaseType (typeof(NSObject))] + interface DDSwiftUIRUMViewsPredicate + { + // @required -(DDRUMView * _Nullable)rumViewFor:(NSString * _Nonnull)extractedViewName; + [Abstract] + [Export ("rumViewFor:")] + [return: NullAllowed] + DDRUMView RumViewFor (string extractedViewName); + } } diff --git a/src/DatadogNet.Objc.iOS/StructsAndEnums.cs b/src/DatadogNet.Objc.iOS/StructsAndEnums.cs index 53c5e9c..19c8674 100644 --- a/src/DatadogNet.Objc.iOS/StructsAndEnums.cs +++ b/src/DatadogNet.Objc.iOS/StructsAndEnums.cs @@ -938,13 +938,6 @@ public enum DDSDKVerbosityLevel : long Critical = 4 } - [Native] - public enum DDSessionReplayConfigurationPrivacyLevel : long - { - Allow = 0, - Mask = 1, - MaskUserInput = 2 - } [Native] public enum DDTelemetryConfigurationEventSource : long @@ -1089,4 +1082,116 @@ public enum DDLogEventInterface : long Loopback = 3, Other = 4 } + + // --------------------------------------------------------------------------------------- + // Added by dd-sdk-ios 2.30.2. Mostly RUM event-model detail reached through the event + // mappers; DDCoreLoggerLevel belongs to the internal-telemetry logger added in 2.19.0. + // --------------------------------------------------------------------------------------- + + [Native] + public enum DDCoreLoggerLevel : long + { + Debug = 0, + Warn = 1, + Error = 2, + Critical = 3 + } + + [Native] + public enum DDRUMActionEventDDActionNameSource : long + { + None = 0, + CustomAttribute = 1, + MaskPlaceholder = 2, + StandardAttribute = 3, + TextContent = 4, + MaskDisallowed = 5, + Blank = 6 + } + + [Native] + public enum DDRUMLongTaskEventDDProfilingErrorReason : long + { + None = 0, + NotSupportedByBrowser = 1, + FailedToLazyLoad = 2, + MissingDocumentPolicyHeader = 3, + UnexpectedException = 4 + } + + [Native] + public enum DDRUMLongTaskEventDDProfilingStatus : long + { + None = 0, + Starting = 1, + Running = 2, + Stopped = 3, + Error = 4 + } + + [Native] + public enum DDRUMResourceEventResourceDeliveryType : long + { + None = 0, + Cache = 1, + NavigationalPrefetch = 2, + Other = 3 + } + + [Native] + public enum DDRUMViewEventDDProfilingErrorReason : long + { + None = 0, + NotSupportedByBrowser = 1, + FailedToLazyLoad = 2, + MissingDocumentPolicyHeader = 3, + UnexpectedException = 4 + } + + [Native] + public enum DDRUMViewEventDDProfilingStatus : long + { + None = 0, + Starting = 1, + Running = 2, + Stopped = 3, + Error = 4 + } + + [Native] + public enum DDRUMVitalEventVitalFailureReason : long + { + None = 0, + Error = 1, + Abandoned = 2, + Other = 3 + } + + [Native] + public enum DDRUMVitalEventVitalStepType : long + { + None = 0, + Start = 1, + Update = 2, + Retry = 3, + End = 4 + } + + [Native] + public enum DDTelemetryConfigurationEventTelemetryConfigurationSessionPersistence : long + { + None = 0, + LocalStorage = 1, + Cookie = 2 + } + + [Native] + public enum DDTelemetryConfigurationEventTelemetryConfigurationTrackFeatureFlagsForEvents : long + { + None = 0, + Vital = 1, + Resource = 2, + Action = 3, + LongTask = 4 + } } diff --git a/src/DatadogNet.SessionReplay.iOS/ApiDefinitions.cs b/src/DatadogNet.SessionReplay.iOS/ApiDefinitions.cs index ba1ca6c..7960733 100644 --- a/src/DatadogNet.SessionReplay.iOS/ApiDefinitions.cs +++ b/src/DatadogNet.SessionReplay.iOS/ApiDefinitions.cs @@ -1,11 +1,27 @@ +using System; using DatadogSessionReplay; using Foundation; using ObjCRuntime; +using UIKit; namespace DatadogSessionReplay { + // NOTE ON RUNTIME NAMES + // + // Through 2.17.0 these classes were declared SWIFT_CLASS("_TtC20DatadogSessionReplay..."), + // which sets objc_runtime_name to the mangled Swift symbol, so the binding had to spell that + // mangled name out in [BaseType (Name = ...)]. + // + // From 2.19.0 they are declared SWIFT_CLASS_NAMED("objc_SessionReplay"), which expands to + // swift_name(...) only - a compile-time alias - and leaves the runtime name as the plain + // @interface name. The binary confirms it: it exports _OBJC_CLASS_$_DDSessionReplay, not + // _OBJC_CLASS_$__TtC20DatadogSessionReplay15DDSessionReplay. + // + // So Name= is deliberately absent here. Keeping the old mangled name would compile happily and + // then fail to resolve the class at runtime. + // @interface DDSessionReplay : NSObject - [BaseType (typeof(NSObject), Name = "_TtC20DatadogSessionReplay15DDSessionReplay")] + [BaseType (typeof(NSObject))] [DisableDefaultCtor] interface DDSessionReplay { @@ -13,10 +29,22 @@ interface DDSessionReplay [Static] [Export ("enableWith:")] void EnableWith (DDSessionReplayConfiguration configuration); + + // +(void)startRecording; + // Only needed when StartRecordingImmediately was set to false; recording otherwise begins + // as soon as the feature is enabled. + [Static] + [Export ("startRecording")] + void StartRecording (); + + // +(void)stopRecording; + [Static] + [Export ("stopRecording")] + void StopRecording (); } // @interface DDSessionReplayConfiguration : NSObject - [BaseType (typeof(NSObject), Name = "_TtC20DatadogSessionReplay28DDSessionReplayConfiguration")] + [BaseType (typeof(NSObject))] [DisableDefaultCtor] interface DDSessionReplayConfiguration { @@ -24,17 +52,97 @@ interface DDSessionReplayConfiguration [Export ("replaySampleRate")] float ReplaySampleRate { get; set; } - // @property (nonatomic) enum DDSessionReplayConfigurationPrivacyLevel defaultPrivacyLevel; - [Export ("defaultPrivacyLevel", ArgumentSemantic.Assign)] - DDSessionReplayConfigurationPrivacyLevel DefaultPrivacyLevel { get; set; } + // @property (nonatomic) enum DDTextAndInputPrivacyLevel textAndInputPrivacyLevel; + [Export ("textAndInputPrivacyLevel", ArgumentSemantic.Assign)] + DDTextAndInputPrivacyLevel TextAndInputPrivacyLevel { get; set; } + + // @property (nonatomic) enum DDImagePrivacyLevel imagePrivacyLevel; + [Export ("imagePrivacyLevel", ArgumentSemantic.Assign)] + DDImagePrivacyLevel ImagePrivacyLevel { get; set; } + + // @property (nonatomic) enum DDTouchPrivacyLevel touchPrivacyLevel; + [Export ("touchPrivacyLevel", ArgumentSemantic.Assign)] + DDTouchPrivacyLevel TouchPrivacyLevel { get; set; } + + // @property (nonatomic) BOOL startRecordingImmediately; + [Export ("startRecordingImmediately")] + bool StartRecordingImmediately { get; set; } // @property (copy, nonatomic) NSURL * _Nullable customEndpoint; [NullAllowed, Export ("customEndpoint", ArgumentSemantic.Copy)] NSUrl CustomEndpoint { get; set; } - // -(instancetype _Nonnull)initWithReplaySampleRate:(float)replaySampleRate __attribute__((objc_designated_initializer)); - [Export ("initWithReplaySampleRate:")] + // @property (copy, nonatomic) NSDictionary * _Nonnull featureFlags; + [Export ("featureFlags", ArgumentSemantic.Copy)] + NSDictionary FeatureFlags { get; set; } + + // @property (nonatomic) enum DDSessionReplayConfigurationPrivacyLevel defaultPrivacyLevel + // SWIFT_DEPRECATED_MSG("... Use the new privacy levels instead."); + [Export ("defaultPrivacyLevel", ArgumentSemantic.Assign)] + DDSessionReplayConfigurationPrivacyLevel DefaultPrivacyLevel { get; set; } + + // -(instancetype)initWithReplaySampleRate:(float)replaySampleRate + // textAndInputPrivacyLevel:(enum DDTextAndInputPrivacyLevel)textAndInputPrivacyLevel + // imagePrivacyLevel:(enum DDImagePrivacyLevel)imagePrivacyLevel + // touchPrivacyLevel:(enum DDTouchPrivacyLevel)touchPrivacyLevel; + [Export ("initWithReplaySampleRate:textAndInputPrivacyLevel:imagePrivacyLevel:touchPrivacyLevel:")] [DesignatedInitializer] + NativeHandle Constructor (float replaySampleRate, DDTextAndInputPrivacyLevel textAndInputPrivacyLevel, DDImagePrivacyLevel imagePrivacyLevel, DDTouchPrivacyLevel touchPrivacyLevel); + + // ... featureFlags:(NSDictionary * _Nullable)featureFlags; + [Export ("initWithReplaySampleRate:textAndInputPrivacyLevel:imagePrivacyLevel:touchPrivacyLevel:featureFlags:")] + NativeHandle Constructor (float replaySampleRate, DDTextAndInputPrivacyLevel textAndInputPrivacyLevel, DDImagePrivacyLevel imagePrivacyLevel, DDTouchPrivacyLevel touchPrivacyLevel, [NullAllowed] NSDictionary featureFlags); + + // -(instancetype)initWithReplaySampleRate:(float)replaySampleRate + // SWIFT_DEPRECATED_MSG("... Use init(replaySampleRate:textAndInputPrivacyLevel:...) instead."); + // + // Still bound so code written against 2.17.0 keeps compiling. It applies the SDK's own + // defaults for the three fine-grained levels. + [Export ("initWithReplaySampleRate:")] NativeHandle Constructor (float replaySampleRate); } + + // @interface DDSessionReplayPrivacyOverrides : NSObject + // + // Per-view privacy, overriding the session-wide levels. Reached through the UIView category + // below rather than constructed directly in normal use. + [BaseType (typeof(NSObject))] + [DisableDefaultCtor] + interface DDSessionReplayPrivacyOverrides + { + // -(instancetype _Nonnull)initWithView:(UIView * _Nonnull)view; + [Export ("initWithView:")] + [DesignatedInitializer] + NativeHandle Constructor (UIView view); + + // @property (nonatomic) enum DDTextAndInputPrivacyLevelOverride textAndInputPrivacy; + [Export ("textAndInputPrivacy", ArgumentSemantic.Assign)] + DDTextAndInputPrivacyLevelOverride TextAndInputPrivacy { get; set; } + + // @property (nonatomic) enum DDImagePrivacyLevelOverride imagePrivacy; + [Export ("imagePrivacy", ArgumentSemantic.Assign)] + DDImagePrivacyLevelOverride ImagePrivacy { get; set; } + + // @property (nonatomic) enum DDTouchPrivacyLevelOverride touchPrivacy; + [Export ("touchPrivacy", ArgumentSemantic.Assign)] + DDTouchPrivacyLevelOverride TouchPrivacy { get; set; } + + // @property (strong, nonatomic) NSNumber * _Nullable hide; + // NSNumber rather than bool: nil means "inherit", which a bool cannot express. + [NullAllowed, Export ("hide", ArgumentSemantic.Strong)] + NSNumber Hide { get; set; } + } + + // @interface UIView (SWIFT_EXTENSION(DatadogSessionReplay)) + // + // A category on UIView, so it is bound as a Category rather than as a type of its own. This is + // how per-view privacy is set: view.DdSessionReplayPrivacyOverrides().ImagePrivacy = ... + [Category] + [BaseType (typeof(UIView))] + interface UIView_DatadogSessionReplay + { + // @property (readonly, nonatomic, strong) DDSessionReplayPrivacyOverrides * _Nonnull ddSessionReplayPrivacyOverrides; + [Export ("ddSessionReplayPrivacyOverrides")] + DDSessionReplayPrivacyOverrides GetDdSessionReplayPrivacyOverrides (); + } } diff --git a/src/DatadogNet.SessionReplay.iOS/StructsAndEnums.cs b/src/DatadogNet.SessionReplay.iOS/StructsAndEnums.cs index 2fffa02..b3dae5f 100644 --- a/src/DatadogNet.SessionReplay.iOS/StructsAndEnums.cs +++ b/src/DatadogNet.SessionReplay.iOS/StructsAndEnums.cs @@ -1,7 +1,28 @@ using ObjCRuntime; + namespace DatadogSessionReplay { + /// Session-wide masking for images. + [Native] + public enum DDImagePrivacyLevel : long + { + MaskNonBundledOnly = 0, + MaskAll = 1, + MaskNone = 2 + } + + /// Per-view override of . None inherits the session-wide level. + [Native] + public enum DDImagePrivacyLevelOverride : long + { + None = 0, + MaskNone = 1, + MaskNonBundledOnly = 2, + MaskAll = 3 + } + + /// The single session-wide privacy level used before 2.19.0. Deprecated upstream in favour of the three fine-grained levels; still accepted for now. [Native] public enum DDSessionReplayConfigurationPrivacyLevel : long { @@ -9,4 +30,41 @@ public enum DDSessionReplayConfigurationPrivacyLevel : long Mask = 1, MaskUserInput = 2 } + + /// Session-wide masking for text and user input. + [Native] + public enum DDTextAndInputPrivacyLevel : long + { + MaskSensitiveInputs = 0, + MaskAllInputs = 1, + MaskAll = 2 + } + + /// Per-view override of . None inherits the session-wide level. + [Native] + public enum DDTextAndInputPrivacyLevelOverride : long + { + None = 0, + MaskSensitiveInputs = 1, + MaskAllInputs = 2, + MaskAll = 3 + } + + /// Session-wide masking for touch interactions. + [Native] + public enum DDTouchPrivacyLevel : long + { + Show = 0, + Hide = 1 + } + + /// Per-view override of . None inherits the session-wide level. + [Native] + public enum DDTouchPrivacyLevelOverride : long + { + None = 0, + Show = 1, + Hide = 2 + } + } diff --git a/tests/DatadogNet.iOS.DeviceTests/SmokeTests.cs b/tests/DatadogNet.iOS.DeviceTests/SmokeTests.cs index fd406ba..dd8affe 100644 --- a/tests/DatadogNet.iOS.DeviceTests/SmokeTests.cs +++ b/tests/DatadogNet.iOS.DeviceTests/SmokeTests.cs @@ -3,16 +3,14 @@ using DatadogCrashReporting; using DatadogInternal; using DatadogObjc; -// DatadogObjc declares its own DDSessionReplay, DDSessionReplayConfiguration and privacy-level -// enum, wrapping different native classes from the identically named ones here -// (_TtC11DatadogObjc15DDSessionReplay against _TtC20DatadogSessionReplay15DDSessionReplay). Both -// work; importing both namespaces unqualified is what does not. These tests deliberately drive the -// DatadogSessionReplay ones, so that package's own binding is exercised rather than only the -// façade's - the sample shows the DatadogObjc route an app would normally take. -using SessionReplay = DatadogSessionReplay; +// Session Replay lives only in DatadogSessionReplay from dd-sdk-ios 2.19.0 onwards; DatadogObjc +// used to declare a parallel set of DD* Session Replay types and no longer does, so there is +// nothing left to disambiguate. +using DatadogSessionReplay; using DatadogWebViewTracking; using Foundation; using ObjCRuntime; +using UIKit; namespace DatadogNet.iOS.DeviceTests; @@ -60,6 +58,9 @@ public static class SmokeTests new("enables Logs and writes every level", EnablesLogsAndWritesEveryLevel), new("enables Trace", EnablesTrace), new("enables Session Replay", EnablesSessionReplay), + new("applies per-view Session Replay privacy overrides", SessionReplayPrivacyOverridesApply), + new("sets and clears account and user info", SetsAccountInfo), + new("exposes the new RUM attribute APIs and the AP2 site", NewRumAndSiteApis), new("enables crash reporting", EnablesCrashReporting), new("constructs a URLSession delegate for first-party tracing", ConstructsUrlSessionDelegate), new("exposes WebView tracking", ExposesWebViewTracking), @@ -476,15 +477,89 @@ private static void EnablesTrace() private static void EnablesSessionReplay() { - var configuration = new SessionReplay.DDSessionReplayConfiguration(replaySampleRate: 100) + + // The three fine-grained levels replaced the single defaultPrivacyLevel in 2.19.0, and the + // initializer requires them, so the choice cannot be left implicit. + var configuration = new DDSessionReplayConfiguration( + replaySampleRate: 100, + textAndInputPrivacyLevel: DDTextAndInputPrivacyLevel.MaskAll, + imagePrivacyLevel: DDImagePrivacyLevel.MaskAll, + touchPrivacyLevel: DDTouchPrivacyLevel.Hide) { - DefaultPrivacyLevel = SessionReplay.DDSessionReplayConfigurationPrivacyLevel.Mask, CustomEndpoint = LocalEndpoint, + // Recording is started explicitly below rather than on enable, which is what an app + // does when it wants replay only after consent or a particular screen. + StartRecordingImmediately = false, }; - SessionReplay.DDSessionReplay.EnableWith(configuration); + DDSessionReplay.EnableWith(configuration); + + Assert( + configuration.TextAndInputPrivacyLevel == DDTextAndInputPrivacyLevel.MaskAll, + "textAndInputPrivacyLevel did not round-trip."); + Assert( + configuration.TouchPrivacyLevel == DDTouchPrivacyLevel.Hide, + "touchPrivacyLevel did not round-trip."); + + DDSessionReplay.StartRecording(); + DDSessionReplay.StopRecording(); + + Report("Session Replay enabled with fine-grained privacy, then started and stopped"); + } + + /// + /// Per-view privacy overrides, added in 2.19.0 as a category on UIView. + /// + private static void SessionReplayPrivacyOverridesApply() + { + var view = new UIView(); + var overrides = view.GetDdSessionReplayPrivacyOverrides(); + + Assert(overrides is not null, "UIView returned no privacy overrides object."); + + overrides.TextAndInputPrivacy = DDTextAndInputPrivacyLevelOverride.MaskAll; + overrides.ImagePrivacy = DDImagePrivacyLevelOverride.MaskAll; + overrides.TouchPrivacy = DDTouchPrivacyLevelOverride.Hide; + overrides.Hide = new NSNumber(true); + + // Read back through a fresh accessor call: the override object is attached to the view, so + // a value set through one handle must be visible through another. + var again = view.GetDdSessionReplayPrivacyOverrides(); + Assert( + again.TextAndInputPrivacy == DDTextAndInputPrivacyLevelOverride.MaskAll, + $"Override did not stick: {again.TextAndInputPrivacy}"); + Assert(again.Hide?.BoolValue == true, "Hide override did not stick."); + + Report("per-view privacy overrides set and read back"); + } + + /// Account info and the user-info clearing APIs, added in 2.29.0 and 2.30.0. + private static void SetsAccountInfo() + { + DDDatadog.SetAccountInfoWithAccountId("acct-1", "Test Account", DatadogAttributes.Empty); + DDDatadog.AddAccountExtraInfo( + new NSDictionary(new NSString("tier"), new NSString("premium"))); + DDDatadog.ClearAccountInfo(); + + // setUserInfo requires an id from 2.24.0, and clearUserInfo arrived in 2.30.0. + DDDatadog.SetUserInfoWithUserId("user-2", "User Two", "user2@example.invalid", DatadogAttributes.Empty); + DDDatadog.ClearUserInfo(); + + Report("account info set, extended and cleared; user info cleared"); + } + + /// Attribute APIs added to the RUM monitor in 2.23.0, and the AP2 site added in 2.29.0. + private static void NewRumAndSiteApis() + { + var monitor = DDRUMMonitor.Shared; + + monitor.AddAttributes( + new NSDictionary(new NSString("build.channel"), new NSString("e2e"))); + monitor.RemoveAttributesForKeys(["build.channel"]); + + Assert(DDSite.Ap2 is not null, "DDSite.Ap2 is missing."); - Report($"Session Replay enabled at privacy level {configuration.DefaultPrivacyLevel}"); + Report("RUM addAttributes/removeAttributes and DDSite.Ap2 available"); } private static void EnablesCrashReporting()