Skip to content

Commit

Permalink
Merge pull request #729 from DataDog/feature/hybrid-application
Browse files Browse the repository at this point in the history
  • Loading branch information
ncreated authored Jan 19, 2022
2 parents 85f9785 + b0e30f9 commit ba835f7
Show file tree
Hide file tree
Showing 40 changed files with 1,539 additions and 178 deletions.
88 changes: 88 additions & 0 deletions Datadog/Datadog.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@
value = "CrashReportingCollectOrSendWithLoggingScenario"
isEnabled = "NO">
</EnvironmentVariable>
<EnvironmentVariable
key = "DD_TEST_SCENARIO_CLASS_NAME"
value = "WebViewTrackingScenario"
isEnabled = "NO">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
Expand Down
12 changes: 8 additions & 4 deletions Datadog/Example/Debugging/DebugWebviewViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
*/

import UIKit
import Datadog
import WebKit
import Datadog

class DebugWebviewViewController: UIViewController {
@IBOutlet weak var rumServiceNameTextField: UITextField!
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -84,8 +84,12 @@ class WebviewViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()

let configuration = WKWebViewConfiguration()
webView = WKWebView(frame: UIScreen.main.bounds, configuration: configuration)
let controller = WKUserContentController()
controller.trackDatadogEvents(in: [request.url!.host!])
let config = WKWebViewConfiguration()
config.userContentController = controller

webView = WKWebView(frame: UIScreen.main.bounds, configuration: config)
view.addSubview(webView)
}

Expand Down
31 changes: 31 additions & 0 deletions Datadog/Example/Scenarios/WebView/WebViewScenarios.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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 UIKit
import Datadog

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(using: WebViewTrackingScenarioPredicate())
.enableLogging(true)
.enableRUM(true)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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 UIKit
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() {
super.viewDidLoad()

let controller = WKUserContentController()
controller.trackDatadogEvents(in: ["shopist.io"])
let config = WKWebViewConfiguration()
config.userContentController = controller

webView = WKWebView(frame: UIScreen.main.bounds, configuration: config)
view.addSubview(webView)
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
webView.load(request)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Y6W-OH-hqX">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Web View Tracking Fixture View Controller-->
<scene sceneID="s0d-6b-0kx">
<objects>
<viewController id="Y6W-OH-hqX" customClass="WebViewTrackingFixtureViewController" customModule="Example" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="5EZ-qb-Rvc">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<viewLayoutGuide key="safeArea" id="vDu-zF-Fre"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Ief-a0-LHa" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="114" y="80"/>
</scene>
</scenes>
<resources>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
51 changes: 5 additions & 46 deletions Sources/Datadog/Core/FeaturesConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,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?
Expand Down Expand Up @@ -231,7 +231,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,
Expand Down Expand Up @@ -318,47 +321,3 @@ private func ifValid(clientToken: String) throws -> String {
}
return clientToken
}

private func sanitized(firstPartyHosts: Set<String>) -> Set<String> {
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)
}
57 changes: 57 additions & 0 deletions Sources/Datadog/Core/Utils/HostsSanitizer.swift
Original file line number Diff line number Diff line change
@@ -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<String>, warningMessage: String) -> Set<String>
}

internal struct HostsSanitizer: HostsSanitizing {
func sanitized(hosts: Set<String>, warningMessage: String) -> Set<String> {
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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ internal struct CrashReportingWithRUMIntegration: CrashReportingIntegration {
),
context: nil,
date: startDate.timeIntervalSince1970.toInt64Milliseconds,
service: nil,
service: rumConfiguration.common.serviceName,
session: .init(
hasReplay: nil,
id: sessionUUID.toRUMDataFormat,
Expand Down
Loading

0 comments on commit ba835f7

Please sign in to comment.