Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Breaking API change: applications can set a User-Agent prefix #56

Merged
merged 4 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Demo/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
.file(Bundle.main.url(forResource: "path-configuration", withExtension: "json")!)
])

// Set an optional custom user agent application prefix.
Hotwire.config.applicationUserAgentPrefix = "Hotwire Demo;"

// Register bridge components
Hotwire.registerBridgeComponents([
FormComponent.self,
Expand Down
13 changes: 10 additions & 3 deletions Source/Bridge/UserAgent.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import Foundation

public enum UserAgent {
public static func userAgentSubstring(for componentTypes: [BridgeComponent.Type]) -> String {
enum UserAgent {
static func build(applicationPrefix: String?, componentTypes: [BridgeComponent.Type]) -> String {
let components = componentTypes.map { $0.name }.joined(separator: " ")
return "bridge-components: [\(components)]"
let componentsSubstring = "bridge-components: [\(components)]"

return [
applicationPrefix,
"Hotwire Native iOS;",
"Turbo Native iOS;",
componentsSubstring
].compactMap { $0 }.joined(separator: " ")
joemasilotti marked this conversation as resolved.
Show resolved Hide resolved
}
}
1 change: 0 additions & 1 deletion Source/Hotwire.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ public enum Hotwire {
/// Use `Hotwire.config.makeCustomWebView` to customize the web view or web view
/// configuration further, making sure to call `Bridge.initialize()`.
public static func registerBridgeComponents(_ componentTypes: [BridgeComponent.Type]) {
Hotwire.config.userAgent += " \(UserAgent.userAgentSubstring(for: componentTypes))"
bridgeComponentTypes = componentTypes

Hotwire.config.makeCustomWebView = { configuration in
Expand Down
19 changes: 14 additions & 5 deletions Source/HotwireConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ import WebKit
public struct HotwireConfig {
public typealias WebViewBlock = (_ configuration: WKWebViewConfiguration) -> WKWebView

/// Override to set a custom user agent.
/// - Important: Include "Hotwire Native" or "Turbo Native" to use `turbo_native_app?`
/// on your Rails server.
public var userAgent = "Hotwire Native iOS; Turbo Native iOS"
/// Set a custom user agent application prefix for every WKWebView instance.
///
/// The library will automatically append a substring to your prefix
/// which includes:
/// - "Hotwire Native iOS; Turbo Native iOS;"
/// - "bridge-components: [your bridge components];"
///
/// WKWebView's default user agent string will also appear at the
/// beginning of the user agent.
public var applicationUserAgentPrefix: String? = nil

/// When enabled, adds a `UIBarButtonItem` of type `.done` to the left
/// navigation bar button item on screens presented modally.
Expand Down Expand Up @@ -73,7 +79,10 @@ public struct HotwireConfig {
private func makeWebViewConfiguration() -> WKWebViewConfiguration {
let configuration = WKWebViewConfiguration()
configuration.defaultWebpagePreferences?.preferredContentMode = .mobile
configuration.applicationNameForUserAgent = userAgent
configuration.applicationNameForUserAgent = UserAgent.build(
applicationPrefix: applicationUserAgentPrefix,
componentTypes: Hotwire.bridgeComponentTypes
)
configuration.processPool = sharedProcessPool
return configuration
}
Expand Down
26 changes: 20 additions & 6 deletions Tests/Bridge/UserAgentTests.swift
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
import Foundation
import HotwireNative
@testable import HotwireNative
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@testable is needed to access the (now) internal UserAgent function.

import XCTest

class UserAgentTests: XCTestCase {
func testUserAgentSubstringWithNoComponents() {
let userAgentSubstring = UserAgent.build(
applicationPrefix: nil,
componentTypes: []
)
XCTAssertEqual(userAgentSubstring, "Hotwire Native iOS; Turbo Native iOS; bridge-components: []")
}

func testUserAgentSubstringWithTwoComponents() {
let userAgentSubstring = UserAgent.userAgentSubstring(for: [OneBridgeComponent.self, TwoBridgeComponent.self])
XCTAssertEqual(userAgentSubstring, "bridge-components: [one two]")
let userAgentSubstring = UserAgent.build(
applicationPrefix: nil,
componentTypes: [OneBridgeComponent.self, TwoBridgeComponent.self]
)
XCTAssertEqual(userAgentSubstring, "Hotwire Native iOS; Turbo Native iOS; bridge-components: [one two]")
}

func testUserAgentSubstringWithNoComponents() {
let userAgentSubstring = UserAgent.userAgentSubstring(for: [])
XCTAssertEqual(userAgentSubstring, "bridge-components: []")
func testUserAgentSubstringCustomPrefix() {
let userAgentSubstring = UserAgent.build(
applicationPrefix: "Hotwire Demo;",
componentTypes: [OneBridgeComponent.self, TwoBridgeComponent.self]
)
XCTAssertEqual(userAgentSubstring, "Hotwire Demo; Hotwire Native iOS; Turbo Native iOS; bridge-components: [one two]")
}
}