From 7cd13d4944bbb94e69814596da5644596b91b64b Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Wed, 24 Nov 2021 14:36:36 +0100 Subject: [PATCH 01/33] RUMM-1649 Webview Tracking feature branch init commit --- .../DebugWebviewViewController.swift | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/Datadog/Example/Debugging/DebugWebviewViewController.swift b/Datadog/Example/Debugging/DebugWebviewViewController.swift index 220160726b..eb5803908f 100644 --- a/Datadog/Example/Debugging/DebugWebviewViewController.swift +++ b/Datadog/Example/Debugging/DebugWebviewViewController.swift @@ -47,7 +47,7 @@ class DebugWebviewViewController: UIViewController { private var webviewURL: String { guard let text = webviewURLTextField.text, !text.isEmpty else { - return "https://www.datadoghq.com" + return "https://datadoghq.dev/browser-sdk-test-playground/webview.html" } return text } @@ -81,11 +81,30 @@ class WebviewViewController: UIViewController { private var webView: WKWebView! + class MessageHandler: NSObject, WKScriptMessageHandler { + func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + print(message.name + String(describing: message.body)) + } + } + override func viewDidLoad() { super.viewDidLoad() - let configuration = WKWebViewConfiguration() - webView = WKWebView(frame: UIScreen.main.bounds, configuration: configuration) + // WKScriptMessageHandler can be called only with `window.webkit.messageHandlers.{Handler name}` syntax + // the code below translates `window.DatadogEventBridge` into that format + let js = """ + window.DatadogEventBridge = { send(msg) { window.webkit.messageHandlers.DatadogEventBridge.postMessage(msg) } } + """ + let script = WKUserScript(source: js, injectionTime: .atDocumentStart, forMainFrameOnly: false) + + let controller = WKUserContentController() + controller.addUserScript(script) + controller.add(MessageHandler(), name: "DatadogEventBridge") + + let config = WKWebViewConfiguration() + config.userContentController = controller + + webView = WKWebView(frame: UIScreen.main.bounds, configuration: config) view.addSubview(webView) } From 43a0a812d62aa62fd5d35b852b4ffbc1c3885f23 Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Fri, 26 Nov 2021 13:21:10 +0100 Subject: [PATCH 02/33] RUMM-1785 DatadogEventBridge implemented --- Datadog/Datadog.xcodeproj/project.pbxproj | 24 +++++++ .../DebugWebviewViewController.swift | 22 ++---- .../RUM/WebView/DatadogEventBridge.swift | 66 +++++++++++++++++ .../WKUserContentController+Datadog.swift | 71 +++++++++++++++++++ .../RUM/WebView/WebLogEventConsumer.swift | 13 ++++ .../RUM/WebView/WebRUMEventConsumer.swift | 13 ++++ 6 files changed, 192 insertions(+), 17 deletions(-) create mode 100644 Sources/Datadog/RUM/WebView/DatadogEventBridge.swift create mode 100644 Sources/Datadog/RUM/WebView/WKUserContentController+Datadog.swift create mode 100644 Sources/Datadog/RUM/WebView/WebLogEventConsumer.swift create mode 100644 Sources/Datadog/RUM/WebView/WebRUMEventConsumer.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index a92f2bb363..6499c77d0e 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -501,6 +501,10 @@ 9E989A4225F640D100235FC3 /* AppStateListenerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E989A4125F640D100235FC3 /* AppStateListenerTests.swift */; }; 9E9973F1268DF69500D8059B /* VitalInfoSampler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9973F0268DF69500D8059B /* VitalInfoSampler.swift */; }; 9EA3CA6926775A3500B16871 /* VitalRefreshRateReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA3CA6826775A3500B16871 /* VitalRefreshRateReader.swift */; }; + 9EB4B862274E79D50041CD03 /* WKUserContentController+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB4B861274E79D50041CD03 /* WKUserContentController+Datadog.swift */; }; + 9EB4B864274FAB410041CD03 /* WebLogEventConsumer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB4B863274FAB410041CD03 /* WebLogEventConsumer.swift */; }; + 9EB4B866274FAB4C0041CD03 /* WebRUMEventConsumer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB4B865274FAB4C0041CD03 /* WebRUMEventConsumer.swift */; }; + 9EB4B868275103E40041CD03 /* DatadogEventBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB4B867275103E40041CD03 /* DatadogEventBridge.swift */; }; 9EC2835A26CFEE0B00FACF1C /* RUMMobileVitalsScenarioTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EC2835926CFEE0B00FACF1C /* RUMMobileVitalsScenarioTests.swift */; }; 9EC2835E26CFF57A00FACF1C /* RUMMobileVitalsScenario.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9EC2835D26CFF57A00FACF1C /* RUMMobileVitalsScenario.storyboard */; }; 9EC2836026CFF59400FACF1C /* RUMMobileVitalsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EC2835F26CFF59400FACF1C /* RUMMobileVitalsViewController.swift */; }; @@ -1158,6 +1162,10 @@ 9E9973F0268DF69500D8059B /* VitalInfoSampler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VitalInfoSampler.swift; sourceTree = ""; }; 9E9EB37624468CE90002C80B /* Datadog.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = Datadog.modulemap; sourceTree = ""; }; 9EA3CA6826775A3500B16871 /* VitalRefreshRateReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VitalRefreshRateReader.swift; sourceTree = ""; }; + 9EB4B861274E79D50041CD03 /* WKUserContentController+Datadog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WKUserContentController+Datadog.swift"; sourceTree = ""; }; + 9EB4B863274FAB410041CD03 /* WebLogEventConsumer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebLogEventConsumer.swift; sourceTree = ""; }; + 9EB4B865274FAB4C0041CD03 /* WebRUMEventConsumer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRUMEventConsumer.swift; sourceTree = ""; }; + 9EB4B867275103E40041CD03 /* DatadogEventBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatadogEventBridge.swift; sourceTree = ""; }; 9EC2835926CFEE0B00FACF1C /* RUMMobileVitalsScenarioTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMMobileVitalsScenarioTests.swift; sourceTree = ""; }; 9EC2835D26CFF57A00FACF1C /* RUMMobileVitalsScenario.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = RUMMobileVitalsScenario.storyboard; sourceTree = ""; }; 9EC2835F26CFF59400FACF1C /* RUMMobileVitalsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMMobileVitalsViewController.swift; sourceTree = ""; }; @@ -2934,6 +2942,7 @@ 616CCE11250A181C009FED46 /* AutoInstrumentation */, 618DCFD524C7264100589570 /* UUIDs */, 61B22E5824F3E6A700DC26D2 /* Debugging */, + 9EB4B860274E79620041CD03 /* WebView */, ); path = RUM; sourceTree = ""; @@ -3259,6 +3268,17 @@ path = ../Sources/_Datadog_Private; sourceTree = ""; }; + 9EB4B860274E79620041CD03 /* WebView */ = { + isa = PBXGroup; + children = ( + 9EB4B861274E79D50041CD03 /* WKUserContentController+Datadog.swift */, + 9EB4B867275103E40041CD03 /* DatadogEventBridge.swift */, + 9EB4B863274FAB410041CD03 /* WebLogEventConsumer.swift */, + 9EB4B865274FAB4C0041CD03 /* WebRUMEventConsumer.swift */, + ); + path = WebView; + sourceTree = ""; + }; 9EC2835C26CFF56B00FACF1C /* MobileVitals */ = { isa = PBXGroup; children = ( @@ -3877,9 +3897,11 @@ 61E909F124A24DD3005EA2DE /* OTReference.swift in Sources */, 6116563B25D2A6C90070EC03 /* ArbitraryDataWriter.swift in Sources */, 61216276247D1CD700AC5D67 /* LoggingForTracingAdapter.swift in Sources */, + 9EB4B866274FAB4C0041CD03 /* WebRUMEventConsumer.swift in Sources */, 61E909EF24A24DD3005EA2DE /* Global.swift in Sources */, 61133BDD2423979B00786299 /* InternalLoggers.swift in Sources */, 6105D1A02508F1600040DD22 /* LoggingWithActiveSpanIntegration.swift in Sources */, + 9EB4B864274FAB410041CD03 /* WebLogEventConsumer.swift in Sources */, 61EF78B1257E2E7A00EDCCB3 /* MoveDataMigrator.swift in Sources */, 61133BDC2423979B00786299 /* Logger.swift in Sources */, 6114FE1625766B310084E372 /* TrackingConsent.swift in Sources */, @@ -3959,11 +3981,13 @@ 613E793B2577B6EE00DFCC17 /* DataReader.swift in Sources */, 614B0A5324EBFE5500A2A780 /* DDRUMMonitor.swift in Sources */, 61133BE32423979B00786299 /* UserInfoProvider.swift in Sources */, + 9EB4B868275103E40041CD03 /* DatadogEventBridge.swift in Sources */, 61133BE02423979B00786299 /* Datadog.swift in Sources */, 61133BCB2423979B00786299 /* CarrierInfoProvider.swift in Sources */, 61C5A89024509AA700DA608C /* TracingFeature.swift in Sources */, 61E5333624B84B43003D6C4E /* RUMMonitor.swift in Sources */, 6156CB9824DEFD44008CB2B2 /* LoggingWithRUMIntegration.swift in Sources */, + 9EB4B862274E79D50041CD03 /* WKUserContentController+Datadog.swift in Sources */, 61133BD62423979B00786299 /* DataUploader.swift in Sources */, 619E16F12578E89700B2516B /* DeleteAllDataMigrator.swift in Sources */, 61494CBA24CB126F0082C633 /* RUMUserActionScope.swift in Sources */, diff --git a/Datadog/Example/Debugging/DebugWebviewViewController.swift b/Datadog/Example/Debugging/DebugWebviewViewController.swift index eb5803908f..cdcfcf239e 100644 --- a/Datadog/Example/Debugging/DebugWebviewViewController.swift +++ b/Datadog/Example/Debugging/DebugWebviewViewController.swift @@ -5,9 +5,11 @@ */ import UIKit -import Datadog import WebKit +// TODO: RUMM-1794 remove @testable and use public API +@testable import Datadog + class DebugWebviewViewController: UIViewController { @IBOutlet weak var rumServiceNameTextField: UITextField! @@ -81,26 +83,12 @@ class WebviewViewController: UIViewController { private var webView: WKWebView! - class MessageHandler: NSObject, WKScriptMessageHandler { - func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { - print(message.name + String(describing: message.body)) - } - } - override func viewDidLoad() { super.viewDidLoad() - // WKScriptMessageHandler can be called only with `window.webkit.messageHandlers.{Handler name}` syntax - // the code below translates `window.DatadogEventBridge` into that format - let js = """ - window.DatadogEventBridge = { send(msg) { window.webkit.messageHandlers.DatadogEventBridge.postMessage(msg) } } - """ - let script = WKUserScript(source: js, injectionTime: .atDocumentStart, forMainFrameOnly: false) - let controller = WKUserContentController() - controller.addUserScript(script) - controller.add(MessageHandler(), name: "DatadogEventBridge") - + // TODO: RUMM-1794 remove internal method call and use public API + controller.addDatadogMessageHandler(allowedWebViewHosts: ["datadoghq.dev"]) let config = WKWebViewConfiguration() config.userContentController = controller diff --git a/Sources/Datadog/RUM/WebView/DatadogEventBridge.swift b/Sources/Datadog/RUM/WebView/DatadogEventBridge.swift new file mode 100644 index 0000000000..44fa36c018 --- /dev/null +++ b/Sources/Datadog/RUM/WebView/DatadogEventBridge.swift @@ -0,0 +1,66 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2020 Datadog, Inc. + */ + +import Foundation + +internal typealias JSON = [String: Any] + +internal protocol WebEventConsumer { + func consume(event: JSON, eventType: String) +} + +internal enum WebEventError: Error { + case dataSerialization(message: String) + case JSONSerialization(rawJSON: Any) + case invalidMessage(message: Any) + case missingKey(key: String, json: JSON) +} + +internal class DatadogEventBridge { + struct Constants { + static let eventTypeKey = "eventType" + static let eventKey = "event" + static let eventTypeLog = "log" + } + + private let logEventConsumer: WebEventConsumer + private let rumEventConsumer: WebEventConsumer + + init(logEventConsumer: WebEventConsumer, rumEventConsumer: WebEventConsumer) { + self.logEventConsumer = logEventConsumer + self.rumEventConsumer = rumEventConsumer + } + + func consume(_ message: Any) throws { + guard let message = message as? String else { + throw WebEventError.invalidMessage(message: message) + } + let eventJSON = try Self.parse(message) + guard let eventType = eventJSON[Constants.eventTypeKey] as? String else { + throw WebEventError.missingKey(key: Constants.eventTypeKey, json: eventJSON) + } + guard let wrappedEvent = eventJSON[Constants.eventKey] as? JSON else { + throw WebEventError.missingKey(key: Constants.eventKey, json: eventJSON) + } + + if eventType == Constants.eventTypeLog { + logEventConsumer.consume(event: wrappedEvent, eventType: eventType) + } else { + rumEventConsumer.consume(event: wrappedEvent, eventType: eventType) + } + } + + static func parse(_ message: String) throws -> JSON { + guard let data = message.data(using: .utf8) else { + throw WebEventError.dataSerialization(message: message) + } + let rawJSON = try JSONSerialization.jsonObject(with: data, options: []) + guard let json = rawJSON as? JSON else { + throw WebEventError.JSONSerialization(rawJSON: rawJSON) + } + return json + } +} diff --git a/Sources/Datadog/RUM/WebView/WKUserContentController+Datadog.swift b/Sources/Datadog/RUM/WebView/WKUserContentController+Datadog.swift new file mode 100644 index 0000000000..c672f9fad4 --- /dev/null +++ b/Sources/Datadog/RUM/WebView/WKUserContentController+Datadog.swift @@ -0,0 +1,71 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2020 Datadog, Inc. + */ + +import Foundation +import WebKit + +internal extension WKUserContentController { + // TODO: RUMM-1794 rename the method + func addDatadogMessageHandler(allowedWebViewHosts: [String]) { + let bridgeName = DatadogMessageHandler.name + + let messageHandler = DatadogMessageHandler( + eventBridge: DatadogEventBridge( + logEventConsumer: WebLogEventConsumer(), + rumEventConsumer: WebRUMEventConsumer() + ) + ) + add(messageHandler, name: bridgeName) + + // WebKit installs message handlers with the given name format below + // We inject a user script to forward `window.{bridgeName}` to WebKit's format + let webkitMethodName = "window.webkit.messageHandlers.\(bridgeName).postMessage" + // `WKScriptMessageHandlerWithReply` returns `Promise` and `browser-sdk` expects immediate values. + // We inject a user script to return `allowedWebViewHosts` instead of using `WKScriptMessageHandlerWithReply` + let allowedWebViewHostsString = allowedWebViewHosts + .map { return "\"\($0)\"" } + .joined(separator: ",") + + let js = """ + window.\(bridgeName) = { + send(msg) { + \(webkitMethodName)(msg) + }, + getAllowedWebViewHosts() { + return '[\(allowedWebViewHostsString)]' + } + } + """ + addUserScript( + WKUserScript( + source: js, + injectionTime: .atDocumentStart, + forMainFrameOnly: false + ) + ) + } +} + +private class DatadogMessageHandler: NSObject, WKScriptMessageHandler { + static let name = "DatadogEventBridge" + private let eventBridge: DatadogEventBridge + + init(eventBridge: DatadogEventBridge) { + self.eventBridge = eventBridge + } + + func userContentController( + _ userContentController: WKUserContentController, + didReceive message: WKScriptMessage + ) { + print(message.name + String(describing: message.body)) + do { + try eventBridge.consume(message.body) + } catch { + userLogger.error("Web Event Error", error: error) + } + } +} diff --git a/Sources/Datadog/RUM/WebView/WebLogEventConsumer.swift b/Sources/Datadog/RUM/WebView/WebLogEventConsumer.swift new file mode 100644 index 0000000000..b9935ce23c --- /dev/null +++ b/Sources/Datadog/RUM/WebView/WebLogEventConsumer.swift @@ -0,0 +1,13 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2020 Datadog, Inc. + */ + +import Foundation + +internal class WebLogEventConsumer: WebEventConsumer { + func consume(event: [String: Any], eventType: String) { + // TODO: RUMM-1791 implement event consumers + } +} diff --git a/Sources/Datadog/RUM/WebView/WebRUMEventConsumer.swift b/Sources/Datadog/RUM/WebView/WebRUMEventConsumer.swift new file mode 100644 index 0000000000..9a4df60d05 --- /dev/null +++ b/Sources/Datadog/RUM/WebView/WebRUMEventConsumer.swift @@ -0,0 +1,13 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2020 Datadog, Inc. + */ + +import Foundation + +internal class WebRUMEventConsumer: WebEventConsumer { + func consume(event: [String: Any], eventType: String) { + // TODO: RUMM-1791 implement event consumers + } +} From afd46de98f55e93978f12a415e88674967dc9c9a Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Mon, 29 Nov 2021 12:06:15 +0100 Subject: [PATCH 03/33] RUMM-1785 DatadogEventBridgeTests added --- Datadog/Datadog.xcodeproj/project.pbxproj | 12 +++ .../DebugWebviewViewController.swift | 4 +- .../RUM/WebView/DatadogEventBridge.swift | 16 ++-- .../WKUserContentController+Datadog.swift | 4 +- .../RUM/WebView/DatadogEventBridgeTests.swift | 85 +++++++++++++++++++ 5 files changed, 108 insertions(+), 13 deletions(-) create mode 100644 Tests/DatadogTests/Datadog/RUM/WebView/DatadogEventBridgeTests.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 6499c77d0e..fc6f8168a9 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -505,6 +505,7 @@ 9EB4B864274FAB410041CD03 /* WebLogEventConsumer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB4B863274FAB410041CD03 /* WebLogEventConsumer.swift */; }; 9EB4B866274FAB4C0041CD03 /* WebRUMEventConsumer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB4B865274FAB4C0041CD03 /* WebRUMEventConsumer.swift */; }; 9EB4B868275103E40041CD03 /* DatadogEventBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB4B867275103E40041CD03 /* DatadogEventBridge.swift */; }; + 9EB4B86C27510AF90041CD03 /* DatadogEventBridgeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB4B86B27510AF90041CD03 /* DatadogEventBridgeTests.swift */; }; 9EC2835A26CFEE0B00FACF1C /* RUMMobileVitalsScenarioTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EC2835926CFEE0B00FACF1C /* RUMMobileVitalsScenarioTests.swift */; }; 9EC2835E26CFF57A00FACF1C /* RUMMobileVitalsScenario.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9EC2835D26CFF57A00FACF1C /* RUMMobileVitalsScenario.storyboard */; }; 9EC2836026CFF59400FACF1C /* RUMMobileVitalsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EC2835F26CFF59400FACF1C /* RUMMobileVitalsViewController.swift */; }; @@ -1166,6 +1167,7 @@ 9EB4B863274FAB410041CD03 /* WebLogEventConsumer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebLogEventConsumer.swift; sourceTree = ""; }; 9EB4B865274FAB4C0041CD03 /* WebRUMEventConsumer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRUMEventConsumer.swift; sourceTree = ""; }; 9EB4B867275103E40041CD03 /* DatadogEventBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatadogEventBridge.swift; sourceTree = ""; }; + 9EB4B86B27510AF90041CD03 /* DatadogEventBridgeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatadogEventBridgeTests.swift; sourceTree = ""; }; 9EC2835926CFEE0B00FACF1C /* RUMMobileVitalsScenarioTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMMobileVitalsScenarioTests.swift; sourceTree = ""; }; 9EC2835D26CFF57A00FACF1C /* RUMMobileVitalsScenario.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = RUMMobileVitalsScenario.storyboard; sourceTree = ""; }; 9EC2835F26CFF59400FACF1C /* RUMMobileVitalsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMMobileVitalsViewController.swift; sourceTree = ""; }; @@ -2950,6 +2952,7 @@ 61E5332D24B75DC7003D6C4E /* RUM */ = { isa = PBXGroup; children = ( + 9EB4B86A27510AC80041CD03 /* WebView */, B3FC3C1226526F4100DEED9E /* RUMVitals */, 61E5332E24B75DE2003D6C4E /* RUMFeatureTests.swift */, 618715FA24DC5EE700FC0F69 /* DataModels */, @@ -3279,6 +3282,14 @@ path = WebView; sourceTree = ""; }; + 9EB4B86A27510AC80041CD03 /* WebView */ = { + isa = PBXGroup; + children = ( + 9EB4B86B27510AF90041CD03 /* DatadogEventBridgeTests.swift */, + ); + path = WebView; + sourceTree = ""; + }; 9EC2835C26CFF56B00FACF1C /* MobileVitals */ = { isa = PBXGroup; children = ( @@ -4131,6 +4142,7 @@ 61BBD19724ED50040023E65F /* FeaturesConfigurationTests.swift in Sources */, 61133C612423990D00786299 /* HTTPClientTests.swift in Sources */, 61133C6A2423990D00786299 /* DatadogTests.swift in Sources */, + 9EB4B86C27510AF90041CD03 /* DatadogEventBridgeTests.swift in Sources */, 61B0387C252724AB00518F3C /* DDURLSessionDelegateTests.swift in Sources */, 61AADBDD263C7ECF008ABC6F /* EquatableInTests.swift in Sources */, 61133C5E2423990D00786299 /* DataUploadDelayTests.swift in Sources */, diff --git a/Datadog/Example/Debugging/DebugWebviewViewController.swift b/Datadog/Example/Debugging/DebugWebviewViewController.swift index cdcfcf239e..084e47176b 100644 --- a/Datadog/Example/Debugging/DebugWebviewViewController.swift +++ b/Datadog/Example/Debugging/DebugWebviewViewController.swift @@ -6,9 +6,7 @@ import UIKit import WebKit - -// TODO: RUMM-1794 remove @testable and use public API -@testable import Datadog +import Datadog class DebugWebviewViewController: UIViewController { @IBOutlet weak var rumServiceNameTextField: UITextField! diff --git a/Sources/Datadog/RUM/WebView/DatadogEventBridge.swift b/Sources/Datadog/RUM/WebView/DatadogEventBridge.swift index 44fa36c018..286bb86678 100644 --- a/Sources/Datadog/RUM/WebView/DatadogEventBridge.swift +++ b/Sources/Datadog/RUM/WebView/DatadogEventBridge.swift @@ -12,11 +12,11 @@ internal protocol WebEventConsumer { func consume(event: JSON, eventType: String) } -internal enum WebEventError: Error { +internal enum WebEventError: Error, Equatable { case dataSerialization(message: String) - case JSONSerialization(rawJSON: Any) - case invalidMessage(message: Any) - case missingKey(key: String, json: JSON) + case JSONSerialization(rawJSONDescription: String) + case invalidMessage(description: String) + case missingKey(key: String) } internal class DatadogEventBridge { @@ -36,14 +36,14 @@ internal class DatadogEventBridge { func consume(_ message: Any) throws { guard let message = message as? String else { - throw WebEventError.invalidMessage(message: message) + throw WebEventError.invalidMessage(description: String(describing: message)) } let eventJSON = try Self.parse(message) guard let eventType = eventJSON[Constants.eventTypeKey] as? String else { - throw WebEventError.missingKey(key: Constants.eventTypeKey, json: eventJSON) + throw WebEventError.missingKey(key: Constants.eventTypeKey) } guard let wrappedEvent = eventJSON[Constants.eventKey] as? JSON else { - throw WebEventError.missingKey(key: Constants.eventKey, json: eventJSON) + throw WebEventError.missingKey(key: Constants.eventKey) } if eventType == Constants.eventTypeLog { @@ -59,7 +59,7 @@ internal class DatadogEventBridge { } let rawJSON = try JSONSerialization.jsonObject(with: data, options: []) guard let json = rawJSON as? JSON else { - throw WebEventError.JSONSerialization(rawJSON: rawJSON) + throw WebEventError.JSONSerialization(rawJSONDescription: String(describing: rawJSON)) } return json } diff --git a/Sources/Datadog/RUM/WebView/WKUserContentController+Datadog.swift b/Sources/Datadog/RUM/WebView/WKUserContentController+Datadog.swift index c672f9fad4..2ac6438823 100644 --- a/Sources/Datadog/RUM/WebView/WKUserContentController+Datadog.swift +++ b/Sources/Datadog/RUM/WebView/WKUserContentController+Datadog.swift @@ -7,8 +7,8 @@ import Foundation import WebKit -internal extension WKUserContentController { - // TODO: RUMM-1794 rename the method +// TODO: RUMM-1794 rename the method +public extension WKUserContentController { func addDatadogMessageHandler(allowedWebViewHosts: [String]) { let bridgeName = DatadogMessageHandler.name diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/DatadogEventBridgeTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/DatadogEventBridgeTests.swift new file mode 100644 index 0000000000..3b733312c1 --- /dev/null +++ b/Tests/DatadogTests/Datadog/RUM/WebView/DatadogEventBridgeTests.swift @@ -0,0 +1,85 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2020 Datadog, Inc. + */ + +import XCTest +@testable import Datadog + +fileprivate class MockEventConsumer: WebEventConsumer { + private(set) var consumedEvents: [(event: JSON, eventType: String)] = [] + + func consume(event: JSON, eventType: String) { + consumedEvents.append((event: event, eventType: eventType)) + } +} + +class DatadogEventBridgeTests: XCTestCase { + let messageLog = """ + {"eventType":"log","event":{"date":1635932927012,"error":{"origin":"console"},"message":"console error: error","session_id":"0110cab4-7471-480e-aa4e-7ce039ced355","status":"error","view":{"referrer":"","url":"https://datadoghq.dev/browser-sdk-test-playground"}},"tags":["browser_sdk_version:3.6.13"]} + """ + let messageRUM = """ + {"eventType":"view","event":{"application":{"id":"xxx"},"date":1635933113708,"service":"super","session":{"id":"0110cab4-7471-480e-aa4e-7ce039ced355","type":"user"},"type":"view","view":{"action":{"count":0},"cumulative_layout_shift":0,"dom_complete":152800000,"dom_content_loaded":118300000,"dom_interactive":116400000,"error":{"count":0},"first_contentful_paint":121300000,"id":"64308fd4-83f9-48cb-b3e1-1e91f6721230","in_foreground_periods":[],"is_active":true,"largest_contentful_paint":121299000,"load_event":152800000,"loading_time":152800000,"loading_type":"initial_load","long_task":{"count":0},"referrer":"","resource":{"count":3},"time_spent":3120000000,"url":"http://localhost:8080/test.html"},"_dd":{"document_version":2,"drift":0,"format_version":2,"session":{"plan":2}}},"tags":["browser_sdk_version:3.6.13"]} + """ + + private let mockLogEventConsumer = MockEventConsumer() + private let mockRUMEventConsumer = MockEventConsumer() + lazy var eventBridge = DatadogEventBridge( + logEventConsumer: mockLogEventConsumer, + rumEventConsumer: mockRUMEventConsumer + ) + + // MARK: - Parsing + + func testWhenLogMessageIsValid_itSucceedsParsing() throws { + let json = try DatadogEventBridge.parse(messageLog) + XCTAssertEqual(json[DatadogEventBridge.Constants.eventTypeKey] as? String, "log") + } + + func testWhenRUMMessageIsValid_itSucceedsParsing() throws { + let json = try DatadogEventBridge.parse(messageRUM) + XCTAssertEqual(json[DatadogEventBridge.Constants.eventTypeKey] as? String, "view") + } + + func testWhenMessageIsInvalid_itFailsParsing() { + let messageInvalidJSON = """ + { 123: foobar } + """ + XCTAssertThrowsError( + try DatadogEventBridge.parse(messageInvalidJSON), + "Non-string keys (123) should throw" + ) + } + + // MARK: - Routing + + func testWhenEventTypeIsMissing_itThrows() { + let messageMissingEventType = """ + {"event":{"date":1635932927012,"error":{"origin":"console"}}} + """ + XCTAssertThrowsError( + try eventBridge.consume(messageMissingEventType), + "Missing eventType should throw" + ) { error in + XCTAssertEqual( + error as? WebEventError, + WebEventError.missingKey(key: DatadogEventBridge.Constants.eventTypeKey) + ) + } + } + + func testWhenEventTypeIsLog_itGoesToLogEventConsumer() throws { + try eventBridge.consume(messageLog) + + XCTAssertEqual(mockLogEventConsumer.consumedEvents.count, 1) + XCTAssertEqual(mockRUMEventConsumer.consumedEvents.count, 0) + } + + func testWhenEventTypeIsNonLog_itGoesToRUMEventConsumer() throws { + try eventBridge.consume(messageRUM) + + XCTAssertEqual(mockLogEventConsumer.consumedEvents.count, 0) + XCTAssertEqual(mockRUMEventConsumer.consumedEvents.count, 1) + } +} From cdf0cced9102a9a498413a568b6102edf96d3b2c Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Tue, 30 Nov 2021 15:46:30 +0100 Subject: [PATCH 04/33] RUMM-1785 PR comments addressed --- Datadog/Datadog.xcodeproj/project.pbxproj | 2 +- .../WebView/DatadogEventBridge.swift | 8 ++--- .../WKUserContentController+Datadog.swift | 15 +++++--- .../WebView/WebLogEventConsumer.swift | 0 .../WebView/WebRUMEventConsumer.swift | 0 .../RUM/WebView/DatadogEventBridgeTests.swift | 35 +++++++++---------- 6 files changed, 32 insertions(+), 28 deletions(-) rename Sources/Datadog/{RUM => FeaturesIntegration}/WebView/DatadogEventBridge.swift (89%) rename Sources/Datadog/{RUM => FeaturesIntegration}/WebView/WKUserContentController+Datadog.swift (86%) rename Sources/Datadog/{RUM => FeaturesIntegration}/WebView/WebLogEventConsumer.swift (100%) rename Sources/Datadog/{RUM => FeaturesIntegration}/WebView/WebRUMEventConsumer.swift (100%) diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index fc6f8168a9..2dfbe3177b 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -1895,6 +1895,7 @@ 6156CB9B24E18224008CB2B2 /* TracingWithRUMIntegration.swift */, 61FC5F4425CC23C9006BB4DE /* RUMWithCrashContextIntegration.swift */, E13A880B257922EC004FB174 /* EnvironmentSpanIntegration.swift */, + 9EB4B860274E79620041CD03 /* WebView */, 6112B11C25C84E7F00B37771 /* CrashReporting */, ); path = FeaturesIntegration; @@ -2944,7 +2945,6 @@ 616CCE11250A181C009FED46 /* AutoInstrumentation */, 618DCFD524C7264100589570 /* UUIDs */, 61B22E5824F3E6A700DC26D2 /* Debugging */, - 9EB4B860274E79620041CD03 /* WebView */, ); path = RUM; sourceTree = ""; diff --git a/Sources/Datadog/RUM/WebView/DatadogEventBridge.swift b/Sources/Datadog/FeaturesIntegration/WebView/DatadogEventBridge.swift similarity index 89% rename from Sources/Datadog/RUM/WebView/DatadogEventBridge.swift rename to Sources/Datadog/FeaturesIntegration/WebView/DatadogEventBridge.swift index 286bb86678..6a84c643fd 100644 --- a/Sources/Datadog/RUM/WebView/DatadogEventBridge.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/DatadogEventBridge.swift @@ -14,7 +14,7 @@ internal protocol WebEventConsumer { internal enum WebEventError: Error, Equatable { case dataSerialization(message: String) - case JSONSerialization(rawJSONDescription: String) + case JSONDeserialization(rawJSONDescription: String) case invalidMessage(description: String) case missingKey(key: String) } @@ -38,7 +38,7 @@ internal class DatadogEventBridge { guard let message = message as? String else { throw WebEventError.invalidMessage(description: String(describing: message)) } - let eventJSON = try Self.parse(message) + let eventJSON = try parse(message) guard let eventType = eventJSON[Constants.eventTypeKey] as? String else { throw WebEventError.missingKey(key: Constants.eventTypeKey) } @@ -53,13 +53,13 @@ internal class DatadogEventBridge { } } - static func parse(_ message: String) throws -> JSON { + private func parse(_ message: String) throws -> JSON { guard let data = message.data(using: .utf8) else { throw WebEventError.dataSerialization(message: message) } let rawJSON = try JSONSerialization.jsonObject(with: data, options: []) guard let json = rawJSON as? JSON else { - throw WebEventError.JSONSerialization(rawJSONDescription: String(describing: rawJSON)) + throw WebEventError.JSONDeserialization(rawJSONDescription: String(describing: rawJSON)) } return json } diff --git a/Sources/Datadog/RUM/WebView/WKUserContentController+Datadog.swift b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift similarity index 86% rename from Sources/Datadog/RUM/WebView/WKUserContentController+Datadog.swift rename to Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift index 2ac6438823..11dea040ee 100644 --- a/Sources/Datadog/RUM/WebView/WKUserContentController+Datadog.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift @@ -52,6 +52,10 @@ public extension WKUserContentController { private class DatadogMessageHandler: NSObject, WKScriptMessageHandler { static let name = "DatadogEventBridge" private let eventBridge: DatadogEventBridge + private let queue = DispatchQueue( + label: "com.datadoghq.JSEventBridge", + target: .global(qos: .userInteractive) + ) init(eventBridge: DatadogEventBridge) { self.eventBridge = eventBridge @@ -61,11 +65,12 @@ private class DatadogMessageHandler: NSObject, WKScriptMessageHandler { _ userContentController: WKUserContentController, didReceive message: WKScriptMessage ) { - print(message.name + String(describing: message.body)) - do { - try eventBridge.consume(message.body) - } catch { - userLogger.error("Web Event Error", error: error) + queue.async { + do { + try self.eventBridge.consume(message.body) + } catch { + userLogger.error("🔥 Web Event Error: \(error)") + } } } } diff --git a/Sources/Datadog/RUM/WebView/WebLogEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift similarity index 100% rename from Sources/Datadog/RUM/WebView/WebLogEventConsumer.swift rename to Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift diff --git a/Sources/Datadog/RUM/WebView/WebRUMEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift similarity index 100% rename from Sources/Datadog/RUM/WebView/WebRUMEventConsumer.swift rename to Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/DatadogEventBridgeTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/DatadogEventBridgeTests.swift index 3b733312c1..700d5c7d40 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/DatadogEventBridgeTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/DatadogEventBridgeTests.swift @@ -16,13 +16,6 @@ fileprivate class MockEventConsumer: WebEventConsumer { } class DatadogEventBridgeTests: XCTestCase { - let messageLog = """ - {"eventType":"log","event":{"date":1635932927012,"error":{"origin":"console"},"message":"console error: error","session_id":"0110cab4-7471-480e-aa4e-7ce039ced355","status":"error","view":{"referrer":"","url":"https://datadoghq.dev/browser-sdk-test-playground"}},"tags":["browser_sdk_version:3.6.13"]} - """ - let messageRUM = """ - {"eventType":"view","event":{"application":{"id":"xxx"},"date":1635933113708,"service":"super","session":{"id":"0110cab4-7471-480e-aa4e-7ce039ced355","type":"user"},"type":"view","view":{"action":{"count":0},"cumulative_layout_shift":0,"dom_complete":152800000,"dom_content_loaded":118300000,"dom_interactive":116400000,"error":{"count":0},"first_contentful_paint":121300000,"id":"64308fd4-83f9-48cb-b3e1-1e91f6721230","in_foreground_periods":[],"is_active":true,"largest_contentful_paint":121299000,"load_event":152800000,"loading_time":152800000,"loading_type":"initial_load","long_task":{"count":0},"referrer":"","resource":{"count":3},"time_spent":3120000000,"url":"http://localhost:8080/test.html"},"_dd":{"document_version":2,"drift":0,"format_version":2,"session":{"plan":2}}},"tags":["browser_sdk_version:3.6.13"]} - """ - private let mockLogEventConsumer = MockEventConsumer() private let mockRUMEventConsumer = MockEventConsumer() lazy var eventBridge = DatadogEventBridge( @@ -32,22 +25,12 @@ class DatadogEventBridgeTests: XCTestCase { // MARK: - Parsing - func testWhenLogMessageIsValid_itSucceedsParsing() throws { - let json = try DatadogEventBridge.parse(messageLog) - XCTAssertEqual(json[DatadogEventBridge.Constants.eventTypeKey] as? String, "log") - } - - func testWhenRUMMessageIsValid_itSucceedsParsing() throws { - let json = try DatadogEventBridge.parse(messageRUM) - XCTAssertEqual(json[DatadogEventBridge.Constants.eventTypeKey] as? String, "view") - } - func testWhenMessageIsInvalid_itFailsParsing() { let messageInvalidJSON = """ { 123: foobar } """ XCTAssertThrowsError( - try DatadogEventBridge.parse(messageInvalidJSON), + try eventBridge.consume(messageInvalidJSON), "Non-string keys (123) should throw" ) } @@ -70,16 +53,32 @@ class DatadogEventBridgeTests: XCTestCase { } func testWhenEventTypeIsLog_itGoesToLogEventConsumer() throws { + let messageLog = """ + {"eventType":"log","event":{"date":1635932927012,"error":{"origin":"console"},"message":"console error: error","session_id":"0110cab4-7471-480e-aa4e-7ce039ced355","status":"error","view":{"referrer":"","url":"https://datadoghq.dev/browser-sdk-test-playground"}},"tags":["browser_sdk_version:3.6.13"]} + """ try eventBridge.consume(messageLog) XCTAssertEqual(mockLogEventConsumer.consumedEvents.count, 1) XCTAssertEqual(mockRUMEventConsumer.consumedEvents.count, 0) + + let consumedEvent = try XCTUnwrap(mockLogEventConsumer.consumedEvents.first) + XCTAssertEqual(consumedEvent.eventType, "log") + XCTAssertEqual(consumedEvent.event["session_id"] as? String, "0110cab4-7471-480e-aa4e-7ce039ced355") + XCTAssertEqual((consumedEvent.event["view"] as? JSON)?["url"] as? String, "https://datadoghq.dev/browser-sdk-test-playground") } func testWhenEventTypeIsNonLog_itGoesToRUMEventConsumer() throws { + let messageRUM = """ + {"eventType":"view","event":{"application":{"id":"xxx"},"date":1635933113708,"service":"super","session":{"id":"0110cab4-7471-480e-aa4e-7ce039ced355","type":"user"},"type":"view","view":{"action":{"count":0},"cumulative_layout_shift":0,"dom_complete":152800000,"dom_content_loaded":118300000,"dom_interactive":116400000,"error":{"count":0},"first_contentful_paint":121300000,"id":"64308fd4-83f9-48cb-b3e1-1e91f6721230","in_foreground_periods":[],"is_active":true,"largest_contentful_paint":121299000,"load_event":152800000,"loading_time":152800000,"loading_type":"initial_load","long_task":{"count":0},"referrer":"","resource":{"count":3},"time_spent":3120000000,"url":"http://localhost:8080/test.html"},"_dd":{"document_version":2,"drift":0,"format_version":2,"session":{"plan":2}}},"tags":["browser_sdk_version:3.6.13"]} + """ try eventBridge.consume(messageRUM) XCTAssertEqual(mockLogEventConsumer.consumedEvents.count, 0) XCTAssertEqual(mockRUMEventConsumer.consumedEvents.count, 1) + + let consumedEvent = try XCTUnwrap(mockRUMEventConsumer.consumedEvents.first) + XCTAssertEqual(consumedEvent.eventType, "view") + XCTAssertEqual((consumedEvent.event["session"] as? JSON)?["id"] as? String, "0110cab4-7471-480e-aa4e-7ce039ced355") + XCTAssertEqual((consumedEvent.event["view"] as? JSON)?["url"] as? String, "http://localhost:8080/test.html") } } From 4d7a40c04e53757777dfb3165ab8d32a720c8f56 Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Mon, 6 Dec 2021 11:18:56 +0100 Subject: [PATCH 05/33] RUMM-1785 PR comments HostsSanitizer refactored out and shared --- Datadog/Datadog.xcodeproj/project.pbxproj | 28 +++++-- .../Datadog/Core/FeaturesConfiguration.swift | 51 ++----------- .../Datadog/Core/Utils/HostsSanitizer.swift | 57 ++++++++++++++ .../WKUserContentController+Datadog.swift | 20 +++-- ...EventBridge.swift => WebEventBridge.swift} | 2 +- .../Core/FeaturesConfigurationTests.swift | 69 +++-------------- .../Core/Utils/HostsSanitizerTests.swift | 75 +++++++++++++++++++ .../Datadog/Mocks/CoreMocks.swift | 8 ++ ...WKUserContentController+DatadogTests.swift | 35 +++++++++ ...eTests.swift => WebEventBridgeTests.swift} | 6 +- 10 files changed, 230 insertions(+), 121 deletions(-) create mode 100644 Sources/Datadog/Core/Utils/HostsSanitizer.swift rename Sources/Datadog/FeaturesIntegration/WebView/{DatadogEventBridge.swift => WebEventBridge.swift} (98%) create mode 100644 Tests/DatadogTests/Datadog/Core/Utils/HostsSanitizerTests.swift create mode 100644 Tests/DatadogTests/Datadog/RUM/WebView/WKUserContentController+DatadogTests.swift rename Tests/DatadogTests/Datadog/RUM/WebView/{DatadogEventBridgeTests.swift => WebEventBridgeTests.swift} (95%) diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 2dfbe3177b..38ddfb970e 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -501,11 +501,14 @@ 9E989A4225F640D100235FC3 /* AppStateListenerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E989A4125F640D100235FC3 /* AppStateListenerTests.swift */; }; 9E9973F1268DF69500D8059B /* VitalInfoSampler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9973F0268DF69500D8059B /* VitalInfoSampler.swift */; }; 9EA3CA6926775A3500B16871 /* VitalRefreshRateReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA3CA6826775A3500B16871 /* VitalRefreshRateReader.swift */; }; + 9EA8A7F1275E1518007D6FDB /* HostsSanitizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA8A7F0275E1518007D6FDB /* HostsSanitizerTests.swift */; }; + 9EAF0CF6275A21100044E8CA /* WKUserContentController+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAF0CF5275A21100044E8CA /* WKUserContentController+DatadogTests.swift */; }; + 9EAF0CF8275A2FDC0044E8CA /* HostsSanitizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAF0CF7275A2FDC0044E8CA /* HostsSanitizer.swift */; }; 9EB4B862274E79D50041CD03 /* WKUserContentController+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB4B861274E79D50041CD03 /* WKUserContentController+Datadog.swift */; }; 9EB4B864274FAB410041CD03 /* WebLogEventConsumer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB4B863274FAB410041CD03 /* WebLogEventConsumer.swift */; }; 9EB4B866274FAB4C0041CD03 /* WebRUMEventConsumer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB4B865274FAB4C0041CD03 /* WebRUMEventConsumer.swift */; }; - 9EB4B868275103E40041CD03 /* DatadogEventBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB4B867275103E40041CD03 /* DatadogEventBridge.swift */; }; - 9EB4B86C27510AF90041CD03 /* DatadogEventBridgeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB4B86B27510AF90041CD03 /* DatadogEventBridgeTests.swift */; }; + 9EB4B868275103E40041CD03 /* WebEventBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB4B867275103E40041CD03 /* WebEventBridge.swift */; }; + 9EB4B86C27510AF90041CD03 /* WebEventBridgeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB4B86B27510AF90041CD03 /* WebEventBridgeTests.swift */; }; 9EC2835A26CFEE0B00FACF1C /* RUMMobileVitalsScenarioTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EC2835926CFEE0B00FACF1C /* RUMMobileVitalsScenarioTests.swift */; }; 9EC2835E26CFF57A00FACF1C /* RUMMobileVitalsScenario.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9EC2835D26CFF57A00FACF1C /* RUMMobileVitalsScenario.storyboard */; }; 9EC2836026CFF59400FACF1C /* RUMMobileVitalsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EC2835F26CFF59400FACF1C /* RUMMobileVitalsViewController.swift */; }; @@ -1163,11 +1166,14 @@ 9E9973F0268DF69500D8059B /* VitalInfoSampler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VitalInfoSampler.swift; sourceTree = ""; }; 9E9EB37624468CE90002C80B /* Datadog.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = Datadog.modulemap; sourceTree = ""; }; 9EA3CA6826775A3500B16871 /* VitalRefreshRateReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VitalRefreshRateReader.swift; sourceTree = ""; }; + 9EA8A7F0275E1518007D6FDB /* HostsSanitizerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostsSanitizerTests.swift; sourceTree = ""; }; + 9EAF0CF5275A21100044E8CA /* WKUserContentController+DatadogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WKUserContentController+DatadogTests.swift"; sourceTree = ""; }; + 9EAF0CF7275A2FDC0044E8CA /* HostsSanitizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostsSanitizer.swift; sourceTree = ""; }; 9EB4B861274E79D50041CD03 /* WKUserContentController+Datadog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WKUserContentController+Datadog.swift"; sourceTree = ""; }; 9EB4B863274FAB410041CD03 /* WebLogEventConsumer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebLogEventConsumer.swift; sourceTree = ""; }; 9EB4B865274FAB4C0041CD03 /* WebRUMEventConsumer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRUMEventConsumer.swift; sourceTree = ""; }; - 9EB4B867275103E40041CD03 /* DatadogEventBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatadogEventBridge.swift; sourceTree = ""; }; - 9EB4B86B27510AF90041CD03 /* DatadogEventBridgeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatadogEventBridgeTests.swift; sourceTree = ""; }; + 9EB4B867275103E40041CD03 /* WebEventBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebEventBridge.swift; sourceTree = ""; }; + 9EB4B86B27510AF90041CD03 /* WebEventBridgeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebEventBridgeTests.swift; sourceTree = ""; }; 9EC2835926CFEE0B00FACF1C /* RUMMobileVitalsScenarioTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMMobileVitalsScenarioTests.swift; sourceTree = ""; }; 9EC2835D26CFF57A00FACF1C /* RUMMobileVitalsScenario.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = RUMMobileVitalsScenario.storyboard; sourceTree = ""; }; 9EC2835F26CFF59400FACF1C /* RUMMobileVitalsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMMobileVitalsViewController.swift; sourceTree = ""; }; @@ -1450,6 +1456,7 @@ 61363D9C24D999F70084CD6F /* DDError.swift */, 6139CD702589FAFD007E8BB7 /* Retrying.swift */, 611529A425E3DD51004F740E /* ValuePublisher.swift */, + 9EAF0CF7275A2FDC0044E8CA /* HostsSanitizer.swift */, ); path = Utils; sourceTree = ""; @@ -2542,6 +2549,7 @@ 618C365E248E85B400520CDE /* DateFormattingTests.swift */, 9E58E8E224615EDA008E5063 /* JSONEncoderTests.swift */, 61363D9E24D99BAA0084CD6F /* DDErrorTests.swift */, + 9EA8A7F0275E1518007D6FDB /* HostsSanitizerTests.swift */, ); path = Utils; sourceTree = ""; @@ -3275,7 +3283,7 @@ isa = PBXGroup; children = ( 9EB4B861274E79D50041CD03 /* WKUserContentController+Datadog.swift */, - 9EB4B867275103E40041CD03 /* DatadogEventBridge.swift */, + 9EB4B867275103E40041CD03 /* WebEventBridge.swift */, 9EB4B863274FAB410041CD03 /* WebLogEventConsumer.swift */, 9EB4B865274FAB4C0041CD03 /* WebRUMEventConsumer.swift */, ); @@ -3285,7 +3293,8 @@ 9EB4B86A27510AC80041CD03 /* WebView */ = { isa = PBXGroup; children = ( - 9EB4B86B27510AF90041CD03 /* DatadogEventBridgeTests.swift */, + 9EB4B86B27510AF90041CD03 /* WebEventBridgeTests.swift */, + 9EAF0CF5275A21100044E8CA /* WKUserContentController+DatadogTests.swift */, ); path = WebView; sourceTree = ""; @@ -3992,7 +4001,7 @@ 613E793B2577B6EE00DFCC17 /* DataReader.swift in Sources */, 614B0A5324EBFE5500A2A780 /* DDRUMMonitor.swift in Sources */, 61133BE32423979B00786299 /* UserInfoProvider.swift in Sources */, - 9EB4B868275103E40041CD03 /* DatadogEventBridge.swift in Sources */, + 9EB4B868275103E40041CD03 /* WebEventBridge.swift in Sources */, 61133BE02423979B00786299 /* Datadog.swift in Sources */, 61133BCB2423979B00786299 /* CarrierInfoProvider.swift in Sources */, 61C5A89024509AA700DA608C /* TracingFeature.swift in Sources */, @@ -4061,6 +4070,7 @@ 6114FE0F257667D40084E372 /* ConsentAwareDataWriter.swift in Sources */, 61C5A88624509A0C00DA608C /* TracingUUIDGenerator.swift in Sources */, 61133BD92423979B00786299 /* DataUploadDelay.swift in Sources */, + 9EAF0CF8275A2FDC0044E8CA /* HostsSanitizer.swift in Sources */, 61C5A88C24509A0C00DA608C /* HTTPHeadersWriter.swift in Sources */, 61BB2B1B244A185D009F3F56 /* PerformancePreset.swift in Sources */, 614B0A4B24EBC43D00A2A780 /* RUMUserInfoProvider.swift in Sources */, @@ -4113,6 +4123,7 @@ 61F9CABA2513A7F5000A5E61 /* RUMSessionMatcher.swift in Sources */, 61C3638324361BE200C4D4E6 /* DatadogPrivateMocks.swift in Sources */, 61AD4E182451C7FF006E34EA /* TracingFeatureMocks.swift in Sources */, + 9EAF0CF6275A21100044E8CA /* WKUserContentController+DatadogTests.swift in Sources */, 615A4A8924A34FD700233986 /* DDTracerTests.swift in Sources */, 615A4A8724A3452800233986 /* DDTracerConfigurationTests.swift in Sources */, 613E81F725A743600084B751 /* RUMEventsMapperTests.swift in Sources */, @@ -4142,7 +4153,7 @@ 61BBD19724ED50040023E65F /* FeaturesConfigurationTests.swift in Sources */, 61133C612423990D00786299 /* HTTPClientTests.swift in Sources */, 61133C6A2423990D00786299 /* DatadogTests.swift in Sources */, - 9EB4B86C27510AF90041CD03 /* DatadogEventBridgeTests.swift in Sources */, + 9EB4B86C27510AF90041CD03 /* WebEventBridgeTests.swift in Sources */, 61B0387C252724AB00518F3C /* DDURLSessionDelegateTests.swift in Sources */, 61AADBDD263C7ECF008ABC6F /* EquatableInTests.swift in Sources */, 61133C5E2423990D00786299 /* DataUploadDelayTests.swift in Sources */, @@ -4208,6 +4219,7 @@ 6114FE3B25768AA90084E372 /* ConsentProviderTests.swift in Sources */, 61133C642423990D00786299 /* LoggerTests.swift in Sources */, 617B953D24BF4D8F00E6F443 /* RUMMonitorTests.swift in Sources */, + 9EA8A7F1275E1518007D6FDB /* HostsSanitizerTests.swift in Sources */, 61F187FC25FA7DD60022CE9A /* InternalMonitoringFeatureTests.swift in Sources */, 61B5E42126DF85C7000B0A5F /* DDRUMMonitor+apiTests.m in Sources */, 61786F7724FCDE05009E6BAB /* RUMDebuggingTests.swift in Sources */, diff --git a/Sources/Datadog/Core/FeaturesConfiguration.swift b/Sources/Datadog/Core/FeaturesConfiguration.swift index 61ad99b6d9..8e3179208c 100644 --- a/Sources/Datadog/Core/FeaturesConfiguration.swift +++ b/Sources/Datadog/Core/FeaturesConfiguration.swift @@ -114,7 +114,7 @@ extension FeaturesConfiguration { /// /// Throws an error on invalid user input, i.e. broken custom URL. /// Prints a warning if configuration is inconsistent, i.e. RUM is enabled, but RUM Application ID was not specified. - init(configuration: Datadog.Configuration, appContext: AppContext) throws { + init(configuration: Datadog.Configuration, appContext: AppContext, hostsSanitizer: HostsSanitizing = HostsSanitizer()) throws { var logging: Logging? var tracing: Tracing? var rum: RUM? @@ -229,7 +229,10 @@ extension FeaturesConfiguration { if let firstPartyHosts = configuration.firstPartyHosts { if configuration.tracingEnabled || configuration.rumEnabled { urlSessionAutoInstrumentation = URLSessionAutoInstrumentation( - userDefinedFirstPartyHosts: sanitized(firstPartyHosts: firstPartyHosts), + userDefinedFirstPartyHosts: hostsSanitizer.sanitized( + hosts: firstPartyHosts, + warningMessage: "The first party host configured for Datadog SDK is not valid" + ), sdkInternalURLs: [ logsEndpoint.url, tracesEndpoint.url, @@ -316,47 +319,3 @@ private func ifValid(clientToken: String) throws -> String { } return clientToken } - -private func sanitized(firstPartyHosts: Set) -> Set { - let urlRegex = #"^(http|https)://(.*)"# - let hostRegex = #"^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)+([A-Za-z]|[A-Za-z][A-Za-z0-9-]*[A-Za-z0-9])$"# - let ipRegex = #"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"# - - var warnings: [String] = [] - - let array: [String] = firstPartyHosts.compactMap { host in - if host.range(of: urlRegex, options: .regularExpression) != nil { - // if an URL is given instead of the host, take its `host` part - if let sanitizedHost = URL(string: host)?.host { - warnings.append("'\(host)' is an url and will be sanitized to: '\(sanitizedHost)'.") - return sanitizedHost - } else { - warnings.append("'\(host)' is not a valid host name and will be dropped.") - return nil - } - } else if host.range(of: hostRegex, options: .regularExpression) != nil { - // if a valid host name is given, accept it - return host - } else if host.range(of: ipRegex, options: .regularExpression) != nil { - // if a valid IP address is given, accept it - return host - } else if host == "localhost" { - // if "localhost" given, accept it - return host - } else { - // otherwise, drop - warnings.append("'\(host)' is not a valid host name and will be dropped.") - return nil - } - } - - warnings.forEach { warning in - consolePrint( - """ - ⚠️ The first party host configured for Datadog SDK is not valid: \(warning) - """ - ) - } - - return Set(array) -} diff --git a/Sources/Datadog/Core/Utils/HostsSanitizer.swift b/Sources/Datadog/Core/Utils/HostsSanitizer.swift new file mode 100644 index 0000000000..6954d613ae --- /dev/null +++ b/Sources/Datadog/Core/Utils/HostsSanitizer.swift @@ -0,0 +1,57 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2020 Datadog, Inc. + */ + +import Foundation + +internal protocol HostsSanitizing { + func sanitized(hosts: Set, warningMessage: String) -> Set +} + +internal struct HostsSanitizer: HostsSanitizing { + func sanitized(hosts: Set, warningMessage: String) -> Set { + let urlRegex = #"^(http|https)://(.*)"# + let hostRegex = #"^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)+([A-Za-z]|[A-Za-z][A-Za-z0-9-]*[A-Za-z0-9])$"# + let ipRegex = #"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"# + + var warnings: [String] = [] + + let array: [String] = hosts.compactMap { host in + if host.range(of: urlRegex, options: .regularExpression) != nil { + // if an URL is given instead of the host, take its `host` part + if let sanitizedHost = URL(string: host)?.host { + warnings.append("'\(host)' is an url and will be sanitized to: '\(sanitizedHost)'.") + return sanitizedHost + } else { + warnings.append("'\(host)' is not a valid host name and will be dropped.") + return nil + } + } else if host.range(of: hostRegex, options: .regularExpression) != nil { + // if a valid host name is given, accept it + return host + } else if host.range(of: ipRegex, options: .regularExpression) != nil { + // if a valid IP address is given, accept it + return host + } else if host == "localhost" { + // if "localhost" given, accept it + return host + } else { + // otherwise, drop + warnings.append("'\(host)' is not a valid host name and will be dropped.") + return nil + } + } + + warnings.forEach { warning in + consolePrint( + """ + ⚠️ \(warningMessage): \(warning) + """ + ) + } + + return Set(array) + } +} diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift index 11dea040ee..e3baf4b917 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift @@ -7,13 +7,17 @@ import Foundation import WebKit -// TODO: RUMM-1794 rename the method +// TODO: RUMM-1794 rename the methods public extension WKUserContentController { - func addDatadogMessageHandler(allowedWebViewHosts: [String]) { + func addDatadogMessageHandler(allowedWebViewHosts: Set) { + __addDatadogMessageHandler(allowedWebViewHosts: allowedWebViewHosts, hostsSanitizer: HostsSanitizer()) + } + + internal func __addDatadogMessageHandler(allowedWebViewHosts: Set, hostsSanitizer: HostsSanitizing) { let bridgeName = DatadogMessageHandler.name let messageHandler = DatadogMessageHandler( - eventBridge: DatadogEventBridge( + eventBridge: WebEventBridge( logEventConsumer: WebLogEventConsumer(), rumEventConsumer: WebRUMEventConsumer() ) @@ -25,7 +29,11 @@ public extension WKUserContentController { let webkitMethodName = "window.webkit.messageHandlers.\(bridgeName).postMessage" // `WKScriptMessageHandlerWithReply` returns `Promise` and `browser-sdk` expects immediate values. // We inject a user script to return `allowedWebViewHosts` instead of using `WKScriptMessageHandlerWithReply` - let allowedWebViewHostsString = allowedWebViewHosts + let sanitizedHosts = hostsSanitizer.sanitized( + hosts: allowedWebViewHosts, + warningMessage: "The allowed WebView host configured for Datadog SDK is not valid" + ) + let allowedWebViewHostsString = sanitizedHosts .map { return "\"\($0)\"" } .joined(separator: ",") @@ -51,13 +59,13 @@ public extension WKUserContentController { private class DatadogMessageHandler: NSObject, WKScriptMessageHandler { static let name = "DatadogEventBridge" - private let eventBridge: DatadogEventBridge + private let eventBridge: WebEventBridge private let queue = DispatchQueue( label: "com.datadoghq.JSEventBridge", target: .global(qos: .userInteractive) ) - init(eventBridge: DatadogEventBridge) { + init(eventBridge: WebEventBridge) { self.eventBridge = eventBridge } diff --git a/Sources/Datadog/FeaturesIntegration/WebView/DatadogEventBridge.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift similarity index 98% rename from Sources/Datadog/FeaturesIntegration/WebView/DatadogEventBridge.swift rename to Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift index 6a84c643fd..457a7f3bdc 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/DatadogEventBridge.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift @@ -19,7 +19,7 @@ internal enum WebEventError: Error, Equatable { case missingKey(key: String) } -internal class DatadogEventBridge { +internal class WebEventBridge { struct Constants { static let eventTypeKey = "eventType" static let eventKey = "event" diff --git a/Tests/DatadogTests/Datadog/Core/FeaturesConfigurationTests.swift b/Tests/DatadogTests/Datadog/Core/FeaturesConfigurationTests.swift index 86a6025764..6f4a8a8009 100644 --- a/Tests/DatadogTests/Datadog/Core/FeaturesConfigurationTests.swift +++ b/Tests/DatadogTests/Datadog/Core/FeaturesConfigurationTests.swift @@ -734,71 +734,26 @@ class FeaturesConfigurationTests: XCTestCase { ) } - func testWhenSomeOfTheFirstPartyHostsAreMistaken_itPrintsWarningsAndDoesSanitization() throws { - let printFunction = PrintFunctionMock() - consolePrint = printFunction.print - defer { consolePrint = { print($0) } } - + func testWhenFirstPartyHostsAreProvided_itPassesThemToSanitizer() throws { // When let firstPartyHosts: Set = [ - "https://first-party.com", // sanitize to → "first-party.com" - "http://api.first-party.com", // sanitize to → "api.first-party.com" - "https://first-party.com/v2/api", // sanitize to → "first-party.com" - "https://192.168.0.1/api", // sanitize to → "192.168.0.1" - "https://192.168.0.2", // sanitize to → "192.168.0.2" - "invalid-host-name", // drop - "192.168.0.3:8080", // drop - "", // drop - "localhost", // accept - "192.168.0.4", // accept - "valid-host-name.com", // accept + "https://first-party.com", + "http://api.first-party.com", + "https://first-party.com/v2/api" ] // Then - let configuration = try FeaturesConfiguration( + let mockHostsSanitizer = MockHostsSanitizer() + _ = try FeaturesConfiguration( configuration: .mockWith(rumEnabled: true, firstPartyHosts: firstPartyHosts), - appContext: .mockAny() - ) - - XCTAssertEqual( - configuration.urlSessionAutoInstrumentation?.userDefinedFirstPartyHosts, - [ - "first-party.com", - "api.first-party.com", - "localhost", - "192.168.0.1", - "192.168.0.2", - "localhost", - "192.168.0.4", - "valid-host-name.com" - ] + appContext: .mockAny(), + hostsSanitizer: mockHostsSanitizer ) - XCTAssertTrue( - printFunction.printedMessages.contains("⚠️ The first party host configured for Datadog SDK is not valid: '192.168.0.3:8080' is not a valid host name and will be dropped.") - ) - XCTAssertTrue( - printFunction.printedMessages.contains("⚠️ The first party host configured for Datadog SDK is not valid: '' is not a valid host name and will be dropped.") - ) - XCTAssertTrue( - printFunction.printedMessages.contains("⚠️ The first party host configured for Datadog SDK is not valid: 'https://first-party.com' is an url and will be sanitized to: 'first-party.com'.") - ) - XCTAssertTrue( - printFunction.printedMessages.contains("⚠️ The first party host configured for Datadog SDK is not valid: 'https://192.168.0.1/api' is an url and will be sanitized to: '192.168.0.1'.") - ) - XCTAssertTrue( - printFunction.printedMessages.contains("⚠️ The first party host configured for Datadog SDK is not valid: 'http://api.first-party.com' is an url and will be sanitized to: 'api.first-party.com'.") - ) - XCTAssertTrue( - printFunction.printedMessages.contains("⚠️ The first party host configured for Datadog SDK is not valid: 'https://first-party.com/v2/api' is an url and will be sanitized to: 'first-party.com'.") - ) - XCTAssertTrue( - printFunction.printedMessages.contains("⚠️ The first party host configured for Datadog SDK is not valid: 'invalid-host-name' is not a valid host name and will be dropped.") - ) - XCTAssertTrue( - printFunction.printedMessages.contains("⚠️ The first party host configured for Datadog SDK is not valid: 'https://192.168.0.2' is an url and will be sanitized to: '192.168.0.2'.") - ) - XCTAssertEqual(printFunction.printedMessages.count, 8) + XCTAssertEqual(mockHostsSanitizer.sanitizations.count, 1) + let sanitization = try XCTUnwrap(mockHostsSanitizer.sanitizations.first) + XCTAssertEqual(sanitization.hosts, firstPartyHosts) + XCTAssertEqual(sanitization.warningMessage, "The first party host configured for Datadog SDK is not valid") } // MARK: - Helpers diff --git a/Tests/DatadogTests/Datadog/Core/Utils/HostsSanitizerTests.swift b/Tests/DatadogTests/Datadog/Core/Utils/HostsSanitizerTests.swift new file mode 100644 index 0000000000..e04dcd5267 --- /dev/null +++ b/Tests/DatadogTests/Datadog/Core/Utils/HostsSanitizerTests.swift @@ -0,0 +1,75 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2020 Datadog, Inc. + */ + +import XCTest +@testable import Datadog + +class HostsSanitizerTests: XCTestCase { + func testSanitizationAndWarningMessages() throws { + let printFunction = PrintFunctionMock() + consolePrint = printFunction.print + defer { consolePrint = { print($0) } } + + // When + let hosts: Set = [ + "https://first-party.com", // sanitize to → "first-party.com" + "http://api.first-party.com", // sanitize to → "api.first-party.com" + "https://first-party.com/v2/api", // sanitize to → "first-party.com" + "https://192.168.0.1/api", // sanitize to → "192.168.0.1" + "https://192.168.0.2", // sanitize to → "192.168.0.2" + "invalid-host-name", // drop + "192.168.0.3:8080", // drop + "", // drop + "localhost", // accept + "192.168.0.4", // accept + "valid-host-name.com", // accept + ] + + // Then + let sanitizer = HostsSanitizer() + let sanitizedHosts = sanitizer.sanitized(hosts: hosts, warningMessage: "Host is not valid") + + XCTAssertEqual( + sanitizedHosts, + [ + "first-party.com", + "api.first-party.com", + "localhost", + "192.168.0.1", + "192.168.0.2", + "localhost", + "192.168.0.4", + "valid-host-name.com" + ] + ) + + XCTAssertTrue( + printFunction.printedMessages.contains("⚠️ Host is not valid: '192.168.0.3:8080' is not a valid host name and will be dropped.") + ) + XCTAssertTrue( + printFunction.printedMessages.contains("⚠️ Host is not valid: '' is not a valid host name and will be dropped.") + ) + XCTAssertTrue( + printFunction.printedMessages.contains("⚠️ Host is not valid: 'https://first-party.com' is an url and will be sanitized to: 'first-party.com'.") + ) + XCTAssertTrue( + printFunction.printedMessages.contains("⚠️ Host is not valid: 'https://192.168.0.1/api' is an url and will be sanitized to: '192.168.0.1'.") + ) + XCTAssertTrue( + printFunction.printedMessages.contains("⚠️ Host is not valid: 'http://api.first-party.com' is an url and will be sanitized to: 'api.first-party.com'.") + ) + XCTAssertTrue( + printFunction.printedMessages.contains("⚠️ Host is not valid: 'https://first-party.com/v2/api' is an url and will be sanitized to: 'first-party.com'.") + ) + XCTAssertTrue( + printFunction.printedMessages.contains("⚠️ Host is not valid: 'invalid-host-name' is not a valid host name and will be dropped.") + ) + XCTAssertTrue( + printFunction.printedMessages.contains("⚠️ Host is not valid: 'https://192.168.0.2' is an url and will be sanitized to: '192.168.0.2'.") + ) + XCTAssertEqual(printFunction.printedMessages.count, 8) + } +} diff --git a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift index ad6b5aca02..b0d1c8dc33 100644 --- a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift @@ -1048,6 +1048,14 @@ extension DDError: RandomMockable { } } +class MockHostsSanitizer: HostsSanitizing { + private(set) var sanitizations = [(hosts: Set, warningMessage: String)]() + func sanitized(hosts: Set, warningMessage: String) -> Set { + sanitizations.append((hosts: hosts, warningMessage: warningMessage)) + return hosts + } +} + // MARK: - Global Dependencies Mocks /// Mock which can be used to intercept messages printed by `developerLogger` or diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WKUserContentController+DatadogTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WKUserContentController+DatadogTests.swift new file mode 100644 index 0000000000..4fad15162f --- /dev/null +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WKUserContentController+DatadogTests.swift @@ -0,0 +1,35 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2020 Datadog, Inc. + */ + +import XCTest +import WebKit +@testable import Datadog + +final class DDUserContentController: WKUserContentController { + private(set) var messageHandlerNames = [String]() + + override func add(_ scriptMessageHandler: WKScriptMessageHandler, name: String) { + messageHandlerNames.append(name) + } +} + +class WKUserContentController_DatadogTests: XCTestCase { + func testItAddsUserScriptAndMessageHandler() throws { + let mockSanitizer = MockHostsSanitizer() + let controller = DDUserContentController() + + let initialUserScriptCount = controller.userScripts.count + + controller.__addDatadogMessageHandler(allowedWebViewHosts: ["datadoghq.com"], hostsSanitizer: mockSanitizer) + + XCTAssertEqual(controller.userScripts.count, initialUserScriptCount + 1) + XCTAssertEqual(controller.messageHandlerNames, ["DatadogEventBridge"]) + XCTAssertEqual(mockSanitizer.sanitizations.count, 1) + let sanitization = try XCTUnwrap(mockSanitizer.sanitizations.first) + XCTAssertEqual(sanitization.hosts, ["datadoghq.com"]) + XCTAssertEqual(sanitization.warningMessage, "The allowed WebView host configured for Datadog SDK is not valid") + } +} diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/DatadogEventBridgeTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WebEventBridgeTests.swift similarity index 95% rename from Tests/DatadogTests/Datadog/RUM/WebView/DatadogEventBridgeTests.swift rename to Tests/DatadogTests/Datadog/RUM/WebView/WebEventBridgeTests.swift index 700d5c7d40..3ffde28db5 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/DatadogEventBridgeTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WebEventBridgeTests.swift @@ -15,10 +15,10 @@ fileprivate class MockEventConsumer: WebEventConsumer { } } -class DatadogEventBridgeTests: XCTestCase { +class WebEventBridgeTests: XCTestCase { private let mockLogEventConsumer = MockEventConsumer() private let mockRUMEventConsumer = MockEventConsumer() - lazy var eventBridge = DatadogEventBridge( + lazy var eventBridge = WebEventBridge( logEventConsumer: mockLogEventConsumer, rumEventConsumer: mockRUMEventConsumer ) @@ -47,7 +47,7 @@ class DatadogEventBridgeTests: XCTestCase { ) { error in XCTAssertEqual( error as? WebEventError, - WebEventError.missingKey(key: DatadogEventBridge.Constants.eventTypeKey) + WebEventError.missingKey(key: WebEventBridge.Constants.eventTypeKey) ) } } From 42d6a9ea8ad75228a7a11c1a1ef83a5858de48ec Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Wed, 8 Dec 2021 18:54:38 +0100 Subject: [PATCH 06/33] RUMM-1791 WebRUMEventConsumer added --- .../WKUserContentController+Datadog.swift | 28 +++++- .../WebView/WebEventBridge.swift | 6 +- .../WebView/WebLogEventConsumer.swift | 4 +- .../WebView/WebRUMEventConsumer.swift | 87 ++++++++++++++++++- 4 files changed, 115 insertions(+), 10 deletions(-) diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift index e3baf4b917..1600d020ec 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift @@ -7,6 +7,17 @@ import Foundation import WebKit +internal protocol WebRUMEventContextProviding { + var context: RUMContext? { get } +} + +internal class WebRUMEventContextProvider: WebRUMEventContextProviding { + var context: RUMContext? { + // TODO: RUMM-1786 implement web event context provider + return nil + } +} + // TODO: RUMM-1794 rename the methods public extension WKUserContentController { func addDatadogMessageHandler(allowedWebViewHosts: Set) { @@ -16,10 +27,19 @@ public extension WKUserContentController { internal func __addDatadogMessageHandler(allowedWebViewHosts: Set, hostsSanitizer: HostsSanitizing) { let bridgeName = DatadogMessageHandler.name + let contextProvider = WebRUMEventContextProvider() + let logEventConsumer = WebLogEventConsumer() + let rumEventConsumer = WebRUMEventConsumer( + dataWriter: RUMFeature.instance?.storage.writer, + dateCorrector: RUMFeature.instance?.dateCorrector, + webRUMEventMapper: WebRUMEventMapper(), + contextProvider: contextProvider + ) + let messageHandler = DatadogMessageHandler( eventBridge: WebEventBridge( - logEventConsumer: WebLogEventConsumer(), - rumEventConsumer: WebRUMEventConsumer() + logEventConsumer: logEventConsumer, + rumEventConsumer: rumEventConsumer ) ) add(messageHandler, name: bridgeName) @@ -73,9 +93,11 @@ private class DatadogMessageHandler: NSObject, WKScriptMessageHandler { _ userContentController: WKUserContentController, didReceive message: WKScriptMessage ) { + // message.body must be called within UI thread + let messageBody = message.body queue.async { do { - try self.eventBridge.consume(message.body) + try self.eventBridge.consume(messageBody) } catch { userLogger.error("🔥 Web Event Error: \(error)") } diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift index 457a7f3bdc..7cd97257ce 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift @@ -9,7 +9,7 @@ import Foundation internal typealias JSON = [String: Any] internal protocol WebEventConsumer { - func consume(event: JSON, eventType: String) + func consume(event: JSON, eventType: String) throws } internal enum WebEventError: Error, Equatable { @@ -47,9 +47,9 @@ internal class WebEventBridge { } if eventType == Constants.eventTypeLog { - logEventConsumer.consume(event: wrappedEvent, eventType: eventType) + try logEventConsumer.consume(event: wrappedEvent, eventType: eventType) } else { - rumEventConsumer.consume(event: wrappedEvent, eventType: eventType) + try rumEventConsumer.consume(event: wrappedEvent, eventType: eventType) } } diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift index b9935ce23c..4ac31acf35 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift @@ -7,7 +7,7 @@ import Foundation internal class WebLogEventConsumer: WebEventConsumer { - func consume(event: [String: Any], eventType: String) { - // TODO: RUMM-1791 implement event consumers + func consume(event: JSON, eventType: String) throws { + // TODO: RUMM-1791 implement log event consumer } } diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift index 9a4df60d05..4ebc174e72 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift @@ -6,8 +6,91 @@ import Foundation +// TODO: RUMM-1786 implement mappers +internal class WebRUMEventMapper { } + internal class WebRUMEventConsumer: WebEventConsumer { - func consume(event: [String: Any], eventType: String) { - // TODO: RUMM-1791 implement event consumers + private let dataWriter: AsyncWriter? + private let dateCorrector: DateCorrectorType? + private let webRUMEventMapper: WebRUMEventMapper? + private let contextProvider: WebRUMEventContextProviding? + + init( + dataWriter: AsyncWriter?, + dateCorrector: DateCorrectorType?, + webRUMEventMapper: WebRUMEventMapper?, + contextProvider: WebRUMEventContextProviding? + ) { + self.dataWriter = dataWriter + self.dateCorrector = dateCorrector + self.webRUMEventMapper = webRUMEventMapper + self.contextProvider = contextProvider + } + + func consume(event: JSON, eventType: String) throws { + let eventData = try JSONSerialization.data(withJSONObject: event, options: []) + let jsonDecoder = JSONDecoder() + let rumContext = contextProvider?.context + + switch eventType { + case "view": + let viewEvent = try jsonDecoder.decode(RUMViewEvent.self, from: eventData) + let mappedViewEvent = mapIfNeeded(dataModel: viewEvent, context: rumContext, offset: getOffset(viewID: viewEvent.view.id)) + write(mappedViewEvent) + case "action": + let actionEvent = try jsonDecoder.decode(RUMViewEvent.self, from: eventData) + let mappedActionEvent = mapIfNeeded(dataModel: actionEvent, context: rumContext, offset: getOffset(viewID: actionEvent.view.id)) + write(mappedActionEvent) + case "resource": + let resourceEvent = try jsonDecoder.decode(RUMViewEvent.self, from: eventData) + let mappedResourceEvent = mapIfNeeded(dataModel: resourceEvent, context: rumContext, offset: getOffset(viewID: resourceEvent.view.id)) + write(mappedResourceEvent) + case "error": + let errorEvent = try jsonDecoder.decode(RUMViewEvent.self, from: eventData) + let mappedErrorEvent = mapIfNeeded(dataModel: errorEvent, context: rumContext, offset: getOffset(viewID: errorEvent.view.id)) + write(mappedErrorEvent) + case "long_task": + let longTaskEvent = try jsonDecoder.decode(RUMViewEvent.self, from: eventData) + let mappedLongTaskEvent = mapIfNeeded(dataModel: longTaskEvent, context: rumContext, offset: getOffset(viewID: longTaskEvent.view.id)) + write(mappedLongTaskEvent) + default: + userLogger.error("🔥 Web RUM Event Error - Unknown event type: \(eventType)") + } + } + + private func mapIfNeeded(dataModel: T, context: RUMContext?, offset: Offset) -> T { + guard let context = context else { + return dataModel + } + // TODO: RUMM-1786 implement mappers + let mappedDataModel = dataModel + return mappedDataModel + } + + private func write(_ model: T) { + dataWriter?.write(value: model) + } + + // MARK: - Time offsets + + // Q: do we really need to cache `offsets`? can't we just read `dateCorrector?.currentCorrection.serverTimeOffset`? + + private typealias Offset = TimeInterval + private var offsets = [String: Offset]() + + private func getOffset(viewID: String) -> Offset { + var offset = offsets[viewID] + if offset == nil { + offset = dateCorrector?.currentCorrection.serverTimeOffset + offsets[viewID] = offset + } + + purgeOffsets() + return offset ?? 0.0 + } + + private func purgeOffsets() { + // TODO: RUMM-1791 keep only 3 most recent entries. + // android uses LinkedHashMap/OrderedDictionary. } } From fd18fa7cf9b57fa62b9944a33c8c4e18a0f19eb2 Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Fri, 10 Dec 2021 14:50:25 +0100 Subject: [PATCH 07/33] RUMM-1791 WebLogEventConsumer added --- Datadog/Datadog.xcodeproj/project.pbxproj | 4 + .../WKUserContentController+Datadog.swift | 20 +-- .../WebView/WebLogEventConsumer.swift | 65 +++++++- .../Datadog/Logging/Log/LogEventEncoder.swift | 1 + .../WebView/WebLogEventConsumerTests.swift | 142 ++++++++++++++++++ 5 files changed, 218 insertions(+), 14 deletions(-) create mode 100644 Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 38ddfb970e..c030a188e2 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -502,6 +502,7 @@ 9E9973F1268DF69500D8059B /* VitalInfoSampler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9973F0268DF69500D8059B /* VitalInfoSampler.swift */; }; 9EA3CA6926775A3500B16871 /* VitalRefreshRateReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA3CA6826775A3500B16871 /* VitalRefreshRateReader.swift */; }; 9EA8A7F1275E1518007D6FDB /* HostsSanitizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA8A7F0275E1518007D6FDB /* HostsSanitizerTests.swift */; }; + 9EA8A7F82768A72B007D6FDB /* WebLogEventConsumerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA8A7F72768A72B007D6FDB /* WebLogEventConsumerTests.swift */; }; 9EAF0CF6275A21100044E8CA /* WKUserContentController+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAF0CF5275A21100044E8CA /* WKUserContentController+DatadogTests.swift */; }; 9EAF0CF8275A2FDC0044E8CA /* HostsSanitizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAF0CF7275A2FDC0044E8CA /* HostsSanitizer.swift */; }; 9EB4B862274E79D50041CD03 /* WKUserContentController+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB4B861274E79D50041CD03 /* WKUserContentController+Datadog.swift */; }; @@ -1167,6 +1168,7 @@ 9E9EB37624468CE90002C80B /* Datadog.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = Datadog.modulemap; sourceTree = ""; }; 9EA3CA6826775A3500B16871 /* VitalRefreshRateReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VitalRefreshRateReader.swift; sourceTree = ""; }; 9EA8A7F0275E1518007D6FDB /* HostsSanitizerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostsSanitizerTests.swift; sourceTree = ""; }; + 9EA8A7F72768A72B007D6FDB /* WebLogEventConsumerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebLogEventConsumerTests.swift; sourceTree = ""; }; 9EAF0CF5275A21100044E8CA /* WKUserContentController+DatadogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WKUserContentController+DatadogTests.swift"; sourceTree = ""; }; 9EAF0CF7275A2FDC0044E8CA /* HostsSanitizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostsSanitizer.swift; sourceTree = ""; }; 9EB4B861274E79D50041CD03 /* WKUserContentController+Datadog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WKUserContentController+Datadog.swift"; sourceTree = ""; }; @@ -3295,6 +3297,7 @@ children = ( 9EB4B86B27510AF90041CD03 /* WebEventBridgeTests.swift */, 9EAF0CF5275A21100044E8CA /* WKUserContentController+DatadogTests.swift */, + 9EA8A7F72768A72B007D6FDB /* WebLogEventConsumerTests.swift */, ); path = WebView; sourceTree = ""; @@ -4216,6 +4219,7 @@ 61B5E42B26DFC433000B0A5F /* DDNSURLSessionDelegate+apiTests.m in Sources */, 61F8CC092469295500FE2908 /* DatadogConfigurationBuilderTests.swift in Sources */, 61F1A623249B811200075390 /* Encoding.swift in Sources */, + 9EA8A7F82768A72B007D6FDB /* WebLogEventConsumerTests.swift in Sources */, 6114FE3B25768AA90084E372 /* ConsentProviderTests.swift in Sources */, 61133C642423990D00786299 /* LoggerTests.swift in Sources */, 617B953D24BF4D8F00E6F443 /* RUMMonitorTests.swift in Sources */, diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift index 1600d020ec..befbe3f387 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift @@ -7,17 +7,6 @@ import Foundation import WebKit -internal protocol WebRUMEventContextProviding { - var context: RUMContext? { get } -} - -internal class WebRUMEventContextProvider: WebRUMEventContextProviding { - var context: RUMContext? { - // TODO: RUMM-1786 implement web event context provider - return nil - } -} - // TODO: RUMM-1794 rename the methods public extension WKUserContentController { func addDatadogMessageHandler(allowedWebViewHosts: Set) { @@ -27,8 +16,13 @@ public extension WKUserContentController { internal func __addDatadogMessageHandler(allowedWebViewHosts: Set, hostsSanitizer: HostsSanitizing) { let bridgeName = DatadogMessageHandler.name - let contextProvider = WebRUMEventContextProvider() - let logEventConsumer = WebLogEventConsumer() + let contextProvider = (Global.rum as? RUMMonitor)?.contextProvider + let logEventConsumer = WebLogEventConsumer( + userLogsWriter: LoggingFeature.instance?.storage.writer, + internalLogsWriter: InternalMonitoringFeature.instance?.logsStorage.writer, + dateCorrector: LoggingFeature.instance?.dateCorrector, + rumContextProvider: contextProvider + ) let rumEventConsumer = WebRUMEventConsumer( dataWriter: RUMFeature.instance?.storage.writer, dateCorrector: RUMFeature.instance?.dateCorrector, diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift index 4ac31acf35..31ac760ce1 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift @@ -7,7 +7,70 @@ import Foundation internal class WebLogEventConsumer: WebEventConsumer { + private struct Constants { + static let logEventType = "log" + static let internalLogEventType = "internal_log" + + static let applicationIDKey = "application_id" + static let sessionIDKey = "session_id" + static let ddTagsKey = "ddtags" + static let dateKey = "date" + } + + private let userLogsWriter: Writer? + private let internalLogsWriter: Writer? + private let dateCorrector: DateCorrectorType? + private let rumContextProvider: RUMContextProvider? + private let jsonDecoder = JSONDecoder() + + private lazy var ddTags: String = { + let versionKey = LogEventEncoder.StaticCodingKeys.applicationVersion.rawValue + let versionValue = LoggingFeature.instance?.configuration.common.applicationVersion ?? "" + let envKey = LogEventEncoder.StaticCodingKeys.environment.rawValue + let envValue = LoggingFeature.instance?.configuration.common.environment ?? "" + + return "\(versionKey):\(versionValue),\(envKey):\(envValue)" + }() + + init( + userLogsWriter: Writer?, + internalLogsWriter: Writer?, + dateCorrector: DateCorrectorType?, + rumContextProvider: RUMContextProvider? + ) { + self.userLogsWriter = userLogsWriter + self.internalLogsWriter = internalLogsWriter + self.dateCorrector = dateCorrector + self.rumContextProvider = rumContextProvider + } + func consume(event: JSON, eventType: String) throws { - // TODO: RUMM-1791 implement log event consumer + var mutableEvent = event + + if let existingTags = mutableEvent[Constants.ddTagsKey] as? String, !existingTags.isEmpty { + mutableEvent[Constants.ddTagsKey] = "\(ddTags),\(existingTags)" + } else { + mutableEvent[Constants.ddTagsKey] = ddTags + } + + if let date = mutableEvent[Constants.dateKey] as? Int { + let serverTimeOffsetInNs = dateCorrector?.currentCorrection.serverTimeOffset.toInt64Nanoseconds ?? 0 + let correctedDate = Int64(date) + serverTimeOffsetInNs + mutableEvent[Constants.dateKey] = correctedDate + } + + if let context = rumContextProvider?.context { + mutableEvent[Constants.applicationIDKey] = context.rumApplicationID + mutableEvent[Constants.sessionIDKey] = context.sessionID.toRUMDataFormat + } + + let jsonData = try JSONSerialization.data(withJSONObject: mutableEvent, options: []) + let encodableEvent = try jsonDecoder.decode(CodableValue.self, from: jsonData) + + if eventType == Constants.logEventType { + userLogsWriter?.write(value: encodableEvent) + } else if eventType == Constants.internalLogEventType { + internalLogsWriter?.write(value: encodableEvent) + } } } diff --git a/Sources/Datadog/Logging/Log/LogEventEncoder.swift b/Sources/Datadog/Logging/Log/LogEventEncoder.swift index 58663c2808..252c6290d9 100644 --- a/Sources/Datadog/Logging/Log/LogEventEncoder.swift +++ b/Sources/Datadog/Logging/Log/LogEventEncoder.swift @@ -89,6 +89,7 @@ internal struct LogEventEncoder { case status case message case serviceName = "service" + case environment = "env" case tags = "ddtags" // MARK: - Error diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift new file mode 100644 index 0000000000..b3f6fa5a59 --- /dev/null +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift @@ -0,0 +1,142 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2020 Datadog, Inc. + */ + +import XCTest +@testable import Datadog + +class WebLogEventConsumerTests: XCTestCase { + let mockUserLogsWriter = FileWriterMock() + let mockInternalLogsWriter = FileWriterMock() + let mockDateCorrector = DateCorrectorMock() + let mockContextProvider = RUMContextProviderMock(context: .mockWith(rumApplicationID: "123456")) + + func testWhenValidWebLogEventPassed_itDecoratesAndPassesToWriter() throws { + let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! + mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) + mockDateCorrector.correctionOffset = 123 + let eventConsumer = WebLogEventConsumer( + userLogsWriter: mockUserLogsWriter, + internalLogsWriter: mockInternalLogsWriter, + dateCorrector: mockDateCorrector, + rumContextProvider: mockContextProvider + ) + + let webLogEvent: JSON = [ + "date": 1_635_932_927_012, + "error": ["origin": "console"], + "message": "console error: error", + "session_id": "0110cab4-7471-480e-aa4e-7ce039ced355", + "status": "error", + "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] + ] + let expectedWebLogEvent: JSON = [ + "date": 1_635_932_927_012 + 123.toInt64Nanoseconds, + "error": ["origin": "console"], + "message": "console error: error", + "application_id": "123456", + "session_id": mockSessionID.uuidString.lowercased(), + "status": "error", + "ddtags": "version:,env:", + "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] + ] + + try eventConsumer.consume(event: webLogEvent, eventType: "log") + + let data = try JSONEncoder().encode(mockUserLogsWriter.dataWritten as? CodableValue) + let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) + + AssertDictionariesEqual(writtenJSON, expectedWebLogEvent) + + XCTAssertNil(mockInternalLogsWriter.dataWritten) + } + + func testWhenValidWebInternalLogEventPassed_itDecoratesAndPassesToWriter() throws { + let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! + mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) + mockDateCorrector.correctionOffset = 123 + let eventConsumer = WebLogEventConsumer( + userLogsWriter: mockUserLogsWriter, + internalLogsWriter: mockInternalLogsWriter, + dateCorrector: mockDateCorrector, + rumContextProvider: mockContextProvider + ) + + let webLogEvent: JSON = [ + "date": 1_635_932_927_012, + "error": ["origin": "console"], + "message": "console error: error", + "session_id": "0110cab4-7471-480e-aa4e-7ce039ced355", + "status": "error", + "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] + ] + let expectedWebLogEvent: JSON = [ + "date": 1_635_932_927_012 + 123.toInt64Nanoseconds, + "error": ["origin": "console"], + "message": "console error: error", + "application_id": "123456", + "session_id": mockSessionID.uuidString.lowercased(), + "status": "error", + "ddtags": "version:,env:", + "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] + ] + + try eventConsumer.consume(event: webLogEvent, eventType: "internal_log") + + let data = try JSONEncoder().encode(mockInternalLogsWriter.dataWritten as? CodableValue) + let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) + + AssertDictionariesEqual(writtenJSON, expectedWebLogEvent) + + XCTAssertNil(mockUserLogsWriter.dataWritten) + } + + func testWhenInvalidEventTypePassed_itIgnoresEvent() throws { + let eventConsumer = WebLogEventConsumer( + userLogsWriter: mockUserLogsWriter, + internalLogsWriter: mockInternalLogsWriter, + dateCorrector: mockDateCorrector, + rumContextProvider: mockContextProvider + ) + + let webLogEvent: JSON = [ + "date": 1_635_932_927_012, + "message": "console error: error", + "status": "error" + ] + + try eventConsumer.consume(event: webLogEvent, eventType: "invalid_log") + + XCTAssertNil(mockUserLogsWriter.dataWritten) + XCTAssertNil(mockInternalLogsWriter.dataWritten) + } + + func testWhenContextIsUnavailable_itPassesEventAsIs() throws { + let eventConsumer = WebLogEventConsumer( + userLogsWriter: mockUserLogsWriter, + internalLogsWriter: mockInternalLogsWriter, + dateCorrector: nil, + rumContextProvider: nil + ) + + let webLogEvent: JSON = [ + "date": 1_635_932_927_012, + "error": ["origin": "console"], + "message": "console error: error", + "session_id": "0110cab4-7471-480e-aa4e-7ce039ced355", + "status": "error", + "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] + ] + + try eventConsumer.consume(event: webLogEvent, eventType: "log") + + let data = try JSONEncoder().encode(mockUserLogsWriter.dataWritten as? CodableValue) + let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) + + AssertDictionariesEqual(writtenJSON, webLogEvent) + + XCTAssertNil(mockInternalLogsWriter.dataWritten) + } +} From aeab8c45dcf5cf08064f409c2f464cf319268370 Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Wed, 15 Dec 2021 21:03:05 +0100 Subject: [PATCH 08/33] RUMM-1791 Offsets cache added to WebRUMEventConsumer --- .../WebView/WebRUMEventConsumer.swift | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift index 4ebc174e72..a240b1f508 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift @@ -10,16 +10,16 @@ import Foundation internal class WebRUMEventMapper { } internal class WebRUMEventConsumer: WebEventConsumer { - private let dataWriter: AsyncWriter? + private let dataWriter: Writer? private let dateCorrector: DateCorrectorType? private let webRUMEventMapper: WebRUMEventMapper? - private let contextProvider: WebRUMEventContextProviding? + private let contextProvider: RUMContextProvider? init( - dataWriter: AsyncWriter?, + dataWriter: Writer?, dateCorrector: DateCorrectorType?, webRUMEventMapper: WebRUMEventMapper?, - contextProvider: WebRUMEventContextProviding? + contextProvider: RUMContextProvider? ) { self.dataWriter = dataWriter self.dateCorrector = dateCorrector @@ -73,24 +73,25 @@ internal class WebRUMEventConsumer: WebEventConsumer { // MARK: - Time offsets - // Q: do we really need to cache `offsets`? can't we just read `dateCorrector?.currentCorrection.serverTimeOffset`? - private typealias Offset = TimeInterval - private var offsets = [String: Offset]() + private typealias ViewIDOffsetPair = (viewID: String, offset: Offset) + private var viewIDOffsetPairs = [ViewIDOffsetPair]() private func getOffset(viewID: String) -> Offset { - var offset = offsets[viewID] - if offset == nil { - offset = dateCorrector?.currentCorrection.serverTimeOffset - offsets[viewID] = offset - } - purgeOffsets() - return offset ?? 0.0 + + let found = viewIDOffsetPairs.first { $0.viewID == viewID } + if let found = found { + return found.offset + } + let offset = dateCorrector?.currentCorrection.serverTimeOffset ?? 0.0 + viewIDOffsetPairs.insert((viewID: viewID, offset: offset), at: 0) + return offset } private func purgeOffsets() { - // TODO: RUMM-1791 keep only 3 most recent entries. - // android uses LinkedHashMap/OrderedDictionary. + while viewIDOffsetPairs.count > 3 { + _ = viewIDOffsetPairs.popLast() + } } } From fc693d3089d93ba3bc8e5d3ef21e841b6e973125 Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Mon, 20 Dec 2021 21:30:21 +0100 Subject: [PATCH 09/33] RUMM-1791 PR comments addressed Optionals to non-optionals, non-optionals to optionals ashes to ashes, dust to dust... --- .../WKUserContentController+Datadog.swift | 32 ++++++++++++------- .../WebView/WebEventBridge.swift | 10 +++--- .../WebView/WebLogEventConsumer.swift | 12 +++---- .../WebView/WebRUMEventConsumer.swift | 12 +++---- .../WebView/WebLogEventConsumerTests.swift | 2 +- 5 files changed, 38 insertions(+), 30 deletions(-) diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift index befbe3f387..e5e211c73c 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift @@ -17,18 +17,26 @@ public extension WKUserContentController { let bridgeName = DatadogMessageHandler.name let contextProvider = (Global.rum as? RUMMonitor)?.contextProvider - let logEventConsumer = WebLogEventConsumer( - userLogsWriter: LoggingFeature.instance?.storage.writer, - internalLogsWriter: InternalMonitoringFeature.instance?.logsStorage.writer, - dateCorrector: LoggingFeature.instance?.dateCorrector, - rumContextProvider: contextProvider - ) - let rumEventConsumer = WebRUMEventConsumer( - dataWriter: RUMFeature.instance?.storage.writer, - dateCorrector: RUMFeature.instance?.dateCorrector, - webRUMEventMapper: WebRUMEventMapper(), - contextProvider: contextProvider - ) + + var logEventConsumer: WebLogEventConsumer? = nil + if let loggingFeature = LoggingFeature.instance { + logEventConsumer = WebLogEventConsumer( + userLogsWriter: loggingFeature.storage.writer, + internalLogsWriter: InternalMonitoringFeature.instance?.logsStorage.writer, + dateCorrector: loggingFeature.dateCorrector, + rumContextProvider: contextProvider + ) + } + + var rumEventConsumer: WebRUMEventConsumer? = nil + if let rumFeature = RUMFeature.instance { + rumEventConsumer = WebRUMEventConsumer( + dataWriter: rumFeature.storage.writer, + dateCorrector: rumFeature.dateCorrector, + webRUMEventMapper: WebRUMEventMapper(), + contextProvider: contextProvider + ) + } let messageHandler = DatadogMessageHandler( eventBridge: WebEventBridge( diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift index 7cd97257ce..994f153ec7 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift @@ -26,10 +26,10 @@ internal class WebEventBridge { static let eventTypeLog = "log" } - private let logEventConsumer: WebEventConsumer - private let rumEventConsumer: WebEventConsumer + private let logEventConsumer: WebEventConsumer? + private let rumEventConsumer: WebEventConsumer? - init(logEventConsumer: WebEventConsumer, rumEventConsumer: WebEventConsumer) { + init(logEventConsumer: WebEventConsumer?, rumEventConsumer: WebEventConsumer?) { self.logEventConsumer = logEventConsumer self.rumEventConsumer = rumEventConsumer } @@ -47,9 +47,9 @@ internal class WebEventBridge { } if eventType == Constants.eventTypeLog { - try logEventConsumer.consume(event: wrappedEvent, eventType: eventType) + try logEventConsumer?.consume(event: wrappedEvent, eventType: eventType) } else { - try rumEventConsumer.consume(event: wrappedEvent, eventType: eventType) + try rumEventConsumer?.consume(event: wrappedEvent, eventType: eventType) } } diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift index 31ac760ce1..0c3839f292 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift @@ -17,9 +17,9 @@ internal class WebLogEventConsumer: WebEventConsumer { static let dateKey = "date" } - private let userLogsWriter: Writer? + private let userLogsWriter: Writer private let internalLogsWriter: Writer? - private let dateCorrector: DateCorrectorType? + private let dateCorrector: DateCorrectorType private let rumContextProvider: RUMContextProvider? private let jsonDecoder = JSONDecoder() @@ -33,9 +33,9 @@ internal class WebLogEventConsumer: WebEventConsumer { }() init( - userLogsWriter: Writer?, + userLogsWriter: Writer, internalLogsWriter: Writer?, - dateCorrector: DateCorrectorType?, + dateCorrector: DateCorrectorType, rumContextProvider: RUMContextProvider? ) { self.userLogsWriter = userLogsWriter @@ -54,7 +54,7 @@ internal class WebLogEventConsumer: WebEventConsumer { } if let date = mutableEvent[Constants.dateKey] as? Int { - let serverTimeOffsetInNs = dateCorrector?.currentCorrection.serverTimeOffset.toInt64Nanoseconds ?? 0 + let serverTimeOffsetInNs = dateCorrector.currentCorrection.serverTimeOffset.toInt64Nanoseconds let correctedDate = Int64(date) + serverTimeOffsetInNs mutableEvent[Constants.dateKey] = correctedDate } @@ -68,7 +68,7 @@ internal class WebLogEventConsumer: WebEventConsumer { let encodableEvent = try jsonDecoder.decode(CodableValue.self, from: jsonData) if eventType == Constants.logEventType { - userLogsWriter?.write(value: encodableEvent) + userLogsWriter.write(value: encodableEvent) } else if eventType == Constants.internalLogEventType { internalLogsWriter?.write(value: encodableEvent) } diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift index a240b1f508..3327856bbf 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift @@ -10,14 +10,14 @@ import Foundation internal class WebRUMEventMapper { } internal class WebRUMEventConsumer: WebEventConsumer { - private let dataWriter: Writer? - private let dateCorrector: DateCorrectorType? + private let dataWriter: Writer + private let dateCorrector: DateCorrectorType private let webRUMEventMapper: WebRUMEventMapper? private let contextProvider: RUMContextProvider? init( - dataWriter: Writer?, - dateCorrector: DateCorrectorType?, + dataWriter: Writer, + dateCorrector: DateCorrectorType, webRUMEventMapper: WebRUMEventMapper?, contextProvider: RUMContextProvider? ) { @@ -68,7 +68,7 @@ internal class WebRUMEventConsumer: WebEventConsumer { } private func write(_ model: T) { - dataWriter?.write(value: model) + dataWriter.write(value: model) } // MARK: - Time offsets @@ -84,7 +84,7 @@ internal class WebRUMEventConsumer: WebEventConsumer { if let found = found { return found.offset } - let offset = dateCorrector?.currentCorrection.serverTimeOffset ?? 0.0 + let offset = dateCorrector.currentCorrection.serverTimeOffset viewIDOffsetPairs.insert((viewID: viewID, offset: offset), at: 0) return offset } diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift index b3f6fa5a59..080f9b7eca 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift @@ -117,7 +117,7 @@ class WebLogEventConsumerTests: XCTestCase { let eventConsumer = WebLogEventConsumer( userLogsWriter: mockUserLogsWriter, internalLogsWriter: mockInternalLogsWriter, - dateCorrector: nil, + dateCorrector: mockDateCorrector, rumContextProvider: nil ) From edc041308ac2f87978c8106af034adfe3c9673ea Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Wed, 22 Dec 2021 21:06:22 +0100 Subject: [PATCH 10/33] RUMM-1791 PR comments addressed WKUserContentController_DatadogTests improved --- .../WKUserContentController+Datadog.swift | 4 +- .../WebView/WebRUMEventConsumer.swift | 8 +- ...WKUserContentController+DatadogTests.swift | 120 +++++++++++++++++- 3 files changed, 123 insertions(+), 9 deletions(-) diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift index e5e211c73c..7486fa7dad 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift @@ -79,10 +79,10 @@ public extension WKUserContentController { } } -private class DatadogMessageHandler: NSObject, WKScriptMessageHandler { +internal class DatadogMessageHandler: NSObject, WKScriptMessageHandler { static let name = "DatadogEventBridge" private let eventBridge: WebEventBridge - private let queue = DispatchQueue( + let queue = DispatchQueue( label: "com.datadoghq.JSEventBridge", target: .global(qos: .userInteractive) ) diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift index 3327856bbf..2f1a1e9576 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift @@ -38,19 +38,19 @@ internal class WebRUMEventConsumer: WebEventConsumer { let mappedViewEvent = mapIfNeeded(dataModel: viewEvent, context: rumContext, offset: getOffset(viewID: viewEvent.view.id)) write(mappedViewEvent) case "action": - let actionEvent = try jsonDecoder.decode(RUMViewEvent.self, from: eventData) + let actionEvent = try jsonDecoder.decode(RUMActionEvent.self, from: eventData) let mappedActionEvent = mapIfNeeded(dataModel: actionEvent, context: rumContext, offset: getOffset(viewID: actionEvent.view.id)) write(mappedActionEvent) case "resource": - let resourceEvent = try jsonDecoder.decode(RUMViewEvent.self, from: eventData) + let resourceEvent = try jsonDecoder.decode(RUMResourceEvent.self, from: eventData) let mappedResourceEvent = mapIfNeeded(dataModel: resourceEvent, context: rumContext, offset: getOffset(viewID: resourceEvent.view.id)) write(mappedResourceEvent) case "error": - let errorEvent = try jsonDecoder.decode(RUMViewEvent.self, from: eventData) + let errorEvent = try jsonDecoder.decode(RUMErrorEvent.self, from: eventData) let mappedErrorEvent = mapIfNeeded(dataModel: errorEvent, context: rumContext, offset: getOffset(viewID: errorEvent.view.id)) write(mappedErrorEvent) case "long_task": - let longTaskEvent = try jsonDecoder.decode(RUMViewEvent.self, from: eventData) + let longTaskEvent = try jsonDecoder.decode(RUMLongTaskEvent.self, from: eventData) let mappedLongTaskEvent = mapIfNeeded(dataModel: longTaskEvent, context: rumContext, offset: getOffset(viewID: longTaskEvent.view.id)) write(mappedLongTaskEvent) default: diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WKUserContentController+DatadogTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WKUserContentController+DatadogTests.swift index 4fad15162f..fcf523e35c 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/WKUserContentController+DatadogTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WKUserContentController+DatadogTests.swift @@ -9,14 +9,41 @@ import WebKit @testable import Datadog final class DDUserContentController: WKUserContentController { - private(set) var messageHandlerNames = [String]() + typealias NameHandlerPair = (name: String, handler: WKScriptMessageHandler) + private(set) var messageHandlers = [NameHandlerPair]() override func add(_ scriptMessageHandler: WKScriptMessageHandler, name: String) { - messageHandlerNames.append(name) + messageHandlers.append((name: name, handler: scriptMessageHandler)) } } +final class MockScriptMessage: WKScriptMessage { + let mockBody: Any + + init(body: Any) { + self.mockBody = body + } + + override var body: Any { return mockBody } +} + class WKUserContentController_DatadogTests: XCTestCase { + override func setUp() { + super.setUp() + XCTAssertNil(Datadog.instance) + XCTAssertNil(LoggingFeature.instance) + XCTAssertNil(RUMFeature.instance) + temporaryFeatureDirectories.create() + } + + override func tearDown() { + XCTAssertNil(Datadog.instance) + XCTAssertNil(LoggingFeature.instance) + XCTAssertNil(RUMFeature.instance) + temporaryFeatureDirectories.delete() + super.tearDown() + } + func testItAddsUserScriptAndMessageHandler() throws { let mockSanitizer = MockHostsSanitizer() let controller = DDUserContentController() @@ -26,10 +53,97 @@ class WKUserContentController_DatadogTests: XCTestCase { controller.__addDatadogMessageHandler(allowedWebViewHosts: ["datadoghq.com"], hostsSanitizer: mockSanitizer) XCTAssertEqual(controller.userScripts.count, initialUserScriptCount + 1) - XCTAssertEqual(controller.messageHandlerNames, ["DatadogEventBridge"]) + XCTAssertEqual(controller.messageHandlers.map({ $0.name }), ["DatadogEventBridge"]) + XCTAssertEqual(mockSanitizer.sanitizations.count, 1) let sanitization = try XCTUnwrap(mockSanitizer.sanitizations.first) XCTAssertEqual(sanitization.hosts, ["datadoghq.com"]) XCTAssertEqual(sanitization.warningMessage, "The allowed WebView host configured for Datadog SDK is not valid") } + + func testItLogsInvalidWebMessages() throws { + let previousUserLogger = userLogger + defer { userLogger = previousUserLogger } + let output = LogOutputMock() + userLogger = .mockWith(logOutput: output) + + let controller = DDUserContentController() + controller.__addDatadogMessageHandler(allowedWebViewHosts: ["datadoghq.com"], hostsSanitizer: MockHostsSanitizer()) + + let messageHandler = try XCTUnwrap(controller.messageHandlers.first?.handler) as? DatadogMessageHandler + // non-string body is passed + messageHandler?.userContentController(controller, didReceive: MockScriptMessage(body: 123)) + messageHandler?.queue.sync { } + + XCTAssertEqual(output.recordedLog?.status, .error) + let userLogMessage = try XCTUnwrap(output.recordedLog?.message) + XCTAssertEqual(userLogMessage, #"🔥 Web Event Error: invalidMessage(description: "123")"#) + } + + func testSendingWebEvents() throws { + let dateProvider = RelativeDateProvider(startingFrom: Date(), advancingBySeconds: 1) + LoggingFeature.instance = .mockByRecordingLogMatchers( + directories: temporaryFeatureDirectories, + configuration: .mockWith( + common: .mockWith( + applicationVersion: "1.0.0", + applicationBundleIdentifier: "com.datadoghq.ios-sdk", + serviceName: "default-service-name", + environment: "tests", + sdkVersion: "1.2.3" + ) + ), + dependencies: .mockWith( + dateProvider: RelativeDateProvider(using: .mockDecember15th2019At10AMUTC()) + ) + ) + RUMFeature.instance = .mockByRecordingRUMEventMatchers( + directories: temporaryFeatureDirectories, + dependencies: .mockWith( + dateProvider: dateProvider + ) + ) + Global.rum = RUMMonitor.initialize() + defer { + LoggingFeature.instance?.deinitialize() + Global.rum = DDNoopRUMMonitor() + RUMFeature.instance?.deinitialize() + } + + let controller = DDUserContentController() + controller.__addDatadogMessageHandler( + allowedWebViewHosts: ["datadoghq.com"], + hostsSanitizer: MockHostsSanitizer() + ) + + let messageHandler = try XCTUnwrap(controller.messageHandlers.first?.handler) as? DatadogMessageHandler + let webLogMessage = MockScriptMessage(body: #"{"eventType":"log","event":{"date":1635932927012,"error":{"origin":"console"},"message":"console error: error","session_id":"0110cab4-7471-480e-aa4e-7ce039ced355","status":"error","view":{"referrer":"","url":"https://datadoghq.dev/browser-sdk-test-playground"}},"tags":["browser_sdk_version:3.6.13"]}"#) + messageHandler?.userContentController(controller, didReceive: webLogMessage) + + let logMatcher = try LoggingFeature.waitAndReturnLogMatchers(count: 1)[0] + + logMatcher.assertValue(forKey: "date", equals: 1_635_932_927_012) + logMatcher.assertValue(forKey: "ddtags", equals: "version:1.0.0,env:tests") + logMatcher.assertValue(forKey: "message", equals: "console error: error") + logMatcher.assertValue(forKey: "status", equals: "error") + logMatcher.assertValue( + forKey: "view", + equals: ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] + ) + logMatcher.assertValue( + forKey: "error", + equals: ["origin": "console"] + ) + // 00000000-0000-0000-0000-000000000000 is session_id of mock RUM context + logMatcher.assertValue(forKey: "session_id", equals: "00000000-0000-0000-0000-000000000000") + + let webRUMMessage = MockScriptMessage(body: #"{"eventType":"view","event":{"application":{"id":"xxx"},"date":1635933113708,"service":"super","session":{"id":"0110cab4-7471-480e-aa4e-7ce039ced355","type":"user"},"type":"view","view":{"action":{"count":0},"cumulative_layout_shift":0,"dom_complete":152800000,"dom_content_loaded":118300000,"dom_interactive":116400000,"error":{"count":0},"first_contentful_paint":121300000,"id":"64308fd4-83f9-48cb-b3e1-1e91f6721230","in_foreground_periods":[],"is_active":true,"largest_contentful_paint":121299000,"load_event":152800000,"loading_time":152800000,"loading_type":"initial_load","long_task":{"count":0},"referrer":"","resource":{"count":3},"time_spent":3120000000,"url":"http://localhost:8080/test.html"},"_dd":{"document_version":2,"drift":0,"format_version":2,"session":{"plan":2}}},"tags":["browser_sdk_version:3.6.13"]}"#) + messageHandler?.userContentController(controller, didReceive: webRUMMessage) + + let rumEventMatchers = try RUMFeature.waitAndReturnRUMEventMatchers(count: 1) + try rumEventMatchers[0].model(ofType: RUMViewEvent.self) { rumModel in + XCTAssertEqual(rumModel.application.id, "xxx") + XCTAssertEqual(rumModel.view.id, "64308fd4-83f9-48cb-b3e1-1e91f6721230") + } + } } From 5b0387966d37634e95a8ac0debfafe0ef4971b50 Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Thu, 23 Dec 2021 11:39:56 +0100 Subject: [PATCH 11/33] RUMM-1791 WebRUMEventConsumerTests added --- Datadog/Datadog.xcodeproj/project.pbxproj | 4 + .../WKUserContentController+Datadog.swift | 1 - .../WebView/WebRUMEventConsumer.swift | 14 +- .../WebView/WebLogEventConsumerTests.swift | 4 +- .../WebView/WebRUMEventConsumerTests.swift | 219 ++++++++++++++++++ 5 files changed, 228 insertions(+), 14 deletions(-) create mode 100644 Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index c030a188e2..bb26e5cb60 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -486,6 +486,7 @@ 9E307C3224C8846D0039607E /* RUMDataModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E26E6B824C87693000B3270 /* RUMDataModels.swift */; }; 9E359F4E26CD518D001E25E9 /* LongTaskObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E359F4D26CD518D001E25E9 /* LongTaskObserver.swift */; }; 9E36D92224373EA700BFBDB7 /* SwiftExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E36D92124373EA700BFBDB7 /* SwiftExtensionsTests.swift */; }; + 9E53889C2773C4B300A7DC42 /* WebRUMEventConsumerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E53889B2773C4B300A7DC42 /* WebRUMEventConsumerTests.swift */; }; 9E544A4F24753C6E00E83072 /* MethodSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E544A4E24753C6E00E83072 /* MethodSwizzler.swift */; }; 9E544A5124753DDE00E83072 /* MethodSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E544A5024753DDE00E83072 /* MethodSwizzlerTests.swift */; }; 9E55407C25812D1C00F6E3AD /* RUMMonitor+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E55407B25812D1C00F6E3AD /* RUMMonitor+objc.swift */; }; @@ -1151,6 +1152,7 @@ 9E2EF44E2694FA14008A7DAE /* VitalInfoSamplerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VitalInfoSamplerTests.swift; sourceTree = ""; }; 9E359F4D26CD518D001E25E9 /* LongTaskObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LongTaskObserver.swift; sourceTree = ""; }; 9E36D92124373EA700BFBDB7 /* SwiftExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftExtensionsTests.swift; sourceTree = ""; }; + 9E53889B2773C4B300A7DC42 /* WebRUMEventConsumerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRUMEventConsumerTests.swift; sourceTree = ""; }; 9E544A4E24753C6E00E83072 /* MethodSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MethodSwizzler.swift; sourceTree = ""; }; 9E544A5024753DDE00E83072 /* MethodSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MethodSwizzlerTests.swift; sourceTree = ""; }; 9E55407B25812D1C00F6E3AD /* RUMMonitor+objc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RUMMonitor+objc.swift"; sourceTree = ""; }; @@ -3298,6 +3300,7 @@ 9EB4B86B27510AF90041CD03 /* WebEventBridgeTests.swift */, 9EAF0CF5275A21100044E8CA /* WKUserContentController+DatadogTests.swift */, 9EA8A7F72768A72B007D6FDB /* WebLogEventConsumerTests.swift */, + 9E53889B2773C4B300A7DC42 /* WebRUMEventConsumerTests.swift */, ); path = WebView; sourceTree = ""; @@ -4117,6 +4120,7 @@ 61C5A89D24509C1100DA608C /* DDSpanTests.swift in Sources */, 61B038BA2527257B00518F3C /* URLSessionAutoInstrumentationMocks.swift in Sources */, 61133C672423990D00786299 /* LogConsoleOutputTests.swift in Sources */, + 9E53889C2773C4B300A7DC42 /* WebRUMEventConsumerTests.swift in Sources */, 6114FDEC257659E90084E372 /* FeatureDirectoriesMock.swift in Sources */, 61122EE825B1C92500F9C7F5 /* SpanSanitizerTests.swift in Sources */, 61B5E42526DFAFBC000B0A5F /* DDGlobal+apiTests.m in Sources */, diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift index 7486fa7dad..d163669268 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift @@ -33,7 +33,6 @@ public extension WKUserContentController { rumEventConsumer = WebRUMEventConsumer( dataWriter: rumFeature.storage.writer, dateCorrector: rumFeature.dateCorrector, - webRUMEventMapper: WebRUMEventMapper(), contextProvider: contextProvider ) } diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift index 2f1a1e9576..5fda40e0b9 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift @@ -6,24 +6,18 @@ import Foundation -// TODO: RUMM-1786 implement mappers -internal class WebRUMEventMapper { } - internal class WebRUMEventConsumer: WebEventConsumer { private let dataWriter: Writer private let dateCorrector: DateCorrectorType - private let webRUMEventMapper: WebRUMEventMapper? private let contextProvider: RUMContextProvider? init( dataWriter: Writer, dateCorrector: DateCorrectorType, - webRUMEventMapper: WebRUMEventMapper?, contextProvider: RUMContextProvider? ) { self.dataWriter = dataWriter self.dateCorrector = dateCorrector - self.webRUMEventMapper = webRUMEventMapper self.contextProvider = contextProvider } @@ -59,12 +53,8 @@ internal class WebRUMEventConsumer: WebEventConsumer { } private func mapIfNeeded(dataModel: T, context: RUMContext?, offset: Offset) -> T { - guard let context = context else { - return dataModel - } - // TODO: RUMM-1786 implement mappers - let mappedDataModel = dataModel - return mappedDataModel + // TODO: RUMM-1786 implement mutating session_id & application_id + return dataModel } private func write(_ model: T) { diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift index 080f9b7eca..d2c8572312 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift @@ -129,13 +129,15 @@ class WebLogEventConsumerTests: XCTestCase { "status": "error", "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] ] + var expectedWebLogEvent: JSON = webLogEvent + expectedWebLogEvent["ddtags"] = "version:,env:" try eventConsumer.consume(event: webLogEvent, eventType: "log") let data = try JSONEncoder().encode(mockUserLogsWriter.dataWritten as? CodableValue) let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) - AssertDictionariesEqual(writtenJSON, webLogEvent) + AssertDictionariesEqual(writtenJSON, expectedWebLogEvent) XCTAssertNil(mockInternalLogsWriter.dataWritten) } diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift new file mode 100644 index 0000000000..8357572f08 --- /dev/null +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift @@ -0,0 +1,219 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2020 Datadog, Inc. + */ + +import XCTest +@testable import Datadog + +// TODO: RUMM-1786 test mutations (session_id, application_id, date) +class WebRUMEventConsumerTests: XCTestCase { + let mockWriter = FileWriterMock() + let mockDateCorrector = DateCorrectorMock() + let mockContextProvider = RUMContextProviderMock(context: .mockWith(rumApplicationID: "123456")) + + private func buildWebRUMViewEvent() -> JSON { + return [ + "application": ["id": "xxx"], + "date": 1_635_933_113_708, + "service": "super", + "session": ["id": "0110cab4-7471-480e-aa4e-7ce039ced355", "type": "user"], + "type": "view", + "view": [ + "action": ["count": 0], + "cumulative_layout_shift": 0, + "dom_complete": 152_800_000, + "dom_content_loaded": 118_300_000, + "dom_interactive": 116_400_000, + "error": ["count": 0], + "first_contentful_paint": 121_300_000, + "id": "64308fd4-83f9-48cb-b3e1-1e91f6721230", + "in_foreground_periods": [], + "is_active": true, + "largest_contentful_paint": 121_299_000, + "load_event": 152_800_000, + "loading_time": 152_800_000, + "loading_type": "initial_load", + "long_task": ["count": 0], + "referrer": "", + "resource": ["count": 3], + "time_spent": 3_120_000_000, + "url": "http://localhost:8080/test.html" + ], + "_dd": [ + "document_version": 2, + "drift": 0, + "format_version": 2, + "session": ["plan": 2] + ] + ] + } + + func testWhenValidWebRUMViewEventPassedWithWrongEventType_itThrowsError() throws { + let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! + mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) + mockDateCorrector.correctionOffset = 123 + let eventConsumer = WebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) + + let webRUMViewEvent = buildWebRUMViewEvent() + + XCTAssertThrowsError(try eventConsumer.consume(event: webRUMViewEvent, eventType: "action")) + } + + func testWhenValidWebRUMViewEventPassed_itDecoratesAndPassesToWriter() throws { + let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! + mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) + mockDateCorrector.correctionOffset = 123 + let eventConsumer = WebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) + + let webRUMViewEvent = buildWebRUMViewEvent() + try eventConsumer.consume(event: webRUMViewEvent, eventType: "view") + + let writtenRUMViewEvent = try XCTUnwrap(mockWriter.dataWritten as? RUMViewEvent) + XCTAssertEqual(writtenRUMViewEvent.view.id, "64308fd4-83f9-48cb-b3e1-1e91f6721230") + XCTAssertEqual(writtenRUMViewEvent.view.loadingTime, 152_800_000) + } + + func testWhenValidWebRUMActionEventPassed_itDecoratesAndPassesToWriter() throws { + let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! + mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) + mockDateCorrector.correctionOffset = 123 + let eventConsumer = WebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) + + let webRUMActionEvent: JSON = [ + "_dd": [ + "format_version": 2, + "drift": 1, + "session": ["plan": 2], + "browser_sdk_version": "3.10.1" + ], + "application": ["id": "75d50c62-8b66-403c-a453-aaa1c44d64bd"], + "date": 1_640_252_823_292, + "service": "shopist-web-ui", + "session": ["id": "00000000-aaaa-0000-aaaa-000000000000", "type": "user", "has_replay": true], + "view": [ + "url": "https://foo.bar/department/chairs/product/2", + "referrer": "https://foo.bar/department/chairs", + "id": "00413060-599f-4a77-80de-5d3beab3da2e", + "in_foreground": true + ], + "action": [ + "id": "e73c32c2-e748-4873-b621-debd7f674c0d", + "target": ["name": "ADD TO CART"], + "type": "click", + "error": ["count": 0], + "loading_time": 5_000_000, + "long_task": ["count": 0], + "resource": ["count": 0] + ], + "type": "action" + ] + + try eventConsumer.consume(event: webRUMActionEvent, eventType: "action") + + let writtenRUMActionEvent = try XCTUnwrap(mockWriter.dataWritten as? RUMActionEvent) + XCTAssertEqual(writtenRUMActionEvent.view.id, "00413060-599f-4a77-80de-5d3beab3da2e") + } + + func testWhenValidWebRUMResourceEventPassed_itDecoratesAndPassesToWriter() throws { + let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! + mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) + mockDateCorrector.correctionOffset = 123 + let eventConsumer = WebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) + + let webRUMResourceEvent: JSON = [ + "_dd": [ + "format_version": 2, + "drift": 0, + "session": ["plan": 2], + "browser_sdk_version": "3.10.1" + ], + "application": ["id": "75d50c62-8b66-403c-a453-aaa1c44d64bd"], + "date": 1_640_252_561_077, + "service": "shopist-web-ui", + "session": [ + "id": "00000000-aaaa-0000-aaaa-000000000000", + "type": "user", + "has_replay": true + ], + "view": [ + "url": "https://foo.bar/", + "referrer": "", + "id": "2aac5419-a626-4098-b1b5-39154f8ea8f3" + ], + "resource": [ + "id": "6df22efd-78b4-454f-b44f-3ac8846e5311", + "type": "image", + "url": "https://foo.bar/_nuxt/img/bedding.8af1600.jpg", + "duration": 369_000_000, + "download": ["duration": 351_000_000, "start": 18_000_000], + "first_byte": ["duration": 17_000_000, "start": 1_000_000] + ], + "type": "resource" + ] + + try eventConsumer.consume(event: webRUMResourceEvent, eventType: "resource") + + let writtenRUMResourceEvent = try XCTUnwrap(mockWriter.dataWritten as? RUMResourceEvent) + XCTAssertEqual(writtenRUMResourceEvent.view.id, "2aac5419-a626-4098-b1b5-39154f8ea8f3") + } + + func testWhenValidWebRUMErrorEventPassed_itDecoratesAndPassesToWriter() throws { + let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! + mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) + mockDateCorrector.correctionOffset = 123 + let eventConsumer = WebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) + + let webRUMErrorEvent: JSON = [ + "_dd": [ + "format_version": 2, + "drift": 0, + "session": ["plan": 2], + "browser_sdk_version": "3.10.1" + ], + "application": ["id": "75d50c62-8b66-403c-a453-aaa1c44d64bd"], + "date": 1_640_252_666_129, + "service": "shopist-web-ui", + "session": [ + "id": "00000000-aaaa-0000-aaaa-000000000000", + "type": "user", + "has_replay": true + ], + "view": [ + "url": "https://foo.bar/department/chairs/product/2", + "referrer": "https://foo.bar/department/chairs", + "id": "00413060-599f-4a77-80de-5d3beab3da2e", + "in_foreground": true + ], + "error": [ + "id": "3de88670-be12-4a30-91c8-378f8ccb8a75", + "message": "Provided [\"type\":\"network error\",\"status\":404]", + "source": "custom", + "stack": "No stack, consider using an instance of Error", + "handling_stack": "Error: \n at @ https://foo.bar/_nuxt/app.30bb4c9.js:2:47460\n at promiseReactionJob @ [native code]", + "handling": "handled" + ], + "type": "error" + ] + + try eventConsumer.consume(event: webRUMErrorEvent, eventType: "error") + + let writtenRUMErrorEvent = try XCTUnwrap(mockWriter.dataWritten as? RUMErrorEvent) + XCTAssertEqual(writtenRUMErrorEvent.view.id, "00413060-599f-4a77-80de-5d3beab3da2e") + } + + func testWhenInvalidEventTypeIsPassed_itLogsToUserLogger() throws { + let previousUserLogger = userLogger + defer { userLogger = previousUserLogger } + let output = LogOutputMock() + userLogger = .mockWith(logOutput: output) + + let eventConsumer = WebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) + try eventConsumer.consume(event: [:], eventType: "unknown_event_type") + + XCTAssertEqual(output.recordedLog?.status, .error) + let userLogMessage = try XCTUnwrap(output.recordedLog?.message) + XCTAssertEqual(userLogMessage, "🔥 Web RUM Event Error - Unknown event type: unknown_event_type") + } +} From e10f099f97e0fb64987d42d71466fa12f7f6b2e2 Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Tue, 28 Dec 2021 14:55:20 +0100 Subject: [PATCH 12/33] RUMM-1791 PR comments addressed --- .../WKUserContentController+Datadog.swift | 4 ++- .../WebView/WebLogEventConsumer.swift | 15 ++++++-- .../WebView/WebLogEventConsumerTests.swift | 34 +++++++++++++++---- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift index d163669268..cc8ed0531b 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift @@ -24,7 +24,9 @@ public extension WKUserContentController { userLogsWriter: loggingFeature.storage.writer, internalLogsWriter: InternalMonitoringFeature.instance?.logsStorage.writer, dateCorrector: loggingFeature.dateCorrector, - rumContextProvider: contextProvider + rumContextProvider: contextProvider, + applicationVersion: loggingFeature.configuration.common.applicationVersion, + environment: loggingFeature.configuration.common.environment ) } diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift index 0c3839f292..ea6213dc49 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift @@ -21,13 +21,16 @@ internal class WebLogEventConsumer: WebEventConsumer { private let internalLogsWriter: Writer? private let dateCorrector: DateCorrectorType private let rumContextProvider: RUMContextProvider? + private let applicationVersion: String + private let environment: String + private let jsonDecoder = JSONDecoder() private lazy var ddTags: String = { let versionKey = LogEventEncoder.StaticCodingKeys.applicationVersion.rawValue - let versionValue = LoggingFeature.instance?.configuration.common.applicationVersion ?? "" + let versionValue = applicationVersion let envKey = LogEventEncoder.StaticCodingKeys.environment.rawValue - let envValue = LoggingFeature.instance?.configuration.common.environment ?? "" + let envValue = environment return "\(versionKey):\(versionValue),\(envKey):\(envValue)" }() @@ -36,12 +39,16 @@ internal class WebLogEventConsumer: WebEventConsumer { userLogsWriter: Writer, internalLogsWriter: Writer?, dateCorrector: DateCorrectorType, - rumContextProvider: RUMContextProvider? + rumContextProvider: RUMContextProvider?, + applicationVersion: String, + environment: String ) { self.userLogsWriter = userLogsWriter self.internalLogsWriter = internalLogsWriter self.dateCorrector = dateCorrector self.rumContextProvider = rumContextProvider + self.applicationVersion = applicationVersion + self.environment = environment } func consume(event: JSON, eventType: String) throws { @@ -71,6 +78,8 @@ internal class WebLogEventConsumer: WebEventConsumer { userLogsWriter.write(value: encodableEvent) } else if eventType == Constants.internalLogEventType { internalLogsWriter?.write(value: encodableEvent) + } else { + userLogger.error("🔥 Invalid Web Event Type: \(eventType)") } } } diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift index d2c8572312..006ee76704 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift @@ -17,11 +17,15 @@ class WebLogEventConsumerTests: XCTestCase { let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) mockDateCorrector.correctionOffset = 123 + let applicationVersion = String.mockRandom() + let environment = String.mockRandom() let eventConsumer = WebLogEventConsumer( userLogsWriter: mockUserLogsWriter, internalLogsWriter: mockInternalLogsWriter, dateCorrector: mockDateCorrector, - rumContextProvider: mockContextProvider + rumContextProvider: mockContextProvider, + applicationVersion: applicationVersion, + environment: environment ) let webLogEvent: JSON = [ @@ -39,7 +43,7 @@ class WebLogEventConsumerTests: XCTestCase { "application_id": "123456", "session_id": mockSessionID.uuidString.lowercased(), "status": "error", - "ddtags": "version:,env:", + "ddtags": "version:\(applicationVersion),env:\(environment)", "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] ] @@ -57,11 +61,15 @@ class WebLogEventConsumerTests: XCTestCase { let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) mockDateCorrector.correctionOffset = 123 + let applicationVersion = String.mockRandom() + let environment = String.mockRandom() let eventConsumer = WebLogEventConsumer( userLogsWriter: mockUserLogsWriter, internalLogsWriter: mockInternalLogsWriter, dateCorrector: mockDateCorrector, - rumContextProvider: mockContextProvider + rumContextProvider: mockContextProvider, + applicationVersion: applicationVersion, + environment: environment ) let webLogEvent: JSON = [ @@ -79,7 +87,7 @@ class WebLogEventConsumerTests: XCTestCase { "application_id": "123456", "session_id": mockSessionID.uuidString.lowercased(), "status": "error", - "ddtags": "version:,env:", + "ddtags": "version:\(applicationVersion),env:\(environment)", "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] ] @@ -94,11 +102,18 @@ class WebLogEventConsumerTests: XCTestCase { } func testWhenInvalidEventTypePassed_itIgnoresEvent() throws { + let previousUserLogger = userLogger + defer { userLogger = previousUserLogger } + let userLoggerOutput = LogOutputMock() + userLogger = .mockWith(logOutput: userLoggerOutput) + let eventConsumer = WebLogEventConsumer( userLogsWriter: mockUserLogsWriter, internalLogsWriter: mockInternalLogsWriter, dateCorrector: mockDateCorrector, - rumContextProvider: mockContextProvider + rumContextProvider: mockContextProvider, + applicationVersion: .mockRandom(), + environment: .mockRandom() ) let webLogEvent: JSON = [ @@ -111,14 +126,19 @@ class WebLogEventConsumerTests: XCTestCase { XCTAssertNil(mockUserLogsWriter.dataWritten) XCTAssertNil(mockInternalLogsWriter.dataWritten) + XCTAssertEqual(userLoggerOutput.recordedLog?.message, "🔥 Invalid Web Event Type: invalid_log") } func testWhenContextIsUnavailable_itPassesEventAsIs() throws { + let applicationVersion = String.mockRandom() + let environment = String.mockRandom() let eventConsumer = WebLogEventConsumer( userLogsWriter: mockUserLogsWriter, internalLogsWriter: mockInternalLogsWriter, dateCorrector: mockDateCorrector, - rumContextProvider: nil + rumContextProvider: nil, + applicationVersion: applicationVersion, + environment: environment ) let webLogEvent: JSON = [ @@ -130,7 +150,7 @@ class WebLogEventConsumerTests: XCTestCase { "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] ] var expectedWebLogEvent: JSON = webLogEvent - expectedWebLogEvent["ddtags"] = "version:,env:" + expectedWebLogEvent["ddtags"] = "version:\(applicationVersion),env:\(environment)" try eventConsumer.consume(event: webLogEvent, eventType: "log") From a05bc9a96371f5f951f1a9756fcdde52a1f47ceb Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Wed, 29 Dec 2021 16:05:20 +0100 Subject: [PATCH 13/33] RUMM-1791 WebRUMEvents manipulated as JSON --- .../WebView/WebEventBridge.swift | 16 +++- .../WebView/WebRUMEventConsumer.swift | 83 +++++++++++-------- 2 files changed, 60 insertions(+), 39 deletions(-) diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift index 994f153ec7..e75a752491 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift @@ -24,6 +24,7 @@ internal class WebEventBridge { static let eventTypeKey = "eventType" static let eventKey = "event" static let eventTypeLog = "log" + static let eventTypeInternalLog = "internal_log" } private let logEventConsumer: WebEventConsumer? @@ -46,10 +47,19 @@ internal class WebEventBridge { throw WebEventError.missingKey(key: Constants.eventKey) } - if eventType == Constants.eventTypeLog { - try logEventConsumer?.consume(event: wrappedEvent, eventType: eventType) + if eventType == Constants.eventTypeLog || + eventType == Constants.eventTypeInternalLog { + if let consumer = logEventConsumer { + try consumer.consume(event: wrappedEvent, eventType: eventType) + } else { + userLogger.warn("A WebView log is lost because Logging is disabled in iOS SDK") + } } else { - try rumEventConsumer?.consume(event: wrappedEvent, eventType: eventType) + if let consumer = rumEventConsumer { + try consumer.consume(event: wrappedEvent, eventType: eventType) + } else { + userLogger.warn("A WebView RUM event is lost because RUM is disabled in iOS SDK") + } } } diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift index 5fda40e0b9..7530cd1ca2 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift @@ -11,6 +11,8 @@ internal class WebRUMEventConsumer: WebEventConsumer { private let dateCorrector: DateCorrectorType private let contextProvider: RUMContextProvider? + private let jsonDecoder = JSONDecoder() + init( dataWriter: Writer, dateCorrector: DateCorrectorType, @@ -22,59 +24,68 @@ internal class WebRUMEventConsumer: WebEventConsumer { } func consume(event: JSON, eventType: String) throws { - let eventData = try JSONSerialization.data(withJSONObject: event, options: []) - let jsonDecoder = JSONDecoder() let rumContext = contextProvider?.context + let mappedEvent = map(event: event, with: rumContext) - switch eventType { - case "view": - let viewEvent = try jsonDecoder.decode(RUMViewEvent.self, from: eventData) - let mappedViewEvent = mapIfNeeded(dataModel: viewEvent, context: rumContext, offset: getOffset(viewID: viewEvent.view.id)) - write(mappedViewEvent) - case "action": - let actionEvent = try jsonDecoder.decode(RUMActionEvent.self, from: eventData) - let mappedActionEvent = mapIfNeeded(dataModel: actionEvent, context: rumContext, offset: getOffset(viewID: actionEvent.view.id)) - write(mappedActionEvent) - case "resource": - let resourceEvent = try jsonDecoder.decode(RUMResourceEvent.self, from: eventData) - let mappedResourceEvent = mapIfNeeded(dataModel: resourceEvent, context: rumContext, offset: getOffset(viewID: resourceEvent.view.id)) - write(mappedResourceEvent) - case "error": - let errorEvent = try jsonDecoder.decode(RUMErrorEvent.self, from: eventData) - let mappedErrorEvent = mapIfNeeded(dataModel: errorEvent, context: rumContext, offset: getOffset(viewID: errorEvent.view.id)) - write(mappedErrorEvent) - case "long_task": - let longTaskEvent = try jsonDecoder.decode(RUMLongTaskEvent.self, from: eventData) - let mappedLongTaskEvent = mapIfNeeded(dataModel: longTaskEvent, context: rumContext, offset: getOffset(viewID: longTaskEvent.view.id)) - write(mappedLongTaskEvent) - default: - userLogger.error("🔥 Web RUM Event Error - Unknown event type: \(eventType)") - } - } + let jsonData = try JSONSerialization.data(withJSONObject: mappedEvent, options: []) + let encodableEvent = try jsonDecoder.decode(CodableValue.self, from: jsonData) - private func mapIfNeeded(dataModel: T, context: RUMContext?, offset: Offset) -> T { - // TODO: RUMM-1786 implement mutating session_id & application_id - return dataModel + dataWriter.write(value: encodableEvent) } - private func write(_ model: T) { - dataWriter.write(value: model) + private func map(event: JSON, with context: RUMContext?) -> JSON { + guard let context = context, + context.sessionID != .nullUUID else { + return event + } + + var mutableEvent = event + + if let date = mutableEvent["date"] as? Int { + let viewID = (mutableEvent["view"] as? JSON)?["id"] as? String + let serverTimeOffsetInNs = getOffset(viewID: viewID) + let correctedDate = Int64(date) + serverTimeOffsetInNs + mutableEvent["date"] = correctedDate + } + + if let context = contextProvider?.context { + if var application = mutableEvent["application"] as? JSON { + application["id"] = context.rumApplicationID + mutableEvent["application"] = application + } + if var session = mutableEvent["session"] as? JSON { + session["id"] = context.sessionID.toRUMDataFormat + mutableEvent["session"] = session + } + } + + if var dd = mutableEvent["_dd"] as? JSON, + var dd_sesion = dd["session"] as? [String: Int] { + dd_sesion["plan"] = 1 + dd["session"] = dd_sesion + mutableEvent["_dd"] = dd + } + + return mutableEvent } // MARK: - Time offsets - private typealias Offset = TimeInterval + private typealias Offset = Int64 private typealias ViewIDOffsetPair = (viewID: String, offset: Offset) private var viewIDOffsetPairs = [ViewIDOffsetPair]() - private func getOffset(viewID: String) -> Offset { - purgeOffsets() + private func getOffset(viewID: String?) -> Offset { + guard let viewID = viewID else { + return 0 + } + purgeOffsets() let found = viewIDOffsetPairs.first { $0.viewID == viewID } if let found = found { return found.offset } - let offset = dateCorrector.currentCorrection.serverTimeOffset + let offset = dateCorrector.currentCorrection.serverTimeOffset.toInt64Nanoseconds viewIDOffsetPairs.insert((viewID: viewID, offset: offset), at: 0) return offset } From 29bf40b7fb3136654a3164551509f8bc6914fa72 Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Thu, 30 Dec 2021 13:40:00 +0100 Subject: [PATCH 14/33] RUMM-1791 WebRUMEventConsumer made more flexible WebRUMEvents are decorated as raw JSONs instead of serializing them into RUMDataModels --- .../WKUserContentController+Datadog.swift | 8 +- .../WebView/WebEventBridge.swift | 21 +- .../WebView/WebLogEventConsumer.swift | 10 +- .../WebView/WebRUMEventConsumer.swift | 4 +- .../RUM/WebView/WebEventBridgeTests.swift | 47 ++-- .../WebView/WebLogEventConsumerTests.swift | 40 +--- .../WebView/WebRUMEventConsumerTests.swift | 216 +++++------------- 7 files changed, 112 insertions(+), 234 deletions(-) diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift index cc8ed0531b..8d9a188f3a 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift @@ -18,9 +18,9 @@ public extension WKUserContentController { let contextProvider = (Global.rum as? RUMMonitor)?.contextProvider - var logEventConsumer: WebLogEventConsumer? = nil + var logEventConsumer: DefaultWebLogEventConsumer? = nil if let loggingFeature = LoggingFeature.instance { - logEventConsumer = WebLogEventConsumer( + logEventConsumer = DefaultWebLogEventConsumer( userLogsWriter: loggingFeature.storage.writer, internalLogsWriter: InternalMonitoringFeature.instance?.logsStorage.writer, dateCorrector: loggingFeature.dateCorrector, @@ -30,9 +30,9 @@ public extension WKUserContentController { ) } - var rumEventConsumer: WebRUMEventConsumer? = nil + var rumEventConsumer: DefaultWebRUMEventConsumer? = nil if let rumFeature = RUMFeature.instance { - rumEventConsumer = WebRUMEventConsumer( + rumEventConsumer = DefaultWebRUMEventConsumer( dataWriter: rumFeature.storage.writer, dateCorrector: rumFeature.dateCorrector, contextProvider: contextProvider diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift index e75a752491..6be1dd93a0 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift @@ -8,8 +8,12 @@ import Foundation internal typealias JSON = [String: Any] -internal protocol WebEventConsumer { - func consume(event: JSON, eventType: String) throws +internal protocol WebLogEventConsumer { + func consume(event: JSON, internalLog: Bool) throws +} + +internal protocol WebRUMEventConsumer { + func consume(event: JSON) throws } internal enum WebEventError: Error, Equatable { @@ -27,10 +31,10 @@ internal class WebEventBridge { static let eventTypeInternalLog = "internal_log" } - private let logEventConsumer: WebEventConsumer? - private let rumEventConsumer: WebEventConsumer? + private let logEventConsumer: WebLogEventConsumer? + private let rumEventConsumer: WebRUMEventConsumer? - init(logEventConsumer: WebEventConsumer?, rumEventConsumer: WebEventConsumer?) { + init(logEventConsumer: WebLogEventConsumer?, rumEventConsumer: WebRUMEventConsumer?) { self.logEventConsumer = logEventConsumer self.rumEventConsumer = rumEventConsumer } @@ -50,13 +54,16 @@ internal class WebEventBridge { if eventType == Constants.eventTypeLog || eventType == Constants.eventTypeInternalLog { if let consumer = logEventConsumer { - try consumer.consume(event: wrappedEvent, eventType: eventType) + try consumer.consume( + event: wrappedEvent, + internalLog: (eventType == Constants.eventTypeInternalLog) + ) } else { userLogger.warn("A WebView log is lost because Logging is disabled in iOS SDK") } } else { if let consumer = rumEventConsumer { - try consumer.consume(event: wrappedEvent, eventType: eventType) + try consumer.consume(event: wrappedEvent) } else { userLogger.warn("A WebView RUM event is lost because RUM is disabled in iOS SDK") } diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift index ea6213dc49..53902f06e0 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift @@ -6,7 +6,7 @@ import Foundation -internal class WebLogEventConsumer: WebEventConsumer { +internal class DefaultWebLogEventConsumer: WebLogEventConsumer { private struct Constants { static let logEventType = "log" static let internalLogEventType = "internal_log" @@ -51,7 +51,7 @@ internal class WebLogEventConsumer: WebEventConsumer { self.environment = environment } - func consume(event: JSON, eventType: String) throws { + func consume(event: JSON, internalLog: Bool) throws { var mutableEvent = event if let existingTags = mutableEvent[Constants.ddTagsKey] as? String, !existingTags.isEmpty { @@ -74,12 +74,10 @@ internal class WebLogEventConsumer: WebEventConsumer { let jsonData = try JSONSerialization.data(withJSONObject: mutableEvent, options: []) let encodableEvent = try jsonDecoder.decode(CodableValue.self, from: jsonData) - if eventType == Constants.logEventType { - userLogsWriter.write(value: encodableEvent) - } else if eventType == Constants.internalLogEventType { + if internalLog { internalLogsWriter?.write(value: encodableEvent) } else { - userLogger.error("🔥 Invalid Web Event Type: \(eventType)") + userLogsWriter.write(value: encodableEvent) } } } diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift index 7530cd1ca2..cdfcff6b01 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift @@ -6,7 +6,7 @@ import Foundation -internal class WebRUMEventConsumer: WebEventConsumer { +internal class DefaultWebRUMEventConsumer: WebRUMEventConsumer { private let dataWriter: Writer private let dateCorrector: DateCorrectorType private let contextProvider: RUMContextProvider? @@ -23,7 +23,7 @@ internal class WebRUMEventConsumer: WebEventConsumer { self.contextProvider = contextProvider } - func consume(event: JSON, eventType: String) throws { + func consume(event: JSON) throws { let rumContext = contextProvider?.context let mappedEvent = map(event: event, with: rumContext) diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WebEventBridgeTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WebEventBridgeTests.swift index 3ffde28db5..757f96db3f 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/WebEventBridgeTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WebEventBridgeTests.swift @@ -7,11 +7,21 @@ import XCTest @testable import Datadog -fileprivate class MockEventConsumer: WebEventConsumer { - private(set) var consumedEvents: [(event: JSON, eventType: String)] = [] +fileprivate class MockEventConsumer: WebLogEventConsumer, WebRUMEventConsumer { + private(set) var consumedLogEvents: [JSON] = [] + private(set) var consumedInternalLogEvents: [JSON] = [] + private(set) var consumedRUMEvents: [JSON] = [] - func consume(event: JSON, eventType: String) { - consumedEvents.append((event: event, eventType: eventType)) + func consume(event: JSON, internalLog: Bool) throws { + if internalLog { + consumedInternalLogEvents.append(event) + } else { + consumedLogEvents.append(event) + } + } + + func consume(event: JSON) throws { + consumedRUMEvents.append(event) } } @@ -58,13 +68,16 @@ class WebEventBridgeTests: XCTestCase { """ try eventBridge.consume(messageLog) - XCTAssertEqual(mockLogEventConsumer.consumedEvents.count, 1) - XCTAssertEqual(mockRUMEventConsumer.consumedEvents.count, 0) + XCTAssertEqual(mockLogEventConsumer.consumedLogEvents.count, 1) + XCTAssertEqual(mockLogEventConsumer.consumedInternalLogEvents.count, 0) + XCTAssertEqual(mockLogEventConsumer.consumedRUMEvents.count, 0) + XCTAssertEqual(mockRUMEventConsumer.consumedLogEvents.count, 0) + XCTAssertEqual(mockRUMEventConsumer.consumedInternalLogEvents.count, 0) + XCTAssertEqual(mockRUMEventConsumer.consumedRUMEvents.count, 0) - let consumedEvent = try XCTUnwrap(mockLogEventConsumer.consumedEvents.first) - XCTAssertEqual(consumedEvent.eventType, "log") - XCTAssertEqual(consumedEvent.event["session_id"] as? String, "0110cab4-7471-480e-aa4e-7ce039ced355") - XCTAssertEqual((consumedEvent.event["view"] as? JSON)?["url"] as? String, "https://datadoghq.dev/browser-sdk-test-playground") + let consumedEvent = try XCTUnwrap(mockLogEventConsumer.consumedLogEvents.first) + XCTAssertEqual(consumedEvent["session_id"] as? String, "0110cab4-7471-480e-aa4e-7ce039ced355") + XCTAssertEqual((consumedEvent["view"] as? JSON)?["url"] as? String, "https://datadoghq.dev/browser-sdk-test-playground") } func testWhenEventTypeIsNonLog_itGoesToRUMEventConsumer() throws { @@ -73,12 +86,14 @@ class WebEventBridgeTests: XCTestCase { """ try eventBridge.consume(messageRUM) - XCTAssertEqual(mockLogEventConsumer.consumedEvents.count, 0) - XCTAssertEqual(mockRUMEventConsumer.consumedEvents.count, 1) + XCTAssertEqual( + mockLogEventConsumer.consumedLogEvents.count + mockLogEventConsumer.consumedInternalLogEvents.count, + 0 + ) + XCTAssertEqual(mockRUMEventConsumer.consumedRUMEvents.count, 1) - let consumedEvent = try XCTUnwrap(mockRUMEventConsumer.consumedEvents.first) - XCTAssertEqual(consumedEvent.eventType, "view") - XCTAssertEqual((consumedEvent.event["session"] as? JSON)?["id"] as? String, "0110cab4-7471-480e-aa4e-7ce039ced355") - XCTAssertEqual((consumedEvent.event["view"] as? JSON)?["url"] as? String, "http://localhost:8080/test.html") + let consumedEvent = try XCTUnwrap(mockRUMEventConsumer.consumedRUMEvents.first) + XCTAssertEqual((consumedEvent["session"] as? JSON)?["id"] as? String, "0110cab4-7471-480e-aa4e-7ce039ced355") + XCTAssertEqual((consumedEvent["view"] as? JSON)?["url"] as? String, "http://localhost:8080/test.html") } } diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift index 006ee76704..be1c9ea46e 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift @@ -19,7 +19,7 @@ class WebLogEventConsumerTests: XCTestCase { mockDateCorrector.correctionOffset = 123 let applicationVersion = String.mockRandom() let environment = String.mockRandom() - let eventConsumer = WebLogEventConsumer( + let eventConsumer = DefaultWebLogEventConsumer( userLogsWriter: mockUserLogsWriter, internalLogsWriter: mockInternalLogsWriter, dateCorrector: mockDateCorrector, @@ -47,7 +47,7 @@ class WebLogEventConsumerTests: XCTestCase { "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] ] - try eventConsumer.consume(event: webLogEvent, eventType: "log") + try eventConsumer.consume(event: webLogEvent, internalLog: false) let data = try JSONEncoder().encode(mockUserLogsWriter.dataWritten as? CodableValue) let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) @@ -63,7 +63,7 @@ class WebLogEventConsumerTests: XCTestCase { mockDateCorrector.correctionOffset = 123 let applicationVersion = String.mockRandom() let environment = String.mockRandom() - let eventConsumer = WebLogEventConsumer( + let eventConsumer = DefaultWebLogEventConsumer( userLogsWriter: mockUserLogsWriter, internalLogsWriter: mockInternalLogsWriter, dateCorrector: mockDateCorrector, @@ -91,7 +91,7 @@ class WebLogEventConsumerTests: XCTestCase { "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] ] - try eventConsumer.consume(event: webLogEvent, eventType: "internal_log") + try eventConsumer.consume(event: webLogEvent, internalLog: true) let data = try JSONEncoder().encode(mockInternalLogsWriter.dataWritten as? CodableValue) let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) @@ -101,38 +101,10 @@ class WebLogEventConsumerTests: XCTestCase { XCTAssertNil(mockUserLogsWriter.dataWritten) } - func testWhenInvalidEventTypePassed_itIgnoresEvent() throws { - let previousUserLogger = userLogger - defer { userLogger = previousUserLogger } - let userLoggerOutput = LogOutputMock() - userLogger = .mockWith(logOutput: userLoggerOutput) - - let eventConsumer = WebLogEventConsumer( - userLogsWriter: mockUserLogsWriter, - internalLogsWriter: mockInternalLogsWriter, - dateCorrector: mockDateCorrector, - rumContextProvider: mockContextProvider, - applicationVersion: .mockRandom(), - environment: .mockRandom() - ) - - let webLogEvent: JSON = [ - "date": 1_635_932_927_012, - "message": "console error: error", - "status": "error" - ] - - try eventConsumer.consume(event: webLogEvent, eventType: "invalid_log") - - XCTAssertNil(mockUserLogsWriter.dataWritten) - XCTAssertNil(mockInternalLogsWriter.dataWritten) - XCTAssertEqual(userLoggerOutput.recordedLog?.message, "🔥 Invalid Web Event Type: invalid_log") - } - func testWhenContextIsUnavailable_itPassesEventAsIs() throws { let applicationVersion = String.mockRandom() let environment = String.mockRandom() - let eventConsumer = WebLogEventConsumer( + let eventConsumer = DefaultWebLogEventConsumer( userLogsWriter: mockUserLogsWriter, internalLogsWriter: mockInternalLogsWriter, dateCorrector: mockDateCorrector, @@ -152,7 +124,7 @@ class WebLogEventConsumerTests: XCTestCase { var expectedWebLogEvent: JSON = webLogEvent expectedWebLogEvent["ddtags"] = "version:\(applicationVersion),env:\(environment)" - try eventConsumer.consume(event: webLogEvent, eventType: "log") + try eventConsumer.consume(event: webLogEvent, internalLog: false) let data = try JSONEncoder().encode(mockUserLogsWriter.dataWritten as? CodableValue) let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift index 8357572f08..3ab570ddb5 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift @@ -13,207 +13,93 @@ class WebRUMEventConsumerTests: XCTestCase { let mockDateCorrector = DateCorrectorMock() let mockContextProvider = RUMContextProviderMock(context: .mockWith(rumApplicationID: "123456")) - private func buildWebRUMViewEvent() -> JSON { - return [ - "application": ["id": "xxx"], - "date": 1_635_933_113_708, - "service": "super", - "session": ["id": "0110cab4-7471-480e-aa4e-7ce039ced355", "type": "user"], - "type": "view", - "view": [ - "action": ["count": 0], - "cumulative_layout_shift": 0, - "dom_complete": 152_800_000, - "dom_content_loaded": 118_300_000, - "dom_interactive": 116_400_000, - "error": ["count": 0], - "first_contentful_paint": 121_300_000, - "id": "64308fd4-83f9-48cb-b3e1-1e91f6721230", - "in_foreground_periods": [], - "is_active": true, - "largest_contentful_paint": 121_299_000, - "load_event": 152_800_000, - "loading_time": 152_800_000, - "loading_type": "initial_load", - "long_task": ["count": 0], - "referrer": "", - "resource": ["count": 3], - "time_spent": 3_120_000_000, - "url": "http://localhost:8080/test.html" - ], - "_dd": [ - "document_version": 2, - "drift": 0, - "format_version": 2, - "session": ["plan": 2] - ] - ] - } - - func testWhenValidWebRUMViewEventPassedWithWrongEventType_itThrowsError() throws { - let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! - mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) - mockDateCorrector.correctionOffset = 123 - let eventConsumer = WebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) - - let webRUMViewEvent = buildWebRUMViewEvent() - - XCTAssertThrowsError(try eventConsumer.consume(event: webRUMViewEvent, eventType: "action")) - } - - func testWhenValidWebRUMViewEventPassed_itDecoratesAndPassesToWriter() throws { - let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! - mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) - mockDateCorrector.correctionOffset = 123 - let eventConsumer = WebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) - - let webRUMViewEvent = buildWebRUMViewEvent() - try eventConsumer.consume(event: webRUMViewEvent, eventType: "view") - - let writtenRUMViewEvent = try XCTUnwrap(mockWriter.dataWritten as? RUMViewEvent) - XCTAssertEqual(writtenRUMViewEvent.view.id, "64308fd4-83f9-48cb-b3e1-1e91f6721230") - XCTAssertEqual(writtenRUMViewEvent.view.loadingTime, 152_800_000) - } - - func testWhenValidWebRUMActionEventPassed_itDecoratesAndPassesToWriter() throws { + func testWhenValidWebRUMEventPassed_itDecoratesAndPassesToWriter() throws { let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) mockDateCorrector.correctionOffset = 123 - let eventConsumer = WebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) + let eventConsumer = DefaultWebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) - let webRUMActionEvent: JSON = [ + let webRUMEvent: JSON = [ "_dd": [ - "format_version": 2, - "drift": 1, - "session": ["plan": 2], - "browser_sdk_version": "3.10.1" + "session": ["plan": 2] ], "application": ["id": "75d50c62-8b66-403c-a453-aaa1c44d64bd"], "date": 1_640_252_823_292, "service": "shopist-web-ui", - "session": ["id": "00000000-aaaa-0000-aaaa-000000000000", "type": "user", "has_replay": true], + "session": ["id": "00000000-aaaa-0000-aaaa-000000000000"], "view": [ - "url": "https://foo.bar/department/chairs/product/2", - "referrer": "https://foo.bar/department/chairs", - "id": "00413060-599f-4a77-80de-5d3beab3da2e", - "in_foreground": true - ], - "action": [ - "id": "e73c32c2-e748-4873-b621-debd7f674c0d", - "target": ["name": "ADD TO CART"], - "type": "click", - "error": ["count": 0], - "loading_time": 5_000_000, - "long_task": ["count": 0], - "resource": ["count": 0] + "id": "00413060-599f-4a77-80de-5d3beab3da2e" ], "type": "action" ] - - try eventConsumer.consume(event: webRUMActionEvent, eventType: "action") - - let writtenRUMActionEvent = try XCTUnwrap(mockWriter.dataWritten as? RUMActionEvent) - XCTAssertEqual(writtenRUMActionEvent.view.id, "00413060-599f-4a77-80de-5d3beab3da2e") - } - - func testWhenValidWebRUMResourceEventPassed_itDecoratesAndPassesToWriter() throws { - let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! - mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) - mockDateCorrector.correctionOffset = 123 - let eventConsumer = WebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) - - let webRUMResourceEvent: JSON = [ + let expectedWebRUMEvent: JSON = [ "_dd": [ - "format_version": 2, - "drift": 0, - "session": ["plan": 2], - "browser_sdk_version": "3.10.1" + "session": ["plan": 1] ], - "application": ["id": "75d50c62-8b66-403c-a453-aaa1c44d64bd"], - "date": 1_640_252_561_077, + "application": ["id": mockContextProvider.context.rumApplicationID], + "date": 1_640_252_823_292 + 123.toInt64Nanoseconds, "service": "shopist-web-ui", - "session": [ - "id": "00000000-aaaa-0000-aaaa-000000000000", - "type": "user", - "has_replay": true - ], + "session": ["id": mockContextProvider.context.sessionID.toRUMDataFormat], "view": [ - "url": "https://foo.bar/", - "referrer": "", - "id": "2aac5419-a626-4098-b1b5-39154f8ea8f3" + "id": "00413060-599f-4a77-80de-5d3beab3da2e" ], - "resource": [ - "id": "6df22efd-78b4-454f-b44f-3ac8846e5311", - "type": "image", - "url": "https://foo.bar/_nuxt/img/bedding.8af1600.jpg", - "duration": 369_000_000, - "download": ["duration": 351_000_000, "start": 18_000_000], - "first_byte": ["duration": 17_000_000, "start": 1_000_000] - ], - "type": "resource" + "type": "action" ] - try eventConsumer.consume(event: webRUMResourceEvent, eventType: "resource") + try eventConsumer.consume(event: webRUMEvent) + + let data = try JSONEncoder().encode(mockWriter.dataWritten as? CodableValue) + let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) - let writtenRUMResourceEvent = try XCTUnwrap(mockWriter.dataWritten as? RUMResourceEvent) - XCTAssertEqual(writtenRUMResourceEvent.view.id, "2aac5419-a626-4098-b1b5-39154f8ea8f3") + AssertDictionariesEqual(writtenJSON, expectedWebRUMEvent) } - func testWhenValidWebRUMErrorEventPassed_itDecoratesAndPassesToWriter() throws { - let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! - mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) - mockDateCorrector.correctionOffset = 123 - let eventConsumer = WebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) + func testWhenValidWebRUMEventPassedWithoutRUMContext_itPassesToWriter() throws { + let eventConsumer = DefaultWebRUMEventConsumer( + dataWriter: mockWriter, + dateCorrector: mockDateCorrector, + contextProvider: nil + ) - let webRUMErrorEvent: JSON = [ + let webRUMEvent: JSON = [ "_dd": [ - "format_version": 2, - "drift": 0, - "session": ["plan": 2], - "browser_sdk_version": "3.10.1" + "session": ["plan": 2] ], "application": ["id": "75d50c62-8b66-403c-a453-aaa1c44d64bd"], - "date": 1_640_252_666_129, + "date": 1_640_252_823_292, "service": "shopist-web-ui", - "session": [ - "id": "00000000-aaaa-0000-aaaa-000000000000", - "type": "user", - "has_replay": true - ], + "session": ["id": "00000000-aaaa-0000-aaaa-000000000000"], "view": [ - "url": "https://foo.bar/department/chairs/product/2", - "referrer": "https://foo.bar/department/chairs", - "id": "00413060-599f-4a77-80de-5d3beab3da2e", - "in_foreground": true + "id": "00413060-599f-4a77-80de-5d3beab3da2e" ], - "error": [ - "id": "3de88670-be12-4a30-91c8-378f8ccb8a75", - "message": "Provided [\"type\":\"network error\",\"status\":404]", - "source": "custom", - "stack": "No stack, consider using an instance of Error", - "handling_stack": "Error: \n at @ https://foo.bar/_nuxt/app.30bb4c9.js:2:47460\n at promiseReactionJob @ [native code]", - "handling": "handled" - ], - "type": "error" + "type": "action" ] - try eventConsumer.consume(event: webRUMErrorEvent, eventType: "error") + try eventConsumer.consume(event: webRUMEvent) + + let data = try JSONEncoder().encode(mockWriter.dataWritten as? CodableValue) + let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) - let writtenRUMErrorEvent = try XCTUnwrap(mockWriter.dataWritten as? RUMErrorEvent) - XCTAssertEqual(writtenRUMErrorEvent.view.id, "00413060-599f-4a77-80de-5d3beab3da2e") + AssertDictionariesEqual(writtenJSON, webRUMEvent) } - func testWhenInvalidEventTypeIsPassed_itLogsToUserLogger() throws { - let previousUserLogger = userLogger - defer { userLogger = previousUserLogger } - let output = LogOutputMock() - userLogger = .mockWith(logOutput: output) + func testWhenUnknownWebRUMEventPassed_itPassesToWriter() throws { + let eventConsumer = DefaultWebRUMEventConsumer( + dataWriter: mockWriter, + dateCorrector: mockDateCorrector, + contextProvider: mockContextProvider + ) + + let unknownWebRUMEvent: JSON = [ + "new_key": "new_value", + "type": "unknown" + ] + + try eventConsumer.consume(event: unknownWebRUMEvent) - let eventConsumer = WebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) - try eventConsumer.consume(event: [:], eventType: "unknown_event_type") + let data = try JSONEncoder().encode(mockWriter.dataWritten as? CodableValue) + let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) - XCTAssertEqual(output.recordedLog?.status, .error) - let userLogMessage = try XCTUnwrap(output.recordedLog?.message) - XCTAssertEqual(userLogMessage, "🔥 Web RUM Event Error - Unknown event type: unknown_event_type") + AssertDictionariesEqual(writtenJSON, unknownWebRUMEvent) } } From c9be2e16acc272170df069034e18da40741f43c4 Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Fri, 31 Dec 2021 10:39:24 +0100 Subject: [PATCH 15/33] RUMM-1791 PR comments addressed Missing test case added: sampled out native session --- .../WebView/WebRUMEventConsumerTests.swift | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift index 3ab570ddb5..02b16ab9af 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift @@ -83,6 +83,27 @@ class WebRUMEventConsumerTests: XCTestCase { AssertDictionariesEqual(writtenJSON, webRUMEvent) } + func testWhenNativeSessionIsSampledOut_itPassesWebEventToWriter() throws { + mockContextProvider.context.sessionID = RUMUUID.nullUUID + let eventConsumer = DefaultWebRUMEventConsumer( + dataWriter: mockWriter, + dateCorrector: mockDateCorrector, + contextProvider: mockContextProvider + ) + + let webRUMEvent: JSON = [ + "new_key": "new_value", + "type": "unknown" + ] + + try eventConsumer.consume(event: webRUMEvent) + + let data = try JSONEncoder().encode(mockWriter.dataWritten as? CodableValue) + let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) + + AssertDictionariesEqual(writtenJSON, webRUMEvent) + } + func testWhenUnknownWebRUMEventPassed_itPassesToWriter() throws { let eventConsumer = DefaultWebRUMEventConsumer( dataWriter: mockWriter, From 2308581efe788bfb5c8fec24497034117e2a6eab Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Tue, 4 Jan 2022 14:31:51 +0100 Subject: [PATCH 16/33] RUMM-1793 WebView events fire RUMWebViewCommand RUMWebViewCommand is an empty command that keeps the session alive --- .../WKUserContentController+Datadog.swift | 8 +++-- .../WebView/WebRUMEventConsumer.swift | 14 ++++++++- .../Datadog/RUM/RUMMonitor/RUMCommand.swift | 7 +++++ .../WebView/WebRUMEventConsumerTests.swift | 30 ++++++++++++++++--- 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift index 8d9a188f3a..80e335615e 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift @@ -16,7 +16,7 @@ public extension WKUserContentController { internal func __addDatadogMessageHandler(allowedWebViewHosts: Set, hostsSanitizer: HostsSanitizing) { let bridgeName = DatadogMessageHandler.name - let contextProvider = (Global.rum as? RUMMonitor)?.contextProvider + let globalRUMMonitor = Global.rum as? RUMMonitor var logEventConsumer: DefaultWebLogEventConsumer? = nil if let loggingFeature = LoggingFeature.instance { @@ -24,7 +24,7 @@ public extension WKUserContentController { userLogsWriter: loggingFeature.storage.writer, internalLogsWriter: InternalMonitoringFeature.instance?.logsStorage.writer, dateCorrector: loggingFeature.dateCorrector, - rumContextProvider: contextProvider, + rumContextProvider: globalRUMMonitor?.contextProvider, applicationVersion: loggingFeature.configuration.common.applicationVersion, environment: loggingFeature.configuration.common.environment ) @@ -35,7 +35,9 @@ public extension WKUserContentController { rumEventConsumer = DefaultWebRUMEventConsumer( dataWriter: rumFeature.storage.writer, dateCorrector: rumFeature.dateCorrector, - contextProvider: contextProvider + contextProvider: globalRUMMonitor?.contextProvider, + rumCommandSubscriber: globalRUMMonitor, + dateProvider: rumFeature.dateProvider ) } diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift index cdfcff6b01..3e567b5c6e 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift @@ -10,20 +10,32 @@ internal class DefaultWebRUMEventConsumer: WebRUMEventConsumer { private let dataWriter: Writer private let dateCorrector: DateCorrectorType private let contextProvider: RUMContextProvider? + private let rumCommandSubscriber: RUMCommandSubscriber? + private let dateProvider: DateProvider private let jsonDecoder = JSONDecoder() init( dataWriter: Writer, dateCorrector: DateCorrectorType, - contextProvider: RUMContextProvider? + contextProvider: RUMContextProvider?, + rumCommandSubscriber: RUMCommandSubscriber?, + dateProvider: DateProvider ) { self.dataWriter = dataWriter self.dateCorrector = dateCorrector self.contextProvider = contextProvider + self.rumCommandSubscriber = rumCommandSubscriber + self.dateProvider = dateProvider } func consume(event: JSON) throws { + rumCommandSubscriber?.process( + command: RUMWebViewCommand( + time: dateProvider.currentDate(), + attributes: [:] + ) + ) let rumContext = contextProvider?.context let mappedEvent = map(event: event, with: rumContext) diff --git a/Sources/Datadog/RUM/RUMMonitor/RUMCommand.swift b/Sources/Datadog/RUM/RUMMonitor/RUMCommand.swift index d9e9f88728..8c00e7562a 100644 --- a/Sources/Datadog/RUM/RUMMonitor/RUMCommand.swift +++ b/Sources/Datadog/RUM/RUMMonitor/RUMCommand.swift @@ -281,3 +281,10 @@ internal struct RUMAddLongTaskCommand: RUMCommand { let duration: TimeInterval } + +// MARK: - RUM Web Events related commands + +internal struct RUMWebViewCommand: RUMCommand { + var time: Date + var attributes: [AttributeKey: AttributeValue] +} diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift index 02b16ab9af..2a09b09ae2 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift @@ -12,12 +12,20 @@ class WebRUMEventConsumerTests: XCTestCase { let mockWriter = FileWriterMock() let mockDateCorrector = DateCorrectorMock() let mockContextProvider = RUMContextProviderMock(context: .mockWith(rumApplicationID: "123456")) + let mockCommandSubscriber = RUMCommandSubscriberMock() + let mockDateProvider = RelativeDateProvider(startingFrom: .mockDecember15th2019At10AMUTC(), advancingBySeconds: 0.0) func testWhenValidWebRUMEventPassed_itDecoratesAndPassesToWriter() throws { let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) mockDateCorrector.correctionOffset = 123 - let eventConsumer = DefaultWebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) + let eventConsumer = DefaultWebRUMEventConsumer( + dataWriter: mockWriter, + dateCorrector: mockDateCorrector, + contextProvider: mockContextProvider, + rumCommandSubscriber: mockCommandSubscriber, + dateProvider: mockDateProvider + ) let webRUMEvent: JSON = [ "_dd": [ @@ -52,13 +60,17 @@ class WebRUMEventConsumerTests: XCTestCase { let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) AssertDictionariesEqual(writtenJSON, expectedWebRUMEvent) + let webViewCommand = try XCTUnwrap(mockCommandSubscriber.lastReceivedCommand) + XCTAssertEqual(webViewCommand.time, .mockDecember15th2019At10AMUTC()) } func testWhenValidWebRUMEventPassedWithoutRUMContext_itPassesToWriter() throws { let eventConsumer = DefaultWebRUMEventConsumer( dataWriter: mockWriter, dateCorrector: mockDateCorrector, - contextProvider: nil + contextProvider: nil, + rumCommandSubscriber: mockCommandSubscriber, + dateProvider: mockDateProvider ) let webRUMEvent: JSON = [ @@ -81,6 +93,8 @@ class WebRUMEventConsumerTests: XCTestCase { let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) AssertDictionariesEqual(writtenJSON, webRUMEvent) + let webViewCommand = try XCTUnwrap(mockCommandSubscriber.lastReceivedCommand) + XCTAssertEqual(webViewCommand.time, .mockDecember15th2019At10AMUTC()) } func testWhenNativeSessionIsSampledOut_itPassesWebEventToWriter() throws { @@ -88,7 +102,9 @@ class WebRUMEventConsumerTests: XCTestCase { let eventConsumer = DefaultWebRUMEventConsumer( dataWriter: mockWriter, dateCorrector: mockDateCorrector, - contextProvider: mockContextProvider + contextProvider: mockContextProvider, + rumCommandSubscriber: mockCommandSubscriber, + dateProvider: mockDateProvider ) let webRUMEvent: JSON = [ @@ -102,13 +118,17 @@ class WebRUMEventConsumerTests: XCTestCase { let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) AssertDictionariesEqual(writtenJSON, webRUMEvent) + let webViewCommand = try XCTUnwrap(mockCommandSubscriber.lastReceivedCommand) + XCTAssertEqual(webViewCommand.time, .mockDecember15th2019At10AMUTC()) } func testWhenUnknownWebRUMEventPassed_itPassesToWriter() throws { let eventConsumer = DefaultWebRUMEventConsumer( dataWriter: mockWriter, dateCorrector: mockDateCorrector, - contextProvider: mockContextProvider + contextProvider: mockContextProvider, + rumCommandSubscriber: mockCommandSubscriber, + dateProvider: mockDateProvider ) let unknownWebRUMEvent: JSON = [ @@ -122,5 +142,7 @@ class WebRUMEventConsumerTests: XCTestCase { let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) AssertDictionariesEqual(writtenJSON, unknownWebRUMEvent) + let webViewCommand = try XCTUnwrap(mockCommandSubscriber.lastReceivedCommand) + XCTAssertEqual(webViewCommand.time, .mockDecember15th2019At10AMUTC()) } } From a729555c326bf116a0b3b26f4cb87798cf71900a Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Wed, 5 Jan 2022 16:41:06 +0100 Subject: [PATCH 17/33] RUMM-1793 PR comments addressed --- .../FeaturesIntegration/WebView/WebRUMEventConsumer.swift | 2 +- Sources/Datadog/RUM/RUMMonitor/RUMCommand.swift | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift index 3e567b5c6e..6257974920 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift @@ -31,7 +31,7 @@ internal class DefaultWebRUMEventConsumer: WebRUMEventConsumer { func consume(event: JSON) throws { rumCommandSubscriber?.process( - command: RUMWebViewCommand( + command: RUMKeepSessionAliveCommand( time: dateProvider.currentDate(), attributes: [:] ) diff --git a/Sources/Datadog/RUM/RUMMonitor/RUMCommand.swift b/Sources/Datadog/RUM/RUMMonitor/RUMCommand.swift index 8c00e7562a..5a576b29f1 100644 --- a/Sources/Datadog/RUM/RUMMonitor/RUMCommand.swift +++ b/Sources/Datadog/RUM/RUMMonitor/RUMCommand.swift @@ -284,7 +284,8 @@ internal struct RUMAddLongTaskCommand: RUMCommand { // MARK: - RUM Web Events related commands -internal struct RUMWebViewCommand: RUMCommand { +/// RUM Events received from WebView should keep the active session alive, therefore they fire this command to do so. (ref: RUMM-1793) +internal struct RUMKeepSessionAliveCommand: RUMCommand { var time: Date var attributes: [AttributeKey: AttributeValue] } From 9cf0651fb0f3fbda787a6e45a646b5669ca872eb Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Thu, 13 Jan 2022 10:54:29 +0100 Subject: [PATCH 18/33] RUMM-1649 WebRUMEvent date offset in Ms It was in ns and that resulted in dropped events --- .../FeaturesIntegration/WebView/WebRUMEventConsumer.swift | 8 ++++---- .../Datadog/RUM/WebView/WebRUMEventConsumerTests.swift | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift index 6257974920..975add700d 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift @@ -55,8 +55,8 @@ internal class DefaultWebRUMEventConsumer: WebRUMEventConsumer { if let date = mutableEvent["date"] as? Int { let viewID = (mutableEvent["view"] as? JSON)?["id"] as? String - let serverTimeOffsetInNs = getOffset(viewID: viewID) - let correctedDate = Int64(date) + serverTimeOffsetInNs + let serverTimeOffsetInMs = getOffsetInMs(viewID: viewID) + let correctedDate = Int64(date) + serverTimeOffsetInMs mutableEvent["date"] = correctedDate } @@ -87,7 +87,7 @@ internal class DefaultWebRUMEventConsumer: WebRUMEventConsumer { private typealias ViewIDOffsetPair = (viewID: String, offset: Offset) private var viewIDOffsetPairs = [ViewIDOffsetPair]() - private func getOffset(viewID: String?) -> Offset { + private func getOffsetInMs(viewID: String?) -> Offset { guard let viewID = viewID else { return 0 } @@ -97,7 +97,7 @@ internal class DefaultWebRUMEventConsumer: WebRUMEventConsumer { if let found = found { return found.offset } - let offset = dateCorrector.currentCorrection.serverTimeOffset.toInt64Nanoseconds + let offset = dateCorrector.currentCorrection.serverTimeOffset.toInt64Milliseconds viewIDOffsetPairs.insert((viewID: viewID, offset: offset), at: 0) return offset } diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift index 2a09b09ae2..0ef4c6fd6c 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift @@ -45,7 +45,7 @@ class WebRUMEventConsumerTests: XCTestCase { "session": ["plan": 1] ], "application": ["id": mockContextProvider.context.rumApplicationID], - "date": 1_640_252_823_292 + 123.toInt64Nanoseconds, + "date": 1_640_252_823_292 + 123.toInt64Milliseconds, "service": "shopist-web-ui", "session": ["id": mockContextProvider.context.sessionID.toRUMDataFormat], "view": [ From 347ea1ad11dd1d0fd485747edb1aa1d1df39a696 Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Fri, 14 Jan 2022 16:11:16 +0100 Subject: [PATCH 19/33] RUMM-1649 WebViewTracking integration test added --- Datadog/Datadog.xcodeproj/project.pbxproj | 32 ++++ .../xcshareddata/xcschemes/Example.xcscheme | 5 + .../Scenarios/WebView/WebViewScenarios.swift | 20 +++ ...WebViewTrackingFixtureViewController.swift | 138 ++++++++++++++++++ .../WebViewTrackingScenario.storyboard | 33 +++++ .../WebView/WebViewScenarioTest.swift | 35 +++++ 6 files changed, 263 insertions(+) create mode 100644 Datadog/Example/Scenarios/WebView/WebViewScenarios.swift create mode 100644 Datadog/Example/Scenarios/WebView/WebViewTrackingFixtureViewController.swift create mode 100644 Datadog/Example/Scenarios/WebView/WebViewTrackingScenario.storyboard create mode 100644 Tests/DatadogIntegrationTests/Scenarios/WebView/WebViewScenarioTest.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index f6f941a50e..ff9d3637cb 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -518,6 +518,10 @@ 9EA3CA6926775A3500B16871 /* VitalRefreshRateReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA3CA6826775A3500B16871 /* VitalRefreshRateReader.swift */; }; 9EA8A7F1275E1518007D6FDB /* HostsSanitizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA8A7F0275E1518007D6FDB /* HostsSanitizerTests.swift */; }; 9EA8A7F82768A72B007D6FDB /* WebLogEventConsumerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA8A7F72768A72B007D6FDB /* WebLogEventConsumerTests.swift */; }; + 9EA95C1C2791C9BE00F6C1F3 /* WebViewTrackingFixtureViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA95C192791C9BE00F6C1F3 /* WebViewTrackingFixtureViewController.swift */; }; + 9EA95C1D2791C9BE00F6C1F3 /* WebViewTrackingScenario.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9EA95C1A2791C9BE00F6C1F3 /* WebViewTrackingScenario.storyboard */; }; + 9EA95C1E2791C9BE00F6C1F3 /* WebViewScenarios.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA95C1B2791C9BE00F6C1F3 /* WebViewScenarios.swift */; }; + 9EA95C212791C9E200F6C1F3 /* WebViewScenarioTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA95C202791C9E200F6C1F3 /* WebViewScenarioTest.swift */; }; 9EAF0CF6275A21100044E8CA /* WKUserContentController+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAF0CF5275A21100044E8CA /* WKUserContentController+DatadogTests.swift */; }; 9EAF0CF8275A2FDC0044E8CA /* HostsSanitizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAF0CF7275A2FDC0044E8CA /* HostsSanitizer.swift */; }; 9EB4B862274E79D50041CD03 /* WKUserContentController+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB4B861274E79D50041CD03 /* WKUserContentController+Datadog.swift */; }; @@ -1213,6 +1217,10 @@ 9EA3CA6826775A3500B16871 /* VitalRefreshRateReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VitalRefreshRateReader.swift; sourceTree = ""; }; 9EA8A7F0275E1518007D6FDB /* HostsSanitizerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostsSanitizerTests.swift; sourceTree = ""; }; 9EA8A7F72768A72B007D6FDB /* WebLogEventConsumerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebLogEventConsumerTests.swift; sourceTree = ""; }; + 9EA95C192791C9BE00F6C1F3 /* WebViewTrackingFixtureViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebViewTrackingFixtureViewController.swift; sourceTree = ""; }; + 9EA95C1A2791C9BE00F6C1F3 /* WebViewTrackingScenario.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = WebViewTrackingScenario.storyboard; sourceTree = ""; }; + 9EA95C1B2791C9BE00F6C1F3 /* WebViewScenarios.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebViewScenarios.swift; sourceTree = ""; }; + 9EA95C202791C9E200F6C1F3 /* WebViewScenarioTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebViewScenarioTest.swift; sourceTree = ""; }; 9EAF0CF5275A21100044E8CA /* WKUserContentController+DatadogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WKUserContentController+DatadogTests.swift"; sourceTree = ""; }; 9EAF0CF7275A2FDC0044E8CA /* HostsSanitizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostsSanitizer.swift; sourceTree = ""; }; 9EB4B861274E79D50041CD03 /* WKUserContentController+Datadog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WKUserContentController+Datadog.swift"; sourceTree = ""; }; @@ -2055,6 +2063,7 @@ 6111542325C992D9007C84C9 /* CrashReporting */, 611EA12B2580F40E00BC0E56 /* TrackingConsent */, 6164AE7D252B4CE2000D78C4 /* URLSession */, + 9EA95C182791C9BE00F6C1F3 /* WebView */, ); path = Scenarios; sourceTree = ""; @@ -3197,6 +3206,7 @@ 61F3CD9F2511070300C816E5 /* RUM */, 61B6811D25F0E8480015B4AF /* CrashReporting */, 611EA15325815EDC00BC0E56 /* TrackingConsent */, + 9EA95C1F2791C9E200F6C1F3 /* WebView */, ); path = Scenarios; sourceTree = ""; @@ -3389,6 +3399,24 @@ path = ../Sources/_Datadog_Private; sourceTree = ""; }; + 9EA95C182791C9BE00F6C1F3 /* WebView */ = { + isa = PBXGroup; + children = ( + 9EA95C192791C9BE00F6C1F3 /* WebViewTrackingFixtureViewController.swift */, + 9EA95C1A2791C9BE00F6C1F3 /* WebViewTrackingScenario.storyboard */, + 9EA95C1B2791C9BE00F6C1F3 /* WebViewScenarios.swift */, + ); + path = WebView; + sourceTree = ""; + }; + 9EA95C1F2791C9E200F6C1F3 /* WebView */ = { + isa = PBXGroup; + children = ( + 9EA95C202791C9E200F6C1F3 /* WebViewScenarioTest.swift */, + ); + path = WebView; + sourceTree = ""; + }; 9EB4B860274E79620041CD03 /* WebView */ = { isa = PBXGroup; children = ( @@ -3899,6 +3927,7 @@ D2F5BB36271831C200BDE2A4 /* RUMSwiftUIInstrumentationScenario.storyboard in Resources */, 611EA13C2580F77400BC0E56 /* TrackingConsentScenario.storyboard in Resources */, 61441C1124616DEC003D8BB8 /* LaunchScreen.storyboard in Resources */, + 9EA95C1D2791C9BE00F6C1F3 /* WebViewTrackingScenario.storyboard in Resources */, 61337039250F852E00236D58 /* RUMManualInstrumentationScenario.storyboard in Resources */, 6193DCA4251B5691009B8011 /* RUMTapActionScenario.storyboard in Resources */, 6167ACC7251A0BCE0012B4D0 /* NSURLSessionScenario.storyboard in Resources */, @@ -4492,6 +4521,7 @@ D2F5BB382718331800BDE2A4 /* SwiftUIRootViewController.swift in Sources */, 618DCFE124C766F500589570 /* SendRUMFixture2ViewController.swift in Sources */, 61441C982461A649003D8BB8 /* DebugTracingViewController.swift in Sources */, + 9EA95C1E2791C9BE00F6C1F3 /* WebViewScenarios.swift in Sources */, 61F9CA8025125C01000A5E61 /* RUMNCSScreen3ViewController.swift in Sources */, 6164AE89252B4ECA000D78C4 /* SendThirdPartyRequestsViewController.swift in Sources */, 611EA14225810E1900BC0E56 /* TSHomeViewController.swift in Sources */, @@ -4528,6 +4558,7 @@ 617247AF25DA9BEA007085B3 /* CrashReportingObjcHelpers.m in Sources */, 6193DCE1251B692C009B8011 /* RUMTASTableViewController.swift in Sources */, 6111544825C9A88B007C84C9 /* PersistenceHelper.swift in Sources */, + 9EA95C1C2791C9BE00F6C1F3 /* WebViewTrackingFixtureViewController.swift in Sources */, 61D50C462580EF19006038A3 /* TracingScenarios.swift in Sources */, 61776CED273BEA5500F93802 /* DebugRUMSessionViewController.swift in Sources */, 618DCFE324C766FB00589570 /* SendRUMFixture3ViewController.swift in Sources */, @@ -4557,6 +4588,7 @@ 61441C4124617013003D8BB8 /* LoggingScenarioTests.swift in Sources */, 612D8F8125AF1C74000E2E09 /* RUMScrubbingScenarioTests.swift in Sources */, 613B77382521E80800155458 /* RUMTabBarControllerScenarioTests.swift in Sources */, + 9EA95C212791C9E200F6C1F3 /* WebViewScenarioTest.swift in Sources */, 61441C4B24618052003D8BB8 /* SpanMatcher.swift in Sources */, 6167ACFD251A22E00012B4D0 /* TracingURLSessionScenarioTests.swift in Sources */, 611EA16625825FB300BC0E56 /* TrackingConsentScenarioTests.swift in Sources */, diff --git a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/Example.xcscheme b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/Example.xcscheme index 53606c9de4..2eb066f4ed 100644 --- a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/Example.xcscheme +++ b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/Example.xcscheme @@ -151,6 +151,11 @@ value = "CrashReportingCollectOrSendWithLoggingScenario" isEnabled = "NO"> + + + + Mobile SDK Detection test page + + + + + +

window.DatadogEventBridge:
undefined

+ """ + + private var webView: WKWebView! + + override func viewDidLoad() { + super.viewDidLoad() + + let controller = WKUserContentController() + controller.addDatadogMessageHandler(allowedWebViewHosts: ["datadoghq.dev"]) + let config = WKWebViewConfiguration() + config.userContentController = controller + + webView = WKWebView(frame: view.bounds, configuration: config) + webView.navigationDelegate = self + view.addSubview(webView) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + webView.loadHTMLString(html, baseURL: nil) + } + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + let js = """ + window.sendLog() + """ + webView.evaluateJavaScript(js) { res, err in + assert(err == nil, "JS execution shouldn't return an error") + } + } +} diff --git a/Datadog/Example/Scenarios/WebView/WebViewTrackingScenario.storyboard b/Datadog/Example/Scenarios/WebView/WebViewTrackingScenario.storyboard new file mode 100644 index 0000000000..29f3512548 --- /dev/null +++ b/Datadog/Example/Scenarios/WebView/WebViewTrackingScenario.storyboard @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/DatadogIntegrationTests/Scenarios/WebView/WebViewScenarioTest.swift b/Tests/DatadogIntegrationTests/Scenarios/WebView/WebViewScenarioTest.swift new file mode 100644 index 0000000000..f71c0160cf --- /dev/null +++ b/Tests/DatadogIntegrationTests/Scenarios/WebView/WebViewScenarioTest.swift @@ -0,0 +1,35 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2020 Datadog, Inc. + */ + +import Foundation +import HTTPServerMock +import XCTest + +class WebViewScenarioTest: IntegrationTests, LoggingCommonAsserts { + func testWebViewLoggingScenario() throws { + let loggingServerSession = server.obtainUniqueRecordingSession() + + let app = ExampleApplication() + app.launchWith( + testScenarioClassName: "WebViewTrackingScenario", + serverConfiguration: HTTPServerMockConfiguration( + logsEndpoint: loggingServerSession.recordingURL + ) + ) + + // Get expected number of `LogMatchers` + let recordedRequests = try loggingServerSession.pullRecordedRequests(timeout: dataDeliveryTimeout) { requests in + try LogMatcher.from(requests: requests).count >= 1 + } + let logMatchers = try LogMatcher.from(requests: recordedRequests) + + // Assert common things + assertLogging(requests: recordedRequests) + + logMatchers[0].assertStatus(equals: "error") + logMatchers[0].assertMessage(equals: "console error: error") + } +} From 1d9597028f293253f1fa2fd9aae70806f56e1f10 Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Fri, 14 Jan 2022 16:56:29 +0100 Subject: [PATCH 20/33] RUMM-1649 PR comments addressed WebViewScenarioTest uses an actual website to test WebView Tracking feature --- ...WebViewTrackingFixtureViewController.swift | 112 +----------------- .../WebView/WebViewScenarioTest.swift | 32 +++-- .../Matchers/RUMEventMatcher.swift | 15 ++- .../Matchers/RUMSessionMatcher.swift | 2 +- 4 files changed, 38 insertions(+), 123 deletions(-) diff --git a/Datadog/Example/Scenarios/WebView/WebViewTrackingFixtureViewController.swift b/Datadog/Example/Scenarios/WebView/WebViewTrackingFixtureViewController.swift index 7c45d7db43..8ebe9aadf4 100644 --- a/Datadog/Example/Scenarios/WebView/WebViewTrackingFixtureViewController.swift +++ b/Datadog/Example/Scenarios/WebView/WebViewTrackingFixtureViewController.swift @@ -9,111 +9,13 @@ import WebKit import Datadog class WebViewTrackingFixtureViewController: UIViewController, WKNavigationDelegate { - let html = """ - - - Mobile SDK Detection test page - - - - - -

window.DatadogEventBridge:
undefined

- """ - private var webView: WKWebView! override func viewDidLoad() { super.viewDidLoad() let controller = WKUserContentController() - controller.addDatadogMessageHandler(allowedWebViewHosts: ["datadoghq.dev"]) + controller.addDatadogMessageHandler(allowedWebViewHosts: ["shopist.io"]) let config = WKWebViewConfiguration() config.userContentController = controller @@ -124,15 +26,9 @@ class WebViewTrackingFixtureViewController: UIViewController, WKNavigationDelega override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - webView.loadHTMLString(html, baseURL: nil) - } - func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - let js = """ - window.sendLog() - """ - webView.evaluateJavaScript(js) { res, err in - assert(err == nil, "JS execution shouldn't return an error") - } + // swiftlint:disable:next force_unwrapping + let request = URLRequest(url: URL(string: "https://shopist.io")!) + webView.load(request) } } diff --git a/Tests/DatadogIntegrationTests/Scenarios/WebView/WebViewScenarioTest.swift b/Tests/DatadogIntegrationTests/Scenarios/WebView/WebViewScenarioTest.swift index f71c0160cf..404ea233b1 100644 --- a/Tests/DatadogIntegrationTests/Scenarios/WebView/WebViewScenarioTest.swift +++ b/Tests/DatadogIntegrationTests/Scenarios/WebView/WebViewScenarioTest.swift @@ -8,28 +8,36 @@ import Foundation import HTTPServerMock import XCTest -class WebViewScenarioTest: IntegrationTests, LoggingCommonAsserts { - func testWebViewLoggingScenario() throws { - let loggingServerSession = server.obtainUniqueRecordingSession() +class WebViewScenarioTest: IntegrationTests, RUMCommonAsserts { + func testWebViewRUMEventsScenario() throws { + // Server session recording RUM events send to `HTTPServerMock`. + let rumServerSession = server.obtainUniqueRecordingSession() let app = ExampleApplication() app.launchWith( testScenarioClassName: "WebViewTrackingScenario", serverConfiguration: HTTPServerMockConfiguration( - logsEndpoint: loggingServerSession.recordingURL + rumEndpoint: rumServerSession.recordingURL ) ) - // Get expected number of `LogMatchers` - let recordedRequests = try loggingServerSession.pullRecordedRequests(timeout: dataDeliveryTimeout) { requests in - try LogMatcher.from(requests: requests).count >= 1 + // Get RUM Sessions with expected number of View visits + let recordedRUMRequests = try rumServerSession.pullRecordedRequests(timeout: dataDeliveryTimeout) { requests in + try RUMSessionMatcher.singleSession(from: requests)?.viewVisits.count == 2 } - let logMatchers = try LogMatcher.from(requests: recordedRequests) - // Assert common things - assertLogging(requests: recordedRequests) + assertRUM(requests: recordedRUMRequests) - logMatchers[0].assertStatus(equals: "error") - logMatchers[0].assertMessage(equals: "console error: error") + let session = try XCTUnwrap(RUMSessionMatcher.singleSession(from: recordedRUMRequests)) + + XCTAssertEqual(session.viewVisits.count, 2) + session.viewVisits[0].viewEvents.forEach { nativeView in + XCTAssertEqual(nativeView.source, .ios) + } + session.viewVisits[1].viewEvents.forEach { browserView in + // ideally `source` should be `.browser` + // but it's not implemented in `browser-sdk` yet + XCTAssertNotEqual(browserView.source, .ios) + } } } diff --git a/Tests/DatadogTests/Matchers/RUMEventMatcher.swift b/Tests/DatadogTests/Matchers/RUMEventMatcher.swift index 0c0db8b5ff..d759aab850 100644 --- a/Tests/DatadogTests/Matchers/RUMEventMatcher.swift +++ b/Tests/DatadogTests/Matchers/RUMEventMatcher.swift @@ -40,8 +40,19 @@ internal class RUMEventMatcher { private let jsonDataDecoder = JSONDecoder() private init(with jsonData: Data) throws { - self.jsonMatcher = JSONDataMatcher(from: try jsonData.toJSONObject()) - self.jsonData = jsonData + var json = try jsonData.toJSONObject() + + // NOTE: RUMM-1649 WebViewScenarioTest receives lowercase `method` values + if let eventType = json["type"] as? String, + eventType == "resource", + var resource = json["resource"] as? [String: Any], + let method = resource["method"] as? String { + resource["method"] = method.uppercased() + json["resource"] = resource + } + + self.jsonMatcher = JSONDataMatcher(from: json) + self.jsonData = try JSONSerialization.data(withJSONObject: json, options: []) } // MARK: - Full match diff --git a/Tests/DatadogTests/Matchers/RUMSessionMatcher.swift b/Tests/DatadogTests/Matchers/RUMSessionMatcher.swift index 0870331a1e..e0c2aab7a2 100644 --- a/Tests/DatadogTests/Matchers/RUMSessionMatcher.swift +++ b/Tests/DatadogTests/Matchers/RUMSessionMatcher.swift @@ -137,7 +137,7 @@ internal class RUMSessionMatcher { visit.viewEvents.append(rumEvent) visit.viewEventMatchers.append(matcher) if visit.name.isEmpty { - visit.name = rumEvent.view.name! + visit.name = rumEvent.view.name ?? "" } else if visit.name != rumEvent.view.name { throw RUMSessionConsistencyException( description: "The RUM View name: \(rumEvent) is different than other RUM View names for the same `view.id`." From da0d5447bb4e8470b8ea370a9ff78eae917d2a31 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 17 Jan 2022 09:37:21 +0100 Subject: [PATCH 21/33] RUMM-1649 Disable Webviews tracking integration test in Crash Reporting test plan this is done by switching crash reporting `.xctestplan` to not include new tests automatically and removing Webviews test from the existing list. --- ...ogCrashReportingIntegrationTests.xctestplan | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/Datadog/TargetSupport/DatadogIntegrationTests/DatadogCrashReportingIntegrationTests.xctestplan b/Datadog/TargetSupport/DatadogIntegrationTests/DatadogCrashReportingIntegrationTests.xctestplan index 431e392aa1..c070030cdf 100644 --- a/Datadog/TargetSupport/DatadogIntegrationTests/DatadogCrashReportingIntegrationTests.xctestplan +++ b/Datadog/TargetSupport/DatadogIntegrationTests/DatadogCrashReportingIntegrationTests.xctestplan @@ -33,21 +33,9 @@ }, "testTargets" : [ { - "skippedTests" : [ - "IntegrationTests", - "LoggingScenarioTests", - "RUMManualInstrumentationScenarioTests", - "RUMMobileVitalsScenarioTests", - "RUMModalViewsScenarioTests", - "RUMNavigationControllerScenarioTests", - "RUMResourcesScenarioTests", - "RUMScrubbingScenarioTests", - "RUMSwiftUIScenarioTests", - "RUMTabBarControllerScenarioTests", - "RUMTapActionScenarioTests", - "TracingManualInstrumentationScenarioTests", - "TracingURLSessionScenarioTests", - "TrackingConsentScenarioTests" + "selectedTests" : [ + "CrashReportingWithLoggingScenarioTests\/testCrashReportingCollectOrSendWithLoggingScenario()", + "CrashReportingWithRUMScenarioTests\/testCrashReportingCollectOrSendWithRUMScenario()" ], "target" : { "containerPath" : "container:Datadog.xcodeproj", From 10fabe3c282f0d77effdf5328c154d2588209cc8 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 17 Jan 2022 12:17:27 +0100 Subject: [PATCH 22/33] RUMM-1649 Allow `viewVisit.name` to be `nil` in `RUMSessionMatcher` as this is now possible for Browser SDK views. --- Tests/DatadogTests/Matchers/RUMSessionMatcher.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Tests/DatadogTests/Matchers/RUMSessionMatcher.swift b/Tests/DatadogTests/Matchers/RUMSessionMatcher.swift index e0c2aab7a2..e269859845 100644 --- a/Tests/DatadogTests/Matchers/RUMSessionMatcher.swift +++ b/Tests/DatadogTests/Matchers/RUMSessionMatcher.swift @@ -48,7 +48,8 @@ internal class RUMSessionMatcher { /// The `name` of the visited RUM View. /// Corresponds to the "VIEW NAME" in RUM Explorer. - fileprivate(set) var name: String = "" + /// Might be `nil` for events received from Browser SDK. + fileprivate(set) var name: String? /// The `path` of the visited RUM View. /// Corresponds to the "VIEW URL" in RUM Explorer. @@ -136,8 +137,8 @@ internal class RUMSessionMatcher { if let visit = visitsByViewID[rumEvent.view.id] { visit.viewEvents.append(rumEvent) visit.viewEventMatchers.append(matcher) - if visit.name.isEmpty { - visit.name = rumEvent.view.name ?? "" + if visit.name == nil { + visit.name = rumEvent.view.name } else if visit.name != rumEvent.view.name { throw RUMSessionConsistencyException( description: "The RUM View name: \(rumEvent) is different than other RUM View names for the same `view.id`." @@ -324,7 +325,7 @@ extension RUMSessionMatcher: CustomStringConvertible { return " → [⛔️ Invalid View - it has no view events]" } - var description = " → [📸 View (name: '\(viewVisit.name)', id: \(viewVisit.viewID), duration: \(seconds(from: lastViewEvent.view.timeSpent)) actions.count: \(lastViewEvent.view.action.count), resources.count: \(lastViewEvent.view.resource.count), errors.count: \(lastViewEvent.view.error.count), longTask.count: \(lastViewEvent.view.longTask?.count ?? 0), frozenFrames.count: \(lastViewEvent.view.frozenFrame?.count ?? 0)]" + var description = " → [📸 View (name: '\(viewVisit.name ?? "nil")', id: \(viewVisit.viewID), duration: \(seconds(from: lastViewEvent.view.timeSpent)) actions.count: \(lastViewEvent.view.action.count), resources.count: \(lastViewEvent.view.resource.count), errors.count: \(lastViewEvent.view.error.count), longTask.count: \(lastViewEvent.view.longTask?.count ?? 0), frozenFrames.count: \(lastViewEvent.view.frozenFrame?.count ?? 0)]" if !viewVisit.actionEvents.isEmpty { description += "\n → action events:" From dda0851f9363dcdfbdf569212b901051d6f024ca Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 17 Jan 2022 12:17:42 +0100 Subject: [PATCH 23/33] RUMM-1649 Remove obsolete TODO --- Datadog/Example/Debugging/DebugWebviewViewController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Datadog/Example/Debugging/DebugWebviewViewController.swift b/Datadog/Example/Debugging/DebugWebviewViewController.swift index 084e47176b..48bb6fd9c0 100644 --- a/Datadog/Example/Debugging/DebugWebviewViewController.swift +++ b/Datadog/Example/Debugging/DebugWebviewViewController.swift @@ -85,7 +85,6 @@ class WebviewViewController: UIViewController { super.viewDidLoad() let controller = WKUserContentController() - // TODO: RUMM-1794 remove internal method call and use public API controller.addDatadogMessageHandler(allowedWebViewHosts: ["datadoghq.dev"]) let config = WKWebViewConfiguration() config.userContentController = controller From 6c260e510f01e68a870b7cd2b2a5d916771253c1 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 17 Jan 2022 12:19:43 +0100 Subject: [PATCH 24/33] RUMM-1649 Add assertions to more properties and to logs sent from Browser SDK. --- .../Scenarios/WebView/WebViewScenarios.swift | 17 +++++- ...WebViewTrackingFixtureViewController.swift | 23 ++++--- .../WebView/WebViewScenarioTest.swift | 61 ++++++++++++++++--- 3 files changed, 81 insertions(+), 20 deletions(-) diff --git a/Datadog/Example/Scenarios/WebView/WebViewScenarios.swift b/Datadog/Example/Scenarios/WebView/WebViewScenarios.swift index 1e42555a54..677e06e7d6 100644 --- a/Datadog/Example/Scenarios/WebView/WebViewScenarios.swift +++ b/Datadog/Example/Scenarios/WebView/WebViewScenarios.swift @@ -4,16 +4,27 @@ * Copyright 2019-2020 Datadog, Inc. */ -import Foundation +import UIKit import Datadog -/// Scenario which uses RUM only. Blocks the main thread and expects to have non-zero MobileVitals values +private struct WebViewTrackingScenarioPredicate: UIKitRUMViewsPredicate { + private let defaultPredicate = DefaultUIKitRUMViewsPredicate() + + func rumView(for viewController: UIViewController) -> RUMView? { + if viewController is ShopistWebviewViewController { + return nil // do not consider the webview itself as RUM view + } else { + return defaultPredicate.rumView(for: viewController) + } + } +} + final class WebViewTrackingScenario: TestScenario { static var storyboardName: String = "WebViewTrackingScenario" func configureSDK(builder: Datadog.Configuration.Builder) { _ = builder - .trackUIKitRUMViews() + .trackUIKitRUMViews(using: WebViewTrackingScenarioPredicate()) .enableLogging(true) .enableRUM(true) } diff --git a/Datadog/Example/Scenarios/WebView/WebViewTrackingFixtureViewController.swift b/Datadog/Example/Scenarios/WebView/WebViewTrackingFixtureViewController.swift index 8ebe9aadf4..8ccdeb1532 100644 --- a/Datadog/Example/Scenarios/WebView/WebViewTrackingFixtureViewController.swift +++ b/Datadog/Example/Scenarios/WebView/WebViewTrackingFixtureViewController.swift @@ -9,6 +9,19 @@ import WebKit import Datadog class WebViewTrackingFixtureViewController: UIViewController, WKNavigationDelegate { + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + // An action sent from native iOS SDK. + Global.rum.addUserAction(type: .custom, name: "Native action") + + // Opens a webview configured to pass all its Browser SDK events to native iOS SDK. + show(ShopistWebviewViewController(), sender: nil) + } +} + +class ShopistWebviewViewController: UIViewController { + private let request = URLRequest(url: URL(string: "https://shopist.io")!) private var webView: WKWebView! override func viewDidLoad() { @@ -19,16 +32,12 @@ class WebViewTrackingFixtureViewController: UIViewController, WKNavigationDelega let config = WKWebViewConfiguration() config.userContentController = controller - webView = WKWebView(frame: view.bounds, configuration: config) - webView.navigationDelegate = self + webView = WKWebView(frame: UIScreen.main.bounds, configuration: config) view.addSubview(webView) } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - // swiftlint:disable:next force_unwrapping - let request = URLRequest(url: URL(string: "https://shopist.io")!) + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) webView.load(request) } } diff --git a/Tests/DatadogIntegrationTests/Scenarios/WebView/WebViewScenarioTest.swift b/Tests/DatadogIntegrationTests/Scenarios/WebView/WebViewScenarioTest.swift index 404ea233b1..1400a095a5 100644 --- a/Tests/DatadogIntegrationTests/Scenarios/WebView/WebViewScenarioTest.swift +++ b/Tests/DatadogIntegrationTests/Scenarios/WebView/WebViewScenarioTest.swift @@ -9,35 +9,76 @@ import HTTPServerMock import XCTest class WebViewScenarioTest: IntegrationTests, RUMCommonAsserts { - func testWebViewRUMEventsScenario() throws { + /// In this test, the app opens a WebView which loads Browser SDK instrumented content. + /// The iOS SDK should capture all RUM events and Logs produced by Browser SDK. + func testWebViewEventsScenario() throws { // Server session recording RUM events send to `HTTPServerMock`. let rumServerSession = server.obtainUniqueRecordingSession() + // Server session recording Logs send to `HTTPServerMock`. + let loggingServerSession = server.obtainUniqueRecordingSession() let app = ExampleApplication() app.launchWith( testScenarioClassName: "WebViewTrackingScenario", serverConfiguration: HTTPServerMockConfiguration( + logsEndpoint: loggingServerSession.recordingURL, rumEndpoint: rumServerSession.recordingURL ) ) - // Get RUM Sessions with expected number of View visits + // Get single RUM Session with expected number of View visits let recordedRUMRequests = try rumServerSession.pullRecordedRequests(timeout: dataDeliveryTimeout) { requests in try RUMSessionMatcher.singleSession(from: requests)?.viewVisits.count == 2 } - assertRUM(requests: recordedRUMRequests) let session = try XCTUnwrap(RUMSessionMatcher.singleSession(from: recordedRUMRequests)) + XCTAssertEqual(session.viewVisits.count, 2, "There should be 2 RUM views - one native and one received from Browser SDK") + + // Check iOS SDK events: + let nativeView = session.viewVisits[0] + XCTAssertEqual(nativeView.name, "Example.WebViewTrackingFixtureViewController") + XCTAssertEqual(nativeView.path, "Example.WebViewTrackingFixtureViewController") - XCTAssertEqual(session.viewVisits.count, 2) - session.viewVisits[0].viewEvents.forEach { nativeView in - XCTAssertEqual(nativeView.source, .ios) + nativeView.viewEvents.forEach { nativeViewEvent in + XCTAssertEqual(nativeViewEvent.source, .ios) } - session.viewVisits[1].viewEvents.forEach { browserView in - // ideally `source` should be `.browser` - // but it's not implemented in `browser-sdk` yet - XCTAssertNotEqual(browserView.source, .ios) + XCTAssertEqual(nativeView.actionEvents.count, 2, "It should track 2 native actions") + + // Check Browser SDK events: + let expectedBrowserServiceName = "shopist-web-ui" + let expectedBrowserRUMApplicationID = nativeView.viewEvents[0].application.id + let expectedBrowserSessionID = nativeView.viewEvents[0].session.id + + let browserView = session.viewVisits[1] + XCTAssertNil(browserView.name, "Browser views should have no `name`") + XCTAssertEqual(browserView.path, "https://shopist.io/") + + browserView.viewEvents.forEach { browserViewEvent in + XCTAssertEqual(browserViewEvent.application.id, expectedBrowserRUMApplicationID, "Webview events should use iOS SDK application ID") + XCTAssertEqual(browserViewEvent.session.id, expectedBrowserSessionID, "Webview events should use iOS SDK session ID") + XCTAssertEqual(browserViewEvent.service, expectedBrowserServiceName, "Webview events should use Browser SDK `service`") + XCTAssertNotEqual(browserViewEvent.source, .ios, "Webview events should use Browser SDK `source`") } + XCTAssertGreaterThan(browserView.resourceEvents.count, 0, "It should track some Webview resources") + browserView.resourceEvents.forEach { browserResourceEvent in + XCTAssertEqual(browserResourceEvent.application.id, expectedBrowserRUMApplicationID, "Webview events should use iOS SDK application ID") + XCTAssertEqual(browserResourceEvent.session.id, expectedBrowserSessionID, "Webview events should use iOS SDK session ID") + XCTAssertEqual(browserResourceEvent.service, expectedBrowserServiceName, "Webview events should use Browser SDK `service`") + XCTAssertNotEqual(browserResourceEvent.source, .ios, "Webview events should use Browser SDK `source`") + } + + // Get `LogMatchers` + let recordedRequests = try loggingServerSession.pullRecordedRequests(timeout: dataDeliveryTimeout) { requests in + try LogMatcher.from(requests: requests).count >= 1 // get at least one log + } + let logMatchers = try LogMatcher.from(requests: recordedRequests) + + let browserLog = logMatchers[0] + browserLog.assertServiceName(equals: expectedBrowserServiceName) + browserLog.assertAttributes(equal: [ + "application_id": expectedBrowserRUMApplicationID, + "session_id": expectedBrowserSessionID, + ]) } } From 2f14fd4cca1a47d9fd11926b79adb83252bd01c2 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 17 Jan 2022 16:42:06 +0100 Subject: [PATCH 25/33] RUMM-1924 Allow debugging any URL with WebViews Debugging feature --- Datadog/Example/Debugging/DebugWebviewViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Datadog/Example/Debugging/DebugWebviewViewController.swift b/Datadog/Example/Debugging/DebugWebviewViewController.swift index 48bb6fd9c0..5c5247957f 100644 --- a/Datadog/Example/Debugging/DebugWebviewViewController.swift +++ b/Datadog/Example/Debugging/DebugWebviewViewController.swift @@ -85,7 +85,7 @@ class WebviewViewController: UIViewController { super.viewDidLoad() let controller = WKUserContentController() - controller.addDatadogMessageHandler(allowedWebViewHosts: ["datadoghq.dev"]) + controller.addDatadogMessageHandler(allowedWebViewHosts: [request.url!.host!]) let config = WKWebViewConfiguration() config.userContentController = controller From c6491bf2317b33c603caa791b51e333cca27eae4 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 17 Jan 2022 16:42:54 +0100 Subject: [PATCH 26/33] RUMM-1924 Fix date correction logic for WebView logs - so logs can be properly digested and processed by BE. --- .../FeaturesIntegration/WebView/WebLogEventConsumer.swift | 8 ++++---- .../Datadog/RUM/WebView/WebLogEventConsumerTests.swift | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift index 53902f06e0..0f01e55b2c 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift @@ -60,10 +60,10 @@ internal class DefaultWebLogEventConsumer: WebLogEventConsumer { mutableEvent[Constants.ddTagsKey] = ddTags } - if let date = mutableEvent[Constants.dateKey] as? Int { - let serverTimeOffsetInNs = dateCorrector.currentCorrection.serverTimeOffset.toInt64Nanoseconds - let correctedDate = Int64(date) + serverTimeOffsetInNs - mutableEvent[Constants.dateKey] = correctedDate + if let timestampInMs = mutableEvent[Constants.dateKey] as? Int { + let serverTimeOffsetInMs = dateCorrector.currentCorrection.serverTimeOffset.toInt64Milliseconds + let correctedTimestamp = Int64(timestampInMs) + serverTimeOffsetInMs + mutableEvent[Constants.dateKey] = correctedTimestamp } if let context = rumContextProvider?.context { diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift index be1c9ea46e..9915d2237a 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift @@ -14,7 +14,7 @@ class WebLogEventConsumerTests: XCTestCase { let mockContextProvider = RUMContextProviderMock(context: .mockWith(rumApplicationID: "123456")) func testWhenValidWebLogEventPassed_itDecoratesAndPassesToWriter() throws { - let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! + let mockSessionID: UUID = .mockRandom() mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) mockDateCorrector.correctionOffset = 123 let applicationVersion = String.mockRandom() @@ -37,7 +37,7 @@ class WebLogEventConsumerTests: XCTestCase { "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] ] let expectedWebLogEvent: JSON = [ - "date": 1_635_932_927_012 + 123.toInt64Nanoseconds, + "date": 1_635_932_927_012 + 123.toInt64Milliseconds, "error": ["origin": "console"], "message": "console error: error", "application_id": "123456", @@ -58,7 +58,7 @@ class WebLogEventConsumerTests: XCTestCase { } func testWhenValidWebInternalLogEventPassed_itDecoratesAndPassesToWriter() throws { - let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! + let mockSessionID: UUID = .mockRandom() mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) mockDateCorrector.correctionOffset = 123 let applicationVersion = String.mockRandom() @@ -81,7 +81,7 @@ class WebLogEventConsumerTests: XCTestCase { "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] ] let expectedWebLogEvent: JSON = [ - "date": 1_635_932_927_012 + 123.toInt64Nanoseconds, + "date": 1_635_932_927_012 + 123.toInt64Milliseconds, "error": ["origin": "console"], "message": "console error: error", "application_id": "123456", From 1593ae73fa2e36dc9bb18f266e2d99e43ec77ebe Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 18 Jan 2022 14:39:54 +0100 Subject: [PATCH 27/33] RUMM-1649 Apply `resource.method` patch only to Browser SDK events, not all events within our tests. --- .../Scenarios/RUM/RUMCommonAsserts.swift | 10 ++++--- .../WebView/WebViewScenarioTest.swift | 25 +++++++++++++++-- .../Matchers/RUMEventMatcher.swift | 27 +++++++------------ 3 files changed, 39 insertions(+), 23 deletions(-) diff --git a/Tests/DatadogIntegrationTests/Scenarios/RUM/RUMCommonAsserts.swift b/Tests/DatadogIntegrationTests/Scenarios/RUM/RUMCommonAsserts.swift index d987eaa27e..96451dd125 100644 --- a/Tests/DatadogIntegrationTests/Scenarios/RUM/RUMCommonAsserts.swift +++ b/Tests/DatadogIntegrationTests/Scenarios/RUM/RUMCommonAsserts.swift @@ -60,14 +60,16 @@ extension RUMCommonAsserts { extension RUMSessionMatcher { /// Retrieves single RUM Session from given `requests`. - class func singleSession(from requests: [HTTPServerMock.Request]) throws -> RUMSessionMatcher? { - return try sessions(maxCount: 1, from: requests).first + /// - Parameter eventsPatch: optional transformation to apply on each event within the payload before instantiating matcher (default: `nil`) + class func singleSession(from requests: [HTTPServerMock.Request], eventsPatch: ((Data) throws -> Data)? = nil) throws -> RUMSessionMatcher? { + return try sessions(maxCount: 1, from: requests, eventsPatch: eventsPatch).first } /// Retrieves `maxCount` RUM Sessions from given `requests`. - class func sessions(maxCount: Int, from requests: [HTTPServerMock.Request]) throws -> [RUMSessionMatcher] { + /// - Parameter eventsPatch: optional transformation to apply on each event within the payload before instantiating matcher (default: `nil`) + class func sessions(maxCount: Int, from requests: [HTTPServerMock.Request], eventsPatch: ((Data) throws -> Data)? = nil) throws -> [RUMSessionMatcher] { let eventMatchers = try requests - .flatMap { request in try RUMEventMatcher.fromNewlineSeparatedJSONObjectsData(request.httpBody) } + .flatMap { request in try RUMEventMatcher.fromNewlineSeparatedJSONObjectsData(request.httpBody, eventsPatch: eventsPatch) } let sessionMatchers = try RUMSessionMatcher.groupMatchersBySessions(eventMatchers) if sessionMatchers.count > maxCount { diff --git a/Tests/DatadogIntegrationTests/Scenarios/WebView/WebViewScenarioTest.swift b/Tests/DatadogIntegrationTests/Scenarios/WebView/WebViewScenarioTest.swift index 1400a095a5..ea73557087 100644 --- a/Tests/DatadogIntegrationTests/Scenarios/WebView/WebViewScenarioTest.swift +++ b/Tests/DatadogIntegrationTests/Scenarios/WebView/WebViewScenarioTest.swift @@ -28,11 +28,11 @@ class WebViewScenarioTest: IntegrationTests, RUMCommonAsserts { // Get single RUM Session with expected number of View visits let recordedRUMRequests = try rumServerSession.pullRecordedRequests(timeout: dataDeliveryTimeout) { requests in - try RUMSessionMatcher.singleSession(from: requests)?.viewVisits.count == 2 + try RUMSessionMatcher.singleSession(from: requests, eventsPatch: patchBrowserResourceEvents)?.viewVisits.count == 2 } assertRUM(requests: recordedRUMRequests) - let session = try XCTUnwrap(RUMSessionMatcher.singleSession(from: recordedRUMRequests)) + let session = try XCTUnwrap(RUMSessionMatcher.singleSession(from: recordedRUMRequests, eventsPatch: patchBrowserResourceEvents)) XCTAssertEqual(session.viewVisits.count, 2, "There should be 2 RUM views - one native and one received from Browser SDK") // Check iOS SDK events: @@ -82,3 +82,24 @@ class WebViewScenarioTest: IntegrationTests, RUMCommonAsserts { ]) } } + +/// Patch applied to RUM resource events received from Browser SDK. +/// +/// Browser SDK v4.1.0 uses lowercase string values for `resource.method` field, whereas RUM format schema +/// defines it as uppercase string (e.g. `"get"` instead of `"GET"`). Here we patch `resource.method` to be +/// uppercased, so it can be read and validated in `RUMEventMatcher`. +/// +/// This can be removed once `resource.method` is fixed in future Browser SDK version. +private func patchBrowserResourceEvents(_ data: Data) throws -> Data { + var json = try data.toJSONObject() + + if let eventType = json["type"] as? String, + eventType == "resource", + var resource = json["resource"] as? [String: Any], + let method = resource["method"] as? String { + resource["method"] = method.uppercased() + json["resource"] = resource + } + + return try JSONSerialization.data(withJSONObject: json, options: []) +} diff --git a/Tests/DatadogTests/Matchers/RUMEventMatcher.swift b/Tests/DatadogTests/Matchers/RUMEventMatcher.swift index d759aab850..391512b9c5 100644 --- a/Tests/DatadogTests/Matchers/RUMEventMatcher.swift +++ b/Tests/DatadogTests/Matchers/RUMEventMatcher.swift @@ -27,11 +27,15 @@ internal class RUMEventMatcher { /// ``` /// /// **See Also** `RUMEventMatcher.fromJSONObjectData(_:)` - /// - class func fromNewlineSeparatedJSONObjectsData(_ data: Data) throws -> [RUMEventMatcher] { + /// - Parameter data: payload data + /// - Parameter eventsPatch: optional transformation to apply on each event within the payload before instantiating matcher (default: `nil`) + class func fromNewlineSeparatedJSONObjectsData(_ data: Data, eventsPatch: ((Data) throws -> Data)? = nil) throws -> [RUMEventMatcher] { let separator = "\n".data(using: .utf8)![0] - let spansData = data.split(separator: separator).map { Data($0) } - return try spansData.map { spanJSONData in try RUMEventMatcher.fromJSONObjectData(spanJSONData) } + var eventsData = data.split(separator: separator).map { Data($0) } + if let patch = eventsPatch { + eventsData = try eventsData.map { try patch($0) } + } + return try eventsData.map { eventJSONData in try RUMEventMatcher.fromJSONObjectData(eventJSONData) } } let jsonData: Data @@ -40,19 +44,8 @@ internal class RUMEventMatcher { private let jsonDataDecoder = JSONDecoder() private init(with jsonData: Data) throws { - var json = try jsonData.toJSONObject() - - // NOTE: RUMM-1649 WebViewScenarioTest receives lowercase `method` values - if let eventType = json["type"] as? String, - eventType == "resource", - var resource = json["resource"] as? [String: Any], - let method = resource["method"] as? String { - resource["method"] = method.uppercased() - json["resource"] = resource - } - - self.jsonMatcher = JSONDataMatcher(from: json) - self.jsonData = try JSONSerialization.data(withJSONObject: json, options: []) + self.jsonMatcher = JSONDataMatcher(from: try jsonData.toJSONObject()) + self.jsonData = jsonData } // MARK: - Full match From 51969f2f1126e327b077c4376436261d478242c9 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 18 Jan 2022 17:02:48 +0100 Subject: [PATCH 28/33] RUMM-1925 Add `@service` attribute to all RUM events to unify it with `dd-sdk-android` and with Browser SDK events. --- .../CrashReportingWithRUMIntegration.swift | 2 +- .../Scopes/RUMApplicationScope.swift | 1 + .../RUMMonitor/Scopes/RUMResourceScope.swift | 4 +-- .../Scopes/RUMUserActionScope.swift | 2 +- .../RUM/RUMMonitor/Scopes/RUMViewScope.swift | 8 ++--- Sources/Datadog/RUMMonitor.swift | 1 + .../Datadog/Mocks/RUMFeatureMocks.swift | 4 +++ .../Scopes/RUMResourceScopeTests.swift | 13 +++++++- .../Scopes/RUMUserActionScopeTests.swift | 9 +++++- .../RUMMonitor/Scopes/RUMViewScopeTests.swift | 30 +++++++++++++++---- .../Datadog/RUMMonitorTests.swift | 8 +++++ 11 files changed, 67 insertions(+), 15 deletions(-) diff --git a/Sources/Datadog/FeaturesIntegration/CrashReporting/CrashReportingWithRUMIntegration.swift b/Sources/Datadog/FeaturesIntegration/CrashReporting/CrashReportingWithRUMIntegration.swift index c34a3e3767..78ce4bbbfe 100644 --- a/Sources/Datadog/FeaturesIntegration/CrashReporting/CrashReportingWithRUMIntegration.swift +++ b/Sources/Datadog/FeaturesIntegration/CrashReporting/CrashReportingWithRUMIntegration.swift @@ -353,7 +353,7 @@ internal struct CrashReportingWithRUMIntegration: CrashReportingIntegration { ), context: nil, date: startDate.timeIntervalSince1970.toInt64Milliseconds, - service: nil, + service: rumConfiguration.common.serviceName, session: .init( hasReplay: nil, id: sessionUUID.toRUMDataFormat, diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScope.swift index 46d0a2dd55..56c1bd9344 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMApplicationScope.swift @@ -14,6 +14,7 @@ internal struct RUMScopeDependencies { let userInfoProvider: RUMUserInfoProvider let launchTimeProvider: LaunchTimeProviderType let connectivityInfoProvider: RUMConnectivityInfoProvider + let serviceName: String let eventBuilder: RUMEventBuilder let eventOutput: RUMEventOutput let rumUUIDGenerator: RUMUUIDGenerator diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift index e5748651a4..c18d89b139 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScope.swift @@ -189,7 +189,7 @@ internal class RUMResourceScope: RUMScope { type: resourceType, url: resourceURL ), - service: nil, + service: dependencies.serviceName, session: .init(hasReplay: nil, id: context.sessionID.toRUMDataFormat, type: .user), source: .ios, synthetics: nil, @@ -242,7 +242,7 @@ internal class RUMResourceScope: RUMScope { stack: command.stack, type: command.errorType ), - service: nil, + service: dependencies.serviceName, session: .init(hasReplay: nil, id: context.sessionID.toRUMDataFormat, type: .user), source: .ios, synthetics: nil, diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMUserActionScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMUserActionScope.swift index 34c8a4e364..33cb2dda82 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMUserActionScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMUserActionScope.swift @@ -152,7 +152,7 @@ internal class RUMUserActionScope: RUMScope, RUMContextProvider { connectivity: dependencies.connectivityInfoProvider.current, context: .init(contextInfo: attributes), date: dateCorrection.applying(to: actionStartTime).timeIntervalSince1970.toInt64Milliseconds, - service: nil, + service: dependencies.serviceName, session: .init(hasReplay: nil, id: context.sessionID.toRUMDataFormat, type: .user), source: .ios, synthetics: nil, diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift index db97ebd8f7..d1134f116a 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift @@ -323,7 +323,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { connectivity: dependencies.connectivityInfoProvider.current, context: .init(contextInfo: attributes), date: dateCorrection.applying(to: viewStartTime).timeIntervalSince1970.toInt64Milliseconds, - service: nil, + service: dependencies.serviceName, session: .init(hasReplay: nil, id: context.sessionID.toRUMDataFormat, type: .user), source: .ios, synthetics: nil, @@ -368,7 +368,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { connectivity: dependencies.connectivityInfoProvider.current, context: .init(contextInfo: attributes), date: dateCorrection.applying(to: viewStartTime).timeIntervalSince1970.toInt64Milliseconds, - service: nil, + service: dependencies.serviceName, session: .init(hasReplay: nil, id: context.sessionID.toRUMDataFormat, type: .user), source: .ios, synthetics: nil, @@ -449,7 +449,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { stack: command.stack, type: command.type ), - service: nil, + service: dependencies.serviceName, session: .init(hasReplay: nil, id: context.sessionID.toRUMDataFormat, type: .user), source: .ios, synthetics: nil, @@ -488,7 +488,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { context: .init(contextInfo: attributes), date: dateCorrection.applying(to: command.time - command.duration).timeIntervalSince1970.toInt64Milliseconds, longTask: .init(duration: taskDurationInNs, id: nil, isFrozenFrame: isFrozenFrame), - service: nil, + service: dependencies.serviceName, session: .init(hasReplay: nil, id: context.sessionID.toRUMDataFormat, type: .user), source: .ios, synthetics: nil, diff --git a/Sources/Datadog/RUMMonitor.swift b/Sources/Datadog/RUMMonitor.swift index bf4a5a2c98..765b0ce728 100644 --- a/Sources/Datadog/RUMMonitor.swift +++ b/Sources/Datadog/RUMMonitor.swift @@ -185,6 +185,7 @@ public class RUMMonitor: DDRUMMonitor, RUMCommandSubscriber { networkConnectionInfoProvider: rumFeature.networkConnectionInfoProvider, carrierInfoProvider: rumFeature.carrierInfoProvider ), + serviceName: rumFeature.configuration.common.serviceName, eventBuilder: RUMEventBuilder( eventsMapper: rumFeature.eventsMapper ), diff --git a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift index 664ff958c0..75259448f7 100644 --- a/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift @@ -630,6 +630,7 @@ extension RUMScopeDependencies { networkConnectionInfoProvider: NetworkConnectionInfoProviderMock(networkConnectionInfo: nil), carrierInfoProvider: CarrierInfoProviderMock(carrierInfo: nil) ), + serviceName: String = .mockAny(), eventBuilder: RUMEventBuilder = RUMEventBuilder(eventsMapper: .mockNoOp()), eventOutput: RUMEventOutput = RUMEventOutputMock(), rumUUIDGenerator: RUMUUIDGenerator = DefaultRUMUUIDGenerator(), @@ -642,6 +643,7 @@ extension RUMScopeDependencies { userInfoProvider: userInfoProvider, launchTimeProvider: launchTimeProvider, connectivityInfoProvider: connectivityInfoProvider, + serviceName: serviceName, eventBuilder: eventBuilder, eventOutput: eventOutput, rumUUIDGenerator: rumUUIDGenerator, @@ -660,6 +662,7 @@ extension RUMScopeDependencies { userInfoProvider: RUMUserInfoProvider? = nil, launchTimeProvider: LaunchTimeProviderType? = nil, connectivityInfoProvider: RUMConnectivityInfoProvider? = nil, + serviceName: String? = nil, eventBuilder: RUMEventBuilder? = nil, eventOutput: RUMEventOutput? = nil, rumUUIDGenerator: RUMUUIDGenerator? = nil, @@ -672,6 +675,7 @@ extension RUMScopeDependencies { userInfoProvider: userInfoProvider ?? self.userInfoProvider, launchTimeProvider: launchTimeProvider ?? self.launchTimeProvider, connectivityInfoProvider: connectivityInfoProvider ?? self.connectivityInfoProvider, + serviceName: serviceName ?? self.serviceName, eventBuilder: eventBuilder ?? self.eventBuilder, eventOutput: eventOutput ?? self.eventOutput, rumUUIDGenerator: rumUUIDGenerator ?? self.rumUUIDGenerator, diff --git a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScopeTests.swift b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScopeTests.swift index f0864ba148..fabe884c92 100644 --- a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScopeTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMResourceScopeTests.swift @@ -9,7 +9,11 @@ import XCTest class RUMResourceScopeTests: XCTestCase { private let output = RUMEventOutputMock() - private lazy var dependencies: RUMScopeDependencies = .mockWith(eventOutput: output) + private let randomServiceName: String = .mockRandom() + private lazy var dependencies: RUMScopeDependencies = .mockWith( + serviceName: randomServiceName, + eventOutput: output + ) private let context = RUMContext.mockWith( rumApplicationID: "rum-123", sessionID: .mockRandom(), @@ -99,6 +103,7 @@ class RUMResourceScopeTests: XCTestCase { XCTAssertEqual(event.model.dd.traceId, "100") XCTAssertEqual(event.model.dd.spanId, "200") XCTAssertEqual(event.model.dd.session?.plan, .plan1, "All RUM events should use RUM Lite plan") + XCTAssertEqual(event.model.service, randomServiceName) } func testGivenStartedResource_whenResourceLoadingEnds_itSendsResourceEventWithCustomSpanAndTraceId() throws { @@ -162,6 +167,8 @@ class RUMResourceScopeTests: XCTestCase { XCTAssertEqual(event.model.context?.contextInfo as? [String: String], ["foo": "bar"]) XCTAssertEqual(event.model.dd.traceId, "100") XCTAssertEqual(event.model.dd.spanId, "200") + XCTAssertEqual(event.model.source, .ios) + XCTAssertEqual(event.model.service, randomServiceName) } func testGivenStartedResource_whenResourceLoadingEndsWithError_itSendsErrorEvent() throws { @@ -216,6 +223,8 @@ class RUMResourceScopeTests: XCTestCase { XCTAssertEqual(try XCTUnwrap(event.model.action?.id), context.activeUserActionID?.toRUMDataFormat) XCTAssertEqual(event.model.context?.contextInfo as? [String: String], ["foo": "bar"]) XCTAssertEqual(event.model.dd.session?.plan, .plan1, "All RUM events should use RUM Lite plan") + XCTAssertEqual(event.model.source, .ios) + XCTAssertEqual(event.model.service, randomServiceName) } func testGivenStartedResource_whenResourceReceivesMetricsBeforeItEnds_itUsesMetricValuesInSentResourceEvent() throws { @@ -324,6 +333,8 @@ class RUMResourceScopeTests: XCTestCase { XCTAssertEqual(event.model.context?.contextInfo as? [String: String], ["foo": "bar"]) XCTAssertNil(event.model.dd.traceId) XCTAssertNil(event.model.dd.spanId) + XCTAssertEqual(event.model.source, .ios) + XCTAssertEqual(event.model.service, randomServiceName) } func testGivenMultipleResourceScopes_whenSendingResourceEvents_eachEventHasUniqueResourceID() throws { diff --git a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMUserActionScopeTests.swift b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMUserActionScopeTests.swift index 6af0221066..73243e81ff 100644 --- a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMUserActionScopeTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMUserActionScopeTests.swift @@ -9,7 +9,11 @@ import XCTest class RUMUserActionScopeTests: XCTestCase { private let output = RUMEventOutputMock() - private lazy var dependencies: RUMScopeDependencies = .mockWith(eventOutput: output) + private let randomServiceName: String = .mockRandom() + private lazy var dependencies: RUMScopeDependencies = .mockWith( + serviceName: randomServiceName, + eventOutput: output + ) private let parent = RUMContextProviderMock( context: .mockWith( rumApplicationID: "rum-123", @@ -59,6 +63,7 @@ class RUMUserActionScopeTests: XCTestCase { XCTAssertEqual(recordedAction.model.action.type.rawValue, String(describing: mockUserActionCmd.actionType)) XCTAssertEqual(recordedAction.model.dd.session?.plan, .plan1, "All RUM events should use RUM Lite plan") XCTAssertEqual(recordedAction.model.source, .ios) + XCTAssertEqual(recordedAction.model.service, randomServiceName) } // MARK: - Continuous User Action @@ -103,6 +108,8 @@ class RUMUserActionScopeTests: XCTestCase { XCTAssertEqual(event.model.action.resource?.count, 0) XCTAssertEqual(event.model.action.error?.count, 0) XCTAssertEqual(event.model.context?.contextInfo as? [String: String], ["foo": "bar"]) + XCTAssertEqual(event.model.source, .ios) + XCTAssertEqual(event.model.service, randomServiceName) } func testWhenContinuousUserActionExpires_itSendsActionEvent() throws { diff --git a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift index 2751657b3a..db347a1f6b 100644 --- a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift @@ -11,7 +11,11 @@ import UIKit class RUMViewScopeTests: XCTestCase { private let output = RUMEventOutputMock() private let parent = RUMContextProviderMock() - private lazy var dependencies: RUMScopeDependencies = .mockWith(eventOutput: output) + private let randomServiceName: String = .mockRandom() + private lazy var dependencies: RUMScopeDependencies = .mockWith( + serviceName: randomServiceName, + eventOutput: output + ) func testDefaultContext() { let applicationScope: RUMApplicationScope = .mockWith(rumApplicationID: "rum-123") @@ -66,9 +70,8 @@ class RUMViewScopeTests: XCTestCase { let scope = RUMViewScope( isInitialView: true, parent: parent, - dependencies: .mockWith( - launchTimeProvider: LaunchTimeProviderMock(launchTime: 2), // 2 seconds - eventOutput: output + dependencies: dependencies.replacing( + launchTimeProvider: LaunchTimeProviderMock(launchTime: 2) // 2 seconds ), identity: mockView, path: "UIViewController", @@ -93,6 +96,7 @@ class RUMViewScopeTests: XCTestCase { XCTAssertEqual(event.model.action.loadingTime, 2_000_000_000) // 2e+9 ns XCTAssertEqual(event.model.dd.session?.plan, .plan1, "All RUM events should use RUM Lite plan") XCTAssertEqual(event.model.source, .ios) + XCTAssertEqual(event.model.service, randomServiceName) } func testWhenInitialViewReceivesAnyCommand_itSendsViewUpdateEvent() throws { @@ -127,6 +131,8 @@ class RUMViewScopeTests: XCTestCase { XCTAssertEqual(event.model.view.resource.count, 0) XCTAssertEqual(event.model.dd.documentVersion, 1) XCTAssertEqual(event.model.dd.session?.plan, .plan1, "All RUM events should use RUM Lite plan") + XCTAssertEqual(event.model.source, .ios) + XCTAssertEqual(event.model.service, randomServiceName) } func testWhenViewIsStarted_itSendsViewUpdateEvent() throws { @@ -166,6 +172,8 @@ class RUMViewScopeTests: XCTestCase { XCTAssertEqual(event.model.view.resource.count, 0) XCTAssertEqual(event.model.dd.documentVersion, 1) XCTAssertEqual(event.model.context?.contextInfo as? [String: String], ["foo": "bar 2", "fizz": "buzz"]) + XCTAssertEqual(event.model.source, .ios) + XCTAssertEqual(event.model.service, randomServiceName) } func testWhenViewIsStopped_itSendsViewUpdateEvent_andEndsTheScope() throws { @@ -218,6 +226,8 @@ class RUMViewScopeTests: XCTestCase { XCTAssertEqual(event.model.view.resource.count, 0) XCTAssertEqual(event.model.dd.documentVersion, 2) XCTAssertEqual(event.model.context?.contextInfo as? [String: String], ["foo": "bar"]) + XCTAssertEqual(event.model.source, .ios) + XCTAssertEqual(event.model.service, randomServiceName) } func testWhenAnotherViewIsStarted_itEndsTheScope() throws { @@ -587,6 +597,8 @@ class RUMViewScopeTests: XCTestCase { let firstActionEvent = try XCTUnwrap(output.recordedEvents(ofType: RUMEvent.self).first) XCTAssertEqual(lastViewEvent.model.view.action.count, 1, "View should record 1 only custom action (pending action is not yet finished)") XCTAssertEqual(firstActionEvent.model.action.target?.name, customActionName) + XCTAssertEqual(firstActionEvent.model.source, .ios) + XCTAssertEqual(firstActionEvent.model.service, randomServiceName) } func testGivenViewWithNoPendingAction_whenCustomActionIsAdded_itSendsItInstantly() throws { @@ -620,6 +632,8 @@ class RUMViewScopeTests: XCTestCase { let firstActionEvent = try XCTUnwrap(output.recordedEvents(ofType: RUMEvent.self).first) XCTAssertEqual(lastViewEvent.model.view.action.count, 1, "View should record custom action") XCTAssertEqual(firstActionEvent.model.action.target?.name, customActionName) + XCTAssertEqual(firstActionEvent.model.source, .ios) + XCTAssertEqual(firstActionEvent.model.service, randomServiceName) } // MARK: - Error Tracking @@ -672,6 +686,8 @@ class RUMViewScopeTests: XCTestCase { XCTAssertNil(error.model.action) XCTAssertEqual(error.model.context?.contextInfo as? [String: String], ["foo": "bar"]) XCTAssertEqual(error.model.dd.session?.plan, .plan1, "All RUM events should use RUM Lite plan") + XCTAssertEqual(error.model.source, .ios) + XCTAssertEqual(error.model.service, randomServiceName) let viewUpdate = try XCTUnwrap(output.recordedEvents(ofType: RUMEvent.self).last) XCTAssertEqual(viewUpdate.model.view.error.count, 1) @@ -700,12 +716,15 @@ class RUMViewScopeTests: XCTestCase { ) let error = try XCTUnwrap(output.recordedEvents(ofType: RUMEvent.self).last) - XCTAssertEqual(error.model.error.sourceType, .reactNative) XCTAssertTrue(error.model.error.isCrash ?? false) + XCTAssertEqual(error.model.source, .ios) + XCTAssertEqual(error.model.service, randomServiceName) let viewUpdate = try XCTUnwrap(output.recordedEvents(ofType: RUMEvent.self).last) XCTAssertEqual(viewUpdate.model.view.error.count, 1) + XCTAssertEqual(viewUpdate.model.source, .ios) + XCTAssertEqual(viewUpdate.model.service, randomServiceName) } func testWhenResourceIsFinishedWithError_itSendsViewUpdateEvent() throws { @@ -792,6 +811,7 @@ class RUMViewScopeTests: XCTestCase { XCTAssertTrue(longTask.longTask.isFrozenFrame == true) XCTAssertEqual(longTask.view.id, scope.viewUUID.toRUMDataFormat) XCTAssertNil(longTask.synthetics) + XCTAssertEqual(longTask.service, randomServiceName) let viewUpdate = try XCTUnwrap(output.recordedEvents(ofType: RUMEvent.self).last) XCTAssertEqual(viewUpdate.model.view.longTask?.count, 1) diff --git a/Tests/DatadogTests/Datadog/RUMMonitorTests.swift b/Tests/DatadogTests/Datadog/RUMMonitorTests.swift index babb66296c..df350b738e 100644 --- a/Tests/DatadogTests/Datadog/RUMMonitorTests.swift +++ b/Tests/DatadogTests/Datadog/RUMMonitorTests.swift @@ -27,8 +27,12 @@ class RUMMonitorTests: XCTestCase { func testStartingViewIdentifiedByViewController() throws { let dateProvider = RelativeDateProvider(startingFrom: Date(), advancingBySeconds: 1) + let randomServiceName: String = .mockRandom() RUMFeature.instance = .mockByRecordingRUMEventMatchers( directories: temporaryFeatureDirectories, + configuration: .mockWith( + common: .mockWith(serviceName: randomServiceName) + ), dependencies: .mockWith( dateProvider: dateProvider ) @@ -46,16 +50,20 @@ class RUMMonitorTests: XCTestCase { verifyGlobalAttributes(in: rumEventMatchers) try rumEventMatchers[0].model(ofType: RUMActionEvent.self) { rumModel in XCTAssertEqual(rumModel.action.type, .applicationStart) + XCTAssertEqual(rumModel.service, randomServiceName) } try rumEventMatchers[1].model(ofType: RUMViewEvent.self) { rumModel in XCTAssertEqual(rumModel.view.action.count, 1) + XCTAssertEqual(rumModel.service, randomServiceName) } try rumEventMatchers[2].model(ofType: RUMViewEvent.self) { rumModel in XCTAssertEqual(rumModel.view.action.count, 1) XCTAssertEqual(rumModel.view.timeSpent, 1_000_000_000) + XCTAssertEqual(rumModel.service, randomServiceName) } try rumEventMatchers[3].model(ofType: RUMViewEvent.self) { rumModel in XCTAssertEqual(rumModel.view.action.count, 0) + XCTAssertEqual(rumModel.service, randomServiceName) } } From 11a5d210ab10ba8953d335155f090261552e2177 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 18 Jan 2022 17:28:33 +0100 Subject: [PATCH 29/33] RUMM-1926 Do not print warning when receiving 'keep alive' command while no view is active as this scenario is very likely to happen when tracking WebViews and it doesn't indicate user mistake. --- .../RUMMonitor/Scopes/RUMSessionScope.swift | 18 ++++--- .../Scopes/RUMSessionScopeTests.swift | 52 ++++++++++++------- 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScope.swift index d460da5f31..d9b1022196 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScope.swift @@ -146,14 +146,16 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { case .handleInBackgroundView where command.canStartBackgroundView: startBackgroundView(on: command) default: - // As no view scope will handle this command, warn the user on dropping it - userLogger.warn( - """ - \(String(describing: command)) was detected, but no view is active. To track views automatically, try calling the - DatadogConfiguration.Builder.trackUIKitRUMViews() method. You can also track views manually using - the RumMonitor.startView() and RumMonitor.stopView() methods. - """ - ) + if !(command is RUMKeepSessionAliveCommand) { // it is expected to receive 'keep alive' while no active view (when tracking WebView events) + // As no view scope will handle this command, warn the user on dropping it. + userLogger.warn( + """ + \(String(describing: command)) was detected, but no view is active. To track views automatically, try calling the + DatadogConfiguration.Builder.trackUIKitRUMViews() method. You can also track views manually using + the RumMonitor.startView() and RumMonitor.stopView() methods. + """ + ) + } } } diff --git a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScopeTests.swift b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScopeTests.swift index 3781bd8172..88b9af541c 100644 --- a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScopeTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScopeTests.swift @@ -444,31 +444,43 @@ class RUMSessionScopeTests: XCTestCase { // MARK: - Usage - func testWhenNoActiveViewScopes_itLogsWarning() { - // Given - let scope: RUMSessionScope = .mockWith(parent: parent, startTime: Date()) - XCTAssertEqual(scope.viewScopes.count, 0) - - let previousUserLogger = userLogger - defer { userLogger = previousUserLogger } - - let logOutput = LogOutputMock() - userLogger = .mockWith(logOutput: logOutput) - - let command = RUMCommandMock(time: Date(), canStartBackgroundView: false) - - // When - _ = scope.process(command: command) - - // Then - XCTAssertEqual(scope.viewScopes.count, 0) + func testGivenSessionWithNoActiveScope_whenReceivingRUMCommandOtherThanKeepSessionAliveCommand_itLogsWarning() throws { + func recordLogOnReceiving(command: RUMCommand) -> LogEvent? { + // Given + let scope: RUMSessionScope = .mockWith( + parent: parent, + startTime: Date() + ) + XCTAssertEqual(scope.viewScopes.count, 0) + + let previousUserLogger = userLogger + defer { userLogger = previousUserLogger } + + let logOutput = LogOutputMock() + userLogger = .mockWith(logOutput: logOutput) + + // When + _ = scope.process(command: command) + + // Then + XCTAssertEqual(scope.viewScopes.count, 0) + return logOutput.recordedLog + } + + let randomCommand = RUMCommandMock(time: Date(), canStartBackgroundView: false, canStartApplicationLaunchView: false) + let randomCommandLog = try XCTUnwrap(recordLogOnReceiving(command: randomCommand)) + XCTAssertEqual(randomCommandLog.status, .warn) XCTAssertEqual( - logOutput.recordedLog?.message, + randomCommandLog.message, """ - \(String(describing: command)) was detected, but no view is active. To track views automatically, try calling the + \(String(describing: randomCommand)) was detected, but no view is active. To track views automatically, try calling the DatadogConfiguration.Builder.trackUIKitRUMViews() method. You can also track views manually using the RumMonitor.startView() and RumMonitor.stopView() methods. """ ) + + let keepAliveCommand = RUMKeepSessionAliveCommand(time: Date(), attributes: [:]) + let keepAliveLog = recordLogOnReceiving(command: keepAliveCommand) + XCTAssertNil(keepAliveLog, "It shouldn't log warning when receiving `RUMKeepSessionAliveCommand`") } } From 6f71abe158a30e27764e3bb28e3cb69e379d0d2a Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Wed, 19 Jan 2022 10:32:15 +0100 Subject: [PATCH 30/33] RUMM-1911 Resolve remaining TODO as it references obsolete task --- .../Datadog/RUM/WebView/WebRUMEventConsumerTests.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift index 0ef4c6fd6c..7493490ac2 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift @@ -7,7 +7,6 @@ import XCTest @testable import Datadog -// TODO: RUMM-1786 test mutations (session_id, application_id, date) class WebRUMEventConsumerTests: XCTestCase { let mockWriter = FileWriterMock() let mockDateCorrector = DateCorrectorMock() From 0a4a5a21972c6e3534c7edb1ad34ab5017a650e4 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Wed, 19 Jan 2022 10:48:52 +0100 Subject: [PATCH 31/33] RUMM-1911 Condition WebViews tracking with `DD_SDK_ENABLE_EXPERIMENTAL_APIS` compiler flag to not expose it in releases until validated. It will be still available in dogfood projects. --- Makefile | 3 ++- .../WebView/WKUserContentController+Datadog.swift | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d970ffe696..88cedc0266 100644 --- a/Makefile +++ b/Makefile @@ -17,8 +17,9 @@ export DD_SDK_TESTING_XCCONFIG_CI define DD_SDK_BASE_XCCONFIG // Active compilation conditions - only enabled on local machine:\n // - DD_SDK_ENABLE_INTERNAL_MONITORING - enables Internal Monitoring APIs\n +// - DD_SDK_ENABLE_EXPERIMENTAL_APIS - enables APIs which are not available in released version of the SDK\n // - DD_SDK_COMPILED_FOR_TESTING - conditions the SDK code compiled for testing\n -SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) DD_SDK_ENABLE_INTERNAL_MONITORING DD_SDK_COMPILED_FOR_TESTING\n +SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) DD_SDK_ENABLE_INTERNAL_MONITORING DD_SDK_ENABLE_EXPERIMENTAL_APIS DD_SDK_COMPILED_FOR_TESTING\n \n // To build only active architecture for all configurations. This gives us ~10% build time gain\n // in targets which do not use 'Debug' configuration.\n diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift index 80e335615e..9f521cf6db 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift @@ -4,6 +4,7 @@ * Copyright 2019-2020 Datadog, Inc. */ +#if DD_SDK_ENABLE_EXPERIMENTAL_APIS import Foundation import WebKit @@ -109,3 +110,4 @@ internal class DatadogMessageHandler: NSObject, WKScriptMessageHandler { } } } +#endif From 9cc0270fe67753d2651383636f21cb3a0175ebb7 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Wed, 19 Jan 2022 11:06:50 +0100 Subject: [PATCH 32/33] RUMM-1911 Rename public API for WebView tracking feature + add docs string --- .../Debugging/DebugWebviewViewController.swift | 2 +- .../WebViewTrackingFixtureViewController.swift | 2 +- .../WebView/WKUserContentController+Datadog.swift | 13 +++++++++---- .../WKUserContentController+DatadogTests.swift | 6 +++--- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Datadog/Example/Debugging/DebugWebviewViewController.swift b/Datadog/Example/Debugging/DebugWebviewViewController.swift index 5c5247957f..ce0dbf485d 100644 --- a/Datadog/Example/Debugging/DebugWebviewViewController.swift +++ b/Datadog/Example/Debugging/DebugWebviewViewController.swift @@ -85,7 +85,7 @@ class WebviewViewController: UIViewController { super.viewDidLoad() let controller = WKUserContentController() - controller.addDatadogMessageHandler(allowedWebViewHosts: [request.url!.host!]) + controller.trackDatadogEvents(in: [request.url!.host!]) let config = WKWebViewConfiguration() config.userContentController = controller diff --git a/Datadog/Example/Scenarios/WebView/WebViewTrackingFixtureViewController.swift b/Datadog/Example/Scenarios/WebView/WebViewTrackingFixtureViewController.swift index 8ccdeb1532..2e5de7355d 100644 --- a/Datadog/Example/Scenarios/WebView/WebViewTrackingFixtureViewController.swift +++ b/Datadog/Example/Scenarios/WebView/WebViewTrackingFixtureViewController.swift @@ -28,7 +28,7 @@ class ShopistWebviewViewController: UIViewController { super.viewDidLoad() let controller = WKUserContentController() - controller.addDatadogMessageHandler(allowedWebViewHosts: ["shopist.io"]) + controller.trackDatadogEvents(in: ["shopist.io"]) let config = WKWebViewConfiguration() config.userContentController = controller diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift index 9f521cf6db..7f5d5f2378 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift @@ -8,13 +8,18 @@ import Foundation import WebKit -// TODO: RUMM-1794 rename the methods public extension WKUserContentController { - func addDatadogMessageHandler(allowedWebViewHosts: Set) { - __addDatadogMessageHandler(allowedWebViewHosts: allowedWebViewHosts, hostsSanitizer: HostsSanitizer()) + /// Enables SDK to correlate Datadog RUM events and Logs from the WebView with native RUM session. + /// + /// If the content loaded in WebView uses Datadog Browser SDK (`v4.2.0+`) and matches specified `hosts`, web events will be correlated + /// with the RUM session from native SDK. + /// + /// - Parameter hosts: a list of hosts instrumented with Browser SDK to capture Datadog events from + func trackDatadogEvents(in hosts: Set) { + addDatadogMessageHandler(allowedWebViewHosts: hosts, hostsSanitizer: HostsSanitizer()) } - internal func __addDatadogMessageHandler(allowedWebViewHosts: Set, hostsSanitizer: HostsSanitizing) { + internal func addDatadogMessageHandler(allowedWebViewHosts: Set, hostsSanitizer: HostsSanitizing) { let bridgeName = DatadogMessageHandler.name let globalRUMMonitor = Global.rum as? RUMMonitor diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WKUserContentController+DatadogTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WKUserContentController+DatadogTests.swift index 8188fd2fe8..b134ec73f6 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/WKUserContentController+DatadogTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WKUserContentController+DatadogTests.swift @@ -50,7 +50,7 @@ class WKUserContentController_DatadogTests: XCTestCase { let initialUserScriptCount = controller.userScripts.count - controller.__addDatadogMessageHandler(allowedWebViewHosts: ["datadoghq.com"], hostsSanitizer: mockSanitizer) + controller.addDatadogMessageHandler(allowedWebViewHosts: ["datadoghq.com"], hostsSanitizer: mockSanitizer) XCTAssertEqual(controller.userScripts.count, initialUserScriptCount + 1) XCTAssertEqual(controller.messageHandlers.map({ $0.name }), ["DatadogEventBridge"]) @@ -68,7 +68,7 @@ class WKUserContentController_DatadogTests: XCTestCase { userLogger = .mockWith(logOutput: output) let controller = DDUserContentController() - controller.__addDatadogMessageHandler(allowedWebViewHosts: ["datadoghq.com"], hostsSanitizer: MockHostsSanitizer()) + controller.addDatadogMessageHandler(allowedWebViewHosts: ["datadoghq.com"], hostsSanitizer: MockHostsSanitizer()) let messageHandler = try XCTUnwrap(controller.messageHandlers.first?.handler) as? DatadogMessageHandler // non-string body is passed @@ -111,7 +111,7 @@ class WKUserContentController_DatadogTests: XCTestCase { } let controller = DDUserContentController() - controller.__addDatadogMessageHandler( + controller.addDatadogMessageHandler( allowedWebViewHosts: ["datadoghq.com"], hostsSanitizer: MockHostsSanitizer() ) From 0e80427cbece0cb65d9badfcccca77b860347878 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Wed, 19 Jan 2022 11:24:56 +0100 Subject: [PATCH 33/33] RUMM-1911 Add strictier assertion on `.source` attribute for Browser SDK events - now possible after https://github.com/DataDog/browser-sdk/pull/1271 --- .../Scenarios/WebView/WebViewScenarioTest.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/DatadogIntegrationTests/Scenarios/WebView/WebViewScenarioTest.swift b/Tests/DatadogIntegrationTests/Scenarios/WebView/WebViewScenarioTest.swift index ea73557087..f3402a48ae 100644 --- a/Tests/DatadogIntegrationTests/Scenarios/WebView/WebViewScenarioTest.swift +++ b/Tests/DatadogIntegrationTests/Scenarios/WebView/WebViewScenarioTest.swift @@ -58,14 +58,14 @@ class WebViewScenarioTest: IntegrationTests, RUMCommonAsserts { XCTAssertEqual(browserViewEvent.application.id, expectedBrowserRUMApplicationID, "Webview events should use iOS SDK application ID") XCTAssertEqual(browserViewEvent.session.id, expectedBrowserSessionID, "Webview events should use iOS SDK session ID") XCTAssertEqual(browserViewEvent.service, expectedBrowserServiceName, "Webview events should use Browser SDK `service`") - XCTAssertNotEqual(browserViewEvent.source, .ios, "Webview events should use Browser SDK `source`") + XCTAssertEqual(browserViewEvent.source, .browser, "Webview events should use Browser SDK `source`") } XCTAssertGreaterThan(browserView.resourceEvents.count, 0, "It should track some Webview resources") browserView.resourceEvents.forEach { browserResourceEvent in XCTAssertEqual(browserResourceEvent.application.id, expectedBrowserRUMApplicationID, "Webview events should use iOS SDK application ID") XCTAssertEqual(browserResourceEvent.session.id, expectedBrowserSessionID, "Webview events should use iOS SDK session ID") XCTAssertEqual(browserResourceEvent.service, expectedBrowserServiceName, "Webview events should use Browser SDK `service`") - XCTAssertNotEqual(browserResourceEvent.source, .ios, "Webview events should use Browser SDK `source`") + XCTAssertEqual(browserResourceEvent.source, .browser, "Webview events should use Browser SDK `source`") } // Get `LogMatchers`