From 9974743bd29953544dee0c5d3fb960af9340d409 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 13 Dec 2025 13:07:39 +0000 Subject: [PATCH 01/24] Initial plan From 3ff1cd93519e56147080bb59b0585fb988af170c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 13 Dec 2025 13:14:41 +0000 Subject: [PATCH 02/24] Add custom user agent to iOS and Android WebViews Co-authored-by: dcalhoun <438664+dcalhoun@users.noreply.github.com> --- .../main/java/org/wordpress/gutenberg/GutenbergView.kt | 6 ++++++ .../GutenbergKit/Sources/EditorViewController.swift | 1 + ios/Sources/GutenbergKit/Sources/GBWebView.swift | 10 ++++++++++ 3 files changed, 17 insertions(+) diff --git a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt index 6ea3abea..b284eefd 100644 --- a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt +++ b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt @@ -125,6 +125,11 @@ class GutenbergView : WebView { this.settings.javaScriptCanOpenWindowsAutomatically = true this.settings.javaScriptEnabled = true this.settings.domStorageEnabled = true + + // Set custom user agent + val defaultUserAgent = this.settings.userAgentString + this.settings.userAgentString = "$defaultUserAgent GutenbergKit/$VERSION" + this.addJavascriptInterface(this, "editorDelegate") this.visibility = View.GONE @@ -720,6 +725,7 @@ class GutenbergView : WebView { companion object { private const val ASSET_LOADING_TIMEOUT_MS = 5000L + private const val VERSION = "0.11.1" // Warmup state management private var warmupHandler: Handler? = null diff --git a/ios/Sources/GutenbergKit/Sources/EditorViewController.swift b/ios/Sources/GutenbergKit/Sources/EditorViewController.swift index f684546a..e9738782 100644 --- a/ios/Sources/GutenbergKit/Sources/EditorViewController.swift +++ b/ios/Sources/GutenbergKit/Sources/EditorViewController.swift @@ -177,6 +177,7 @@ public final class EditorViewController: UIViewController, GutenbergEditorContro self.webView = GBWebView(frame: .zero, configuration: config) self.webView.scrollView.keyboardDismissMode = .interactive + self.webView.customUserAgent = GBWebView.createCustomUserAgent() self.isWarmupMode = isWarmupMode diff --git a/ios/Sources/GutenbergKit/Sources/GBWebView.swift b/ios/Sources/GutenbergKit/Sources/GBWebView.swift index 7c036fe8..34d227b6 100644 --- a/ios/Sources/GutenbergKit/Sources/GBWebView.swift +++ b/ios/Sources/GutenbergKit/Sources/GBWebView.swift @@ -1,6 +1,16 @@ import WebKit class GBWebView: WKWebView { + + /// The GutenbergKit version string + static let version = "0.11.1" + + /// Creates a custom user agent string by appending GutenbergKit identifier to the default user agent + static func createCustomUserAgent() -> String { + let webView = WKWebView() + let defaultUserAgent = webView.value(forKey: "userAgent") as? String ?? "" + return "\(defaultUserAgent) GutenbergKit/\(version)" + } #if canImport(UIKit) /// Disables the default bottom bar that competes with the Gutenberg inserter From e41c61b118274353ddadbb55ef0a2cf6dfcaf360 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 13 Dec 2025 13:15:33 +0000 Subject: [PATCH 03/24] Add tests for custom user agent functionality Co-authored-by: dcalhoun <438664+dcalhoun@users.noreply.github.com> --- .../wordpress/gutenberg/GutenbergViewTest.kt | 16 ++++++++++ .../GutenbergKitTests/GBWebViewTests.swift | 32 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 ios/Tests/GutenbergKitTests/GBWebViewTests.swift diff --git a/android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt b/android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt index ede20c98..ba2834a0 100644 --- a/android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt +++ b/android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt @@ -147,4 +147,20 @@ class GutenbergViewTest { assertEquals("File path callback should be null after reset", null, gutenbergView.filePathCallback) } + + @Test + fun `initializeWebView sets custom user agent with GutenbergKit identifier`() { + // Given + val gutenbergView = GutenbergView(RuntimeEnvironment.getApplication()) + + // When + gutenbergView.initializeWebView() + + // Then + val userAgent = gutenbergView.settings.userAgentString + assertTrue("User agent should contain GutenbergKit identifier", + userAgent.contains("GutenbergKit/")) + assertTrue("User agent should contain version number", + userAgent.contains("GutenbergKit/0.11.1")) + } } diff --git a/ios/Tests/GutenbergKitTests/GBWebViewTests.swift b/ios/Tests/GutenbergKitTests/GBWebViewTests.swift new file mode 100644 index 00000000..4ce7ef85 --- /dev/null +++ b/ios/Tests/GutenbergKitTests/GBWebViewTests.swift @@ -0,0 +1,32 @@ +import XCTest +import WebKit +@testable import GutenbergKit + +final class GBWebViewTests: XCTestCase { + + func testCreateCustomUserAgent() { + // When + let customUserAgent = GBWebView.createCustomUserAgent() + + // Then + XCTAssertTrue(customUserAgent.contains("GutenbergKit/"), + "User agent should contain GutenbergKit identifier") + XCTAssertTrue(customUserAgent.contains("GutenbergKit/0.11.1"), + "User agent should contain version number") + } + + func testCustomUserAgentAppendsToDefault() { + // Given + let defaultWebView = WKWebView() + let defaultUserAgent = defaultWebView.value(forKey: "userAgent") as? String ?? "" + + // When + let customUserAgent = GBWebView.createCustomUserAgent() + + // Then + XCTAssertTrue(customUserAgent.hasPrefix(defaultUserAgent), + "Custom user agent should start with default user agent") + XCTAssertTrue(customUserAgent.hasSuffix(" GutenbergKit/0.11.1"), + "Custom user agent should end with GutenbergKit identifier") + } +} From a09b7e93bc4aeba27bdb7a665216ba24c49d2bdd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 13 Dec 2025 13:16:35 +0000 Subject: [PATCH 04/24] Set custom user agent in HTMLWebViewRenderer for consistency Co-authored-by: dcalhoun <438664+dcalhoun@users.noreply.github.com> --- .../Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift b/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift index 991b12a3..ad7ec168 100644 --- a/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift +++ b/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift @@ -94,6 +94,7 @@ final class HTMLWebViewRenderer { // Create web view with small initial frame for off-screen rendering // Frame will be adjusted per render based on viewport width and content height webView = WKWebView(frame: CGRect(x: 0, y: 0, width: 1200, height: 100), configuration: config) + webView.customUserAgent = GBWebView.createCustomUserAgent() delegate = RenderDelegate() webView.navigationDelegate = delegate From 24cc77ec9f167ede896aad52815fa7901c91a5d6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 13 Dec 2025 13:17:40 +0000 Subject: [PATCH 05/24] Cache default user agent and improve test robustness Co-authored-by: dcalhoun <438664+dcalhoun@users.noreply.github.com> --- .../GutenbergKit/Sources/GBWebView.swift | 8 +++++-- .../GutenbergKitTests/GBWebViewTests.swift | 22 +++++++++++++------ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/ios/Sources/GutenbergKit/Sources/GBWebView.swift b/ios/Sources/GutenbergKit/Sources/GBWebView.swift index 34d227b6..57d983dc 100644 --- a/ios/Sources/GutenbergKit/Sources/GBWebView.swift +++ b/ios/Sources/GutenbergKit/Sources/GBWebView.swift @@ -5,10 +5,14 @@ class GBWebView: WKWebView { /// The GutenbergKit version string static let version = "0.11.1" + /// Cached default user agent to avoid repeated WKWebView instantiation + private static let defaultUserAgent: String = { + let webView = WKWebView() + return webView.value(forKey: "userAgent") as? String ?? "" + }() + /// Creates a custom user agent string by appending GutenbergKit identifier to the default user agent static func createCustomUserAgent() -> String { - let webView = WKWebView() - let defaultUserAgent = webView.value(forKey: "userAgent") as? String ?? "" return "\(defaultUserAgent) GutenbergKit/\(version)" } diff --git a/ios/Tests/GutenbergKitTests/GBWebViewTests.swift b/ios/Tests/GutenbergKitTests/GBWebViewTests.swift index 4ce7ef85..acd8e42e 100644 --- a/ios/Tests/GutenbergKitTests/GBWebViewTests.swift +++ b/ios/Tests/GutenbergKitTests/GBWebViewTests.swift @@ -13,20 +13,28 @@ final class GBWebViewTests: XCTestCase { "User agent should contain GutenbergKit identifier") XCTAssertTrue(customUserAgent.contains("GutenbergKit/0.11.1"), "User agent should contain version number") + XCTAssertTrue(customUserAgent.contains("Mozilla"), + "User agent should contain Mozilla identifier from default user agent") } - func testCustomUserAgentAppendsToDefault() { - // Given - let defaultWebView = WKWebView() - let defaultUserAgent = defaultWebView.value(forKey: "userAgent") as? String ?? "" - + func testCustomUserAgentEndsWithGutenbergKitIdentifier() { // When let customUserAgent = GBWebView.createCustomUserAgent() // Then - XCTAssertTrue(customUserAgent.hasPrefix(defaultUserAgent), - "Custom user agent should start with default user agent") XCTAssertTrue(customUserAgent.hasSuffix(" GutenbergKit/0.11.1"), "Custom user agent should end with GutenbergKit identifier") } + + func testCustomUserAgentIsConsistent() { + // Given + let firstCall = GBWebView.createCustomUserAgent() + + // When + let secondCall = GBWebView.createCustomUserAgent() + + // Then + XCTAssertEqual(firstCall, secondCall, + "Custom user agent should be consistent across calls") + } } From 46febb2fa85d0f59b99600fb6f27076a8b7f6cda Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 13 Dec 2025 13:19:20 +0000 Subject: [PATCH 06/24] Use public APIs and version constants in tests Co-authored-by: dcalhoun <438664+dcalhoun@users.noreply.github.com> --- .../org/wordpress/gutenberg/GutenbergView.kt | 2 +- .../wordpress/gutenberg/GutenbergViewTest.kt | 2 +- .../GutenbergKit/Sources/GBWebView.swift | 17 +++++++++++++++-- .../GutenbergKitTests/GBWebViewTests.swift | 4 ++-- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt index b284eefd..99694eb4 100644 --- a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt +++ b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt @@ -725,7 +725,7 @@ class GutenbergView : WebView { companion object { private const val ASSET_LOADING_TIMEOUT_MS = 5000L - private const val VERSION = "0.11.1" + internal const val VERSION = "0.11.1" // Warmup state management private var warmupHandler: Handler? = null diff --git a/android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt b/android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt index ba2834a0..8492c80c 100644 --- a/android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt +++ b/android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt @@ -161,6 +161,6 @@ class GutenbergViewTest { assertTrue("User agent should contain GutenbergKit identifier", userAgent.contains("GutenbergKit/")) assertTrue("User agent should contain version number", - userAgent.contains("GutenbergKit/0.11.1")) + userAgent.contains("GutenbergKit/${GutenbergView.VERSION}")) } } diff --git a/ios/Sources/GutenbergKit/Sources/GBWebView.swift b/ios/Sources/GutenbergKit/Sources/GBWebView.swift index 57d983dc..80fa30f9 100644 --- a/ios/Sources/GutenbergKit/Sources/GBWebView.swift +++ b/ios/Sources/GutenbergKit/Sources/GBWebView.swift @@ -5,10 +5,23 @@ class GBWebView: WKWebView { /// The GutenbergKit version string static let version = "0.11.1" - /// Cached default user agent to avoid repeated WKWebView instantiation + /// Cached default user agent, lazily initialized private static let defaultUserAgent: String = { + // Use a temporary WKWebView to get the default user agent + // We need to use evaluateJavaScript since customUserAgent is write-only let webView = WKWebView() - return webView.value(forKey: "userAgent") as? String ?? "" + let semaphore = DispatchSemaphore(value: 0) + var userAgent = "" + + webView.evaluateJavaScript("navigator.userAgent") { result, error in + if let result = result as? String { + userAgent = result + } + semaphore.signal() + } + + semaphore.wait() + return userAgent }() /// Creates a custom user agent string by appending GutenbergKit identifier to the default user agent diff --git a/ios/Tests/GutenbergKitTests/GBWebViewTests.swift b/ios/Tests/GutenbergKitTests/GBWebViewTests.swift index acd8e42e..f2779eb5 100644 --- a/ios/Tests/GutenbergKitTests/GBWebViewTests.swift +++ b/ios/Tests/GutenbergKitTests/GBWebViewTests.swift @@ -11,7 +11,7 @@ final class GBWebViewTests: XCTestCase { // Then XCTAssertTrue(customUserAgent.contains("GutenbergKit/"), "User agent should contain GutenbergKit identifier") - XCTAssertTrue(customUserAgent.contains("GutenbergKit/0.11.1"), + XCTAssertTrue(customUserAgent.contains("GutenbergKit/\(GBWebView.version)"), "User agent should contain version number") XCTAssertTrue(customUserAgent.contains("Mozilla"), "User agent should contain Mozilla identifier from default user agent") @@ -22,7 +22,7 @@ final class GBWebViewTests: XCTestCase { let customUserAgent = GBWebView.createCustomUserAgent() // Then - XCTAssertTrue(customUserAgent.hasSuffix(" GutenbergKit/0.11.1"), + XCTAssertTrue(customUserAgent.hasSuffix(" GutenbergKit/\(GBWebView.version)"), "Custom user agent should end with GutenbergKit identifier") } From 96efbf700ff74785970d8b60f29263c2ecb517ab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 13 Dec 2025 13:21:12 +0000 Subject: [PATCH 07/24] Cache custom user agent to avoid repeated WebView instantiation Co-authored-by: dcalhoun <438664+dcalhoun@users.noreply.github.com> --- .../GutenbergKit/Sources/GBWebView.swift | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/ios/Sources/GutenbergKit/Sources/GBWebView.swift b/ios/Sources/GutenbergKit/Sources/GBWebView.swift index 80fa30f9..a9bcddb8 100644 --- a/ios/Sources/GutenbergKit/Sources/GBWebView.swift +++ b/ios/Sources/GutenbergKit/Sources/GBWebView.swift @@ -5,28 +5,39 @@ class GBWebView: WKWebView { /// The GutenbergKit version string static let version = "0.11.1" - /// Cached default user agent, lazily initialized - private static let defaultUserAgent: String = { - // Use a temporary WKWebView to get the default user agent - // We need to use evaluateJavaScript since customUserAgent is write-only + /// Cached custom user agent to avoid repeated WebView instantiation + private static var cachedCustomUserAgent: String? + + /// Creates a custom user agent string by appending GutenbergKit identifier to the default user agent + /// The result is cached after the first call to avoid performance overhead + static func createCustomUserAgent() -> String { + // Return cached value if available + if let cached = cachedCustomUserAgent { + return cached + } + + // Get the default user agent by creating a temporary WKWebView let webView = WKWebView() - let semaphore = DispatchSemaphore(value: 0) - var userAgent = "" + var defaultUserAgent = "" + + // Use DispatchGroup for synchronous wait + // This is safe during view initialization as it's not on a critical path + let group = DispatchGroup() + group.enter() webView.evaluateJavaScript("navigator.userAgent") { result, error in if let result = result as? String { - userAgent = result + defaultUserAgent = result } - semaphore.signal() + group.leave() } - semaphore.wait() - return userAgent - }() - - /// Creates a custom user agent string by appending GutenbergKit identifier to the default user agent - static func createCustomUserAgent() -> String { - return "\(defaultUserAgent) GutenbergKit/\(version)" + group.wait() + + // Cache and return the result + let customUserAgent = "\(defaultUserAgent) GutenbergKit/\(version)" + cachedCustomUserAgent = customUserAgent + return customUserAgent } #if canImport(UIKit) From 5602c97a0536c6a7cca2a1dcfb06454c013bf5d7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 13 Dec 2025 14:49:06 +0000 Subject: [PATCH 08/24] Source version from package.json instead of hardcoding in native code Co-authored-by: dcalhoun <438664+dcalhoun@users.noreply.github.com> --- .../gutenberg/GutenbergKitVersion.kt | 14 +++++ .../org/wordpress/gutenberg/GutenbergView.kt | 3 +- .../wordpress/gutenberg/GutenbergViewTest.kt | 2 +- bin/generate-version.js | 57 +++++++++++++++++++ .../GutenbergKit/Sources/GBWebView.swift | 5 +- .../Sources/GutenbergKitVersion.swift | 7 +++ .../GutenbergKitTests/GBWebViewTests.swift | 4 +- package.json | 3 +- 8 files changed, 85 insertions(+), 10 deletions(-) create mode 100644 android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKitVersion.kt create mode 100755 bin/generate-version.js create mode 100644 ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift diff --git a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKitVersion.kt b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKitVersion.kt new file mode 100644 index 00000000..0386dbfd --- /dev/null +++ b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKitVersion.kt @@ -0,0 +1,14 @@ +// This file is auto-generated by bin/generate-version.js +// Do not edit manually - changes will be overwritten + +package org.wordpress.gutenberg + +/** + * GutenbergKit version information + */ +object GutenbergKitVersion { + /** + * The GutenbergKit version string + */ + const val VERSION = "0.11.1" +} diff --git a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt index 99694eb4..572b484a 100644 --- a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt +++ b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt @@ -128,7 +128,7 @@ class GutenbergView : WebView { // Set custom user agent val defaultUserAgent = this.settings.userAgentString - this.settings.userAgentString = "$defaultUserAgent GutenbergKit/$VERSION" + this.settings.userAgentString = "$defaultUserAgent GutenbergKit/${GutenbergKitVersion.VERSION}" this.addJavascriptInterface(this, "editorDelegate") this.visibility = View.GONE @@ -725,7 +725,6 @@ class GutenbergView : WebView { companion object { private const val ASSET_LOADING_TIMEOUT_MS = 5000L - internal const val VERSION = "0.11.1" // Warmup state management private var warmupHandler: Handler? = null diff --git a/android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt b/android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt index 8492c80c..b070e14a 100644 --- a/android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt +++ b/android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt @@ -161,6 +161,6 @@ class GutenbergViewTest { assertTrue("User agent should contain GutenbergKit identifier", userAgent.contains("GutenbergKit/")) assertTrue("User agent should contain version number", - userAgent.contains("GutenbergKit/${GutenbergView.VERSION}")) + userAgent.contains("GutenbergKit/${GutenbergKitVersion.VERSION}")) } } diff --git a/bin/generate-version.js b/bin/generate-version.js new file mode 100755 index 00000000..cccdd68c --- /dev/null +++ b/bin/generate-version.js @@ -0,0 +1,57 @@ +#!/usr/bin/env node + +/** + * Generate version files for iOS and Android from package.json + * This ensures a single source of truth for the version number + */ + +import { readFileSync, writeFileSync } from 'fs'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const rootDir = join(__dirname, '..'); + +// Read version from package.json +const packageJson = JSON.parse(readFileSync(join(rootDir, 'package.json'), 'utf8')); +const version = packageJson.version; + +console.log(`[GBK] Generating version files for version ${version}`); + +// Generate iOS version file +const iosVersionContent = `// This file is auto-generated by bin/generate-version.js +// Do not edit manually - changes will be overwritten + +extension GutenbergKit { + /// The GutenbergKit version string + public static let version = "${version}" +} +`; + +const iosVersionPath = join(rootDir, 'ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift'); +writeFileSync(iosVersionPath, iosVersionContent, 'utf8'); +console.log(`[GBK] Generated ${iosVersionPath}`); + +// Generate Android version file +const androidVersionContent = `// This file is auto-generated by bin/generate-version.js +// Do not edit manually - changes will be overwritten + +package org.wordpress.gutenberg + +/** + * GutenbergKit version information + */ +object GutenbergKitVersion { + /** + * The GutenbergKit version string + */ + const val VERSION = "${version}" +} +`; + +const androidVersionPath = join(rootDir, 'android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKitVersion.kt'); +writeFileSync(androidVersionPath, androidVersionContent, 'utf8'); +console.log(`[GBK] Generated ${androidVersionPath}`); + +console.log('[GBK] Version files generated successfully'); diff --git a/ios/Sources/GutenbergKit/Sources/GBWebView.swift b/ios/Sources/GutenbergKit/Sources/GBWebView.swift index a9bcddb8..e17916cd 100644 --- a/ios/Sources/GutenbergKit/Sources/GBWebView.swift +++ b/ios/Sources/GutenbergKit/Sources/GBWebView.swift @@ -2,9 +2,6 @@ import WebKit class GBWebView: WKWebView { - /// The GutenbergKit version string - static let version = "0.11.1" - /// Cached custom user agent to avoid repeated WebView instantiation private static var cachedCustomUserAgent: String? @@ -35,7 +32,7 @@ class GBWebView: WKWebView { group.wait() // Cache and return the result - let customUserAgent = "\(defaultUserAgent) GutenbergKit/\(version)" + let customUserAgent = "\(defaultUserAgent) GutenbergKit/\(GutenbergKit.version)" cachedCustomUserAgent = customUserAgent return customUserAgent } diff --git a/ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift b/ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift new file mode 100644 index 00000000..75d8cf03 --- /dev/null +++ b/ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift @@ -0,0 +1,7 @@ +// This file is auto-generated by bin/generate-version.js +// Do not edit manually - changes will be overwritten + +extension GutenbergKit { + /// The GutenbergKit version string + public static let version = "0.11.1" +} diff --git a/ios/Tests/GutenbergKitTests/GBWebViewTests.swift b/ios/Tests/GutenbergKitTests/GBWebViewTests.swift index f2779eb5..fa57696f 100644 --- a/ios/Tests/GutenbergKitTests/GBWebViewTests.swift +++ b/ios/Tests/GutenbergKitTests/GBWebViewTests.swift @@ -11,7 +11,7 @@ final class GBWebViewTests: XCTestCase { // Then XCTAssertTrue(customUserAgent.contains("GutenbergKit/"), "User agent should contain GutenbergKit identifier") - XCTAssertTrue(customUserAgent.contains("GutenbergKit/\(GBWebView.version)"), + XCTAssertTrue(customUserAgent.contains("GutenbergKit/\(GutenbergKit.version)"), "User agent should contain version number") XCTAssertTrue(customUserAgent.contains("Mozilla"), "User agent should contain Mozilla identifier from default user agent") @@ -22,7 +22,7 @@ final class GBWebViewTests: XCTestCase { let customUserAgent = GBWebView.createCustomUserAgent() // Then - XCTAssertTrue(customUserAgent.hasSuffix(" GutenbergKit/\(GBWebView.version)"), + XCTAssertTrue(customUserAgent.hasSuffix(" GutenbergKit/\(GutenbergKit.version)"), "Custom user agent should end with GutenbergKit identifier") } diff --git a/package.json b/package.json index afd82ff0..4ee09849 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,10 @@ "dev:tools": "react-devtools", "build": "vite --emptyOutDir build", "format": "prettier --write .", + "generate-version": "node bin/generate-version.js", "lint:js": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", "lint:js:fix": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0 --fix", - "postinstall": "patch-package && npm run prep-translations", + "postinstall": "patch-package && npm run prep-translations && npm run generate-version", "prep-translations": "node bin/prep-translations.js", "preview": "vite preview --host", "test:unit": "vitest run", From ca964571cfe480ebf1d0e940744856d1f3d3145a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 13 Dec 2025 16:15:08 +0000 Subject: [PATCH 09/24] Use logger utility and format generate-version.js Co-authored-by: dcalhoun <438664+dcalhoun@users.noreply.github.com> --- bin/generate-version.js | 44 ++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/bin/generate-version.js b/bin/generate-version.js index cccdd68c..602e2ebd 100755 --- a/bin/generate-version.js +++ b/bin/generate-version.js @@ -5,19 +5,29 @@ * This ensures a single source of truth for the version number */ +/** + * External dependencies + */ import { readFileSync, writeFileSync } from 'fs'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); -const rootDir = join(__dirname, '..'); +/** + * Internal dependencies + */ +import { info } from '../src/utils/logger.js'; + +const __filename = fileURLToPath( import.meta.url ); +const __dirname = dirname( __filename ); +const rootDir = join( __dirname, '..' ); // Read version from package.json -const packageJson = JSON.parse(readFileSync(join(rootDir, 'package.json'), 'utf8')); +const packageJson = JSON.parse( + readFileSync( join( rootDir, 'package.json' ), 'utf8' ) +); const version = packageJson.version; -console.log(`[GBK] Generating version files for version ${version}`); +info( `Generating version files for version ${ version }` ); // Generate iOS version file const iosVersionContent = `// This file is auto-generated by bin/generate-version.js @@ -25,13 +35,16 @@ const iosVersionContent = `// This file is auto-generated by bin/generate-versio extension GutenbergKit { /// The GutenbergKit version string - public static let version = "${version}" + public static let version = "${ version }" } `; -const iosVersionPath = join(rootDir, 'ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift'); -writeFileSync(iosVersionPath, iosVersionContent, 'utf8'); -console.log(`[GBK] Generated ${iosVersionPath}`); +const iosVersionPath = join( + rootDir, + 'ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift' +); +writeFileSync( iosVersionPath, iosVersionContent, 'utf8' ); +info( `Generated ${ iosVersionPath }` ); // Generate Android version file const androidVersionContent = `// This file is auto-generated by bin/generate-version.js @@ -46,12 +59,15 @@ object GutenbergKitVersion { /** * The GutenbergKit version string */ - const val VERSION = "${version}" + const val VERSION = "${ version }" } `; -const androidVersionPath = join(rootDir, 'android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKitVersion.kt'); -writeFileSync(androidVersionPath, androidVersionContent, 'utf8'); -console.log(`[GBK] Generated ${androidVersionPath}`); +const androidVersionPath = join( + rootDir, + 'android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKitVersion.kt' +); +writeFileSync( androidVersionPath, androidVersionContent, 'utf8' ); +info( `Generated ${ androidVersionPath }` ); -console.log('[GBK] Version files generated successfully'); +info( 'Version files generated successfully' ); From 04196322588fe203baee88aa6e44359fd7998e8f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 13 Dec 2025 16:24:32 +0000 Subject: [PATCH 10/24] Fix Swift compilation: use enum namespace instead of extension Co-authored-by: dcalhoun <438664+dcalhoun@users.noreply.github.com> --- bin/generate-version.js | 3 ++- ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bin/generate-version.js b/bin/generate-version.js index 602e2ebd..558828c1 100755 --- a/bin/generate-version.js +++ b/bin/generate-version.js @@ -33,7 +33,8 @@ info( `Generating version files for version ${ version }` ); const iosVersionContent = `// This file is auto-generated by bin/generate-version.js // Do not edit manually - changes will be overwritten -extension GutenbergKit { +/// GutenbergKit version information namespace +public enum GutenbergKit { /// The GutenbergKit version string public static let version = "${ version }" } diff --git a/ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift b/ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift index 75d8cf03..f9003b3a 100644 --- a/ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift +++ b/ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift @@ -1,7 +1,8 @@ // This file is auto-generated by bin/generate-version.js // Do not edit manually - changes will be overwritten -extension GutenbergKit { +/// GutenbergKit version information namespace +public enum GutenbergKit { /// The GutenbergKit version string public static let version = "0.11.1" } From 3bdab9db608d46916f19622cde810689e9381bb0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 13 Dec 2025 18:01:22 +0000 Subject: [PATCH 11/24] Fix Swift module name conflict: rename to GBKVersion Co-authored-by: dcalhoun <438664+dcalhoun@users.noreply.github.com> --- bin/generate-version.js | 4 ++-- ios/Sources/GutenbergKit/Sources/GBWebView.swift | 2 +- ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift | 4 ++-- ios/Tests/GutenbergKitTests/GBWebViewTests.swift | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bin/generate-version.js b/bin/generate-version.js index 558828c1..3613a73d 100755 --- a/bin/generate-version.js +++ b/bin/generate-version.js @@ -33,8 +33,8 @@ info( `Generating version files for version ${ version }` ); const iosVersionContent = `// This file is auto-generated by bin/generate-version.js // Do not edit manually - changes will be overwritten -/// GutenbergKit version information namespace -public enum GutenbergKit { +/// GutenbergKit version information +public struct GBKVersion { /// The GutenbergKit version string public static let version = "${ version }" } diff --git a/ios/Sources/GutenbergKit/Sources/GBWebView.swift b/ios/Sources/GutenbergKit/Sources/GBWebView.swift index e17916cd..46505c14 100644 --- a/ios/Sources/GutenbergKit/Sources/GBWebView.swift +++ b/ios/Sources/GutenbergKit/Sources/GBWebView.swift @@ -32,7 +32,7 @@ class GBWebView: WKWebView { group.wait() // Cache and return the result - let customUserAgent = "\(defaultUserAgent) GutenbergKit/\(GutenbergKit.version)" + let customUserAgent = "\(defaultUserAgent) GutenbergKit/\(GBKVersion.version)" cachedCustomUserAgent = customUserAgent return customUserAgent } diff --git a/ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift b/ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift index f9003b3a..35b1ffc3 100644 --- a/ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift +++ b/ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift @@ -1,8 +1,8 @@ // This file is auto-generated by bin/generate-version.js // Do not edit manually - changes will be overwritten -/// GutenbergKit version information namespace -public enum GutenbergKit { +/// GutenbergKit version information +public struct GBKVersion { /// The GutenbergKit version string public static let version = "0.11.1" } diff --git a/ios/Tests/GutenbergKitTests/GBWebViewTests.swift b/ios/Tests/GutenbergKitTests/GBWebViewTests.swift index fa57696f..823cf3ca 100644 --- a/ios/Tests/GutenbergKitTests/GBWebViewTests.swift +++ b/ios/Tests/GutenbergKitTests/GBWebViewTests.swift @@ -11,7 +11,7 @@ final class GBWebViewTests: XCTestCase { // Then XCTAssertTrue(customUserAgent.contains("GutenbergKit/"), "User agent should contain GutenbergKit identifier") - XCTAssertTrue(customUserAgent.contains("GutenbergKit/\(GutenbergKit.version)"), + XCTAssertTrue(customUserAgent.contains("GutenbergKit/\(GBKVersion.version)"), "User agent should contain version number") XCTAssertTrue(customUserAgent.contains("Mozilla"), "User agent should contain Mozilla identifier from default user agent") @@ -22,7 +22,7 @@ final class GBWebViewTests: XCTestCase { let customUserAgent = GBWebView.createCustomUserAgent() // Then - XCTAssertTrue(customUserAgent.hasSuffix(" GutenbergKit/\(GutenbergKit.version)"), + XCTAssertTrue(customUserAgent.hasSuffix(" GutenbergKit/\(GBKVersion.version)"), "Custom user agent should end with GutenbergKit identifier") } From edf1acb95a8e298bacc6934b17783c591c8dc330 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Dec 2025 15:24:26 +0000 Subject: [PATCH 12/24] Use applicationNameForUserAgent instead of customUserAgent Co-authored-by: dcalhoun <438664+dcalhoun@users.noreply.github.com> --- .../Sources/EditorViewController.swift | 2 +- .../GutenbergKit/Sources/GBWebView.swift | 35 ------------------ .../HTMLPreview/HTMLWebViewRenderer.swift | 2 +- .../GutenbergKitTests/GBWebViewTests.swift | 36 ++++++------------- 4 files changed, 13 insertions(+), 62 deletions(-) diff --git a/ios/Sources/GutenbergKit/Sources/EditorViewController.swift b/ios/Sources/GutenbergKit/Sources/EditorViewController.swift index e9738782..9403c36c 100644 --- a/ios/Sources/GutenbergKit/Sources/EditorViewController.swift +++ b/ios/Sources/GutenbergKit/Sources/EditorViewController.swift @@ -177,7 +177,7 @@ public final class EditorViewController: UIViewController, GutenbergEditorContro self.webView = GBWebView(frame: .zero, configuration: config) self.webView.scrollView.keyboardDismissMode = .interactive - self.webView.customUserAgent = GBWebView.createCustomUserAgent() + self.webView.applicationNameForUserAgent = "GutenbergKit/\(GBKVersion.version)" self.isWarmupMode = isWarmupMode diff --git a/ios/Sources/GutenbergKit/Sources/GBWebView.swift b/ios/Sources/GutenbergKit/Sources/GBWebView.swift index 46505c14..7c036fe8 100644 --- a/ios/Sources/GutenbergKit/Sources/GBWebView.swift +++ b/ios/Sources/GutenbergKit/Sources/GBWebView.swift @@ -1,41 +1,6 @@ import WebKit class GBWebView: WKWebView { - - /// Cached custom user agent to avoid repeated WebView instantiation - private static var cachedCustomUserAgent: String? - - /// Creates a custom user agent string by appending GutenbergKit identifier to the default user agent - /// The result is cached after the first call to avoid performance overhead - static func createCustomUserAgent() -> String { - // Return cached value if available - if let cached = cachedCustomUserAgent { - return cached - } - - // Get the default user agent by creating a temporary WKWebView - let webView = WKWebView() - var defaultUserAgent = "" - - // Use DispatchGroup for synchronous wait - // This is safe during view initialization as it's not on a critical path - let group = DispatchGroup() - group.enter() - - webView.evaluateJavaScript("navigator.userAgent") { result, error in - if let result = result as? String { - defaultUserAgent = result - } - group.leave() - } - - group.wait() - - // Cache and return the result - let customUserAgent = "\(defaultUserAgent) GutenbergKit/\(GBKVersion.version)" - cachedCustomUserAgent = customUserAgent - return customUserAgent - } #if canImport(UIKit) /// Disables the default bottom bar that competes with the Gutenberg inserter diff --git a/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift b/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift index ad7ec168..a3891172 100644 --- a/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift +++ b/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift @@ -94,7 +94,7 @@ final class HTMLWebViewRenderer { // Create web view with small initial frame for off-screen rendering // Frame will be adjusted per render based on viewport width and content height webView = WKWebView(frame: CGRect(x: 0, y: 0, width: 1200, height: 100), configuration: config) - webView.customUserAgent = GBWebView.createCustomUserAgent() + webView.applicationNameForUserAgent = "GutenbergKit/\(GBKVersion.version)" delegate = RenderDelegate() webView.navigationDelegate = delegate diff --git a/ios/Tests/GutenbergKitTests/GBWebViewTests.swift b/ios/Tests/GutenbergKitTests/GBWebViewTests.swift index 823cf3ca..59df757c 100644 --- a/ios/Tests/GutenbergKitTests/GBWebViewTests.swift +++ b/ios/Tests/GutenbergKitTests/GBWebViewTests.swift @@ -4,37 +4,23 @@ import WebKit final class GBWebViewTests: XCTestCase { - func testCreateCustomUserAgent() { - // When - let customUserAgent = GBWebView.createCustomUserAgent() + func testApplicationNameForUserAgent() { + // Given + let webView = GBWebView() - // Then - XCTAssertTrue(customUserAgent.contains("GutenbergKit/"), - "User agent should contain GutenbergKit identifier") - XCTAssertTrue(customUserAgent.contains("GutenbergKit/\(GBKVersion.version)"), - "User agent should contain version number") - XCTAssertTrue(customUserAgent.contains("Mozilla"), - "User agent should contain Mozilla identifier from default user agent") - } - - func testCustomUserAgentEndsWithGutenbergKitIdentifier() { // When - let customUserAgent = GBWebView.createCustomUserAgent() + webView.applicationNameForUserAgent = "GutenbergKit/\(GBKVersion.version)" // Then - XCTAssertTrue(customUserAgent.hasSuffix(" GutenbergKit/\(GBKVersion.version)"), - "Custom user agent should end with GutenbergKit identifier") + XCTAssertEqual(webView.applicationNameForUserAgent, "GutenbergKit/\(GBKVersion.version)", + "Application name should be set correctly") } - func testCustomUserAgentIsConsistent() { - // Given - let firstCall = GBWebView.createCustomUserAgent() - - // When - let secondCall = GBWebView.createCustomUserAgent() - + func testVersionConstantExists() { // Then - XCTAssertEqual(firstCall, secondCall, - "Custom user agent should be consistent across calls") + XCTAssertFalse(GBKVersion.version.isEmpty, + "Version constant should not be empty") + XCTAssertTrue(GBKVersion.version.contains("."), + "Version should be in semantic versioning format") } } From 1ad006f574f49b0a384e3cf972681c2833069d54 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Dec 2025 16:43:03 +0000 Subject: [PATCH 13/24] Convert tests to Swift Testing and verify navigator.userAgent Co-authored-by: dcalhoun <438664+dcalhoun@users.noreply.github.com> --- .../GutenbergKitTests/GBWebViewTests.swift | 44 ++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/ios/Tests/GutenbergKitTests/GBWebViewTests.swift b/ios/Tests/GutenbergKitTests/GBWebViewTests.swift index 59df757c..2bc67c91 100644 --- a/ios/Tests/GutenbergKitTests/GBWebViewTests.swift +++ b/ios/Tests/GutenbergKitTests/GBWebViewTests.swift @@ -1,10 +1,13 @@ -import XCTest +import Foundation +import Testing import WebKit @testable import GutenbergKit -final class GBWebViewTests: XCTestCase { +@Suite("GBWebView Tests") +struct GBWebViewTests { - func testApplicationNameForUserAgent() { + @Test("Application name for user agent is set correctly") + func testApplicationNameForUserAgent() async throws { // Given let webView = GBWebView() @@ -12,15 +15,36 @@ final class GBWebViewTests: XCTestCase { webView.applicationNameForUserAgent = "GutenbergKit/\(GBKVersion.version)" // Then - XCTAssertEqual(webView.applicationNameForUserAgent, "GutenbergKit/\(GBKVersion.version)", - "Application name should be set correctly") + #expect(webView.applicationNameForUserAgent == "GutenbergKit/\(GBKVersion.version)") } - func testVersionConstantExists() { + @Test("Version constant exists and is valid") + func testVersionConstantExists() async throws { // Then - XCTAssertFalse(GBKVersion.version.isEmpty, - "Version constant should not be empty") - XCTAssertTrue(GBKVersion.version.contains("."), - "Version should be in semantic versioning format") + #expect(!GBKVersion.version.isEmpty) + #expect(GBKVersion.version.contains(".")) + } + + @Test("Navigator user agent includes GutenbergKit identifier") + func testNavigatorUserAgentIncludesGutenbergKit() async throws { + // Given + let webView = GBWebView() + webView.applicationNameForUserAgent = "GutenbergKit/\(GBKVersion.version)" + + // Load a simple HTML page to ensure the WebView is ready + let html = "Test" + webView.loadHTMLString(html, baseURL: nil) + + // Wait for the page to load + try await Task.sleep(for: .milliseconds(500)) + + // When - evaluate navigator.userAgent in the WebView + let userAgent = try await webView.evaluateJavaScript("navigator.userAgent") as? String + + // Then + #expect(userAgent != nil) + if let userAgent = userAgent { + #expect(userAgent.contains("GutenbergKit/\(GBKVersion.version)")) + } } } From d183fa5cf545a2e77157df092e474e42186ee195 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Dec 2025 16:45:13 +0000 Subject: [PATCH 14/24] Simplify test assertion using optional chaining Co-authored-by: dcalhoun <438664+dcalhoun@users.noreply.github.com> --- ios/Tests/GutenbergKitTests/GBWebViewTests.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ios/Tests/GutenbergKitTests/GBWebViewTests.swift b/ios/Tests/GutenbergKitTests/GBWebViewTests.swift index 2bc67c91..0620acc8 100644 --- a/ios/Tests/GutenbergKitTests/GBWebViewTests.swift +++ b/ios/Tests/GutenbergKitTests/GBWebViewTests.swift @@ -42,9 +42,6 @@ struct GBWebViewTests { let userAgent = try await webView.evaluateJavaScript("navigator.userAgent") as? String // Then - #expect(userAgent != nil) - if let userAgent = userAgent { - #expect(userAgent.contains("GutenbergKit/\(GBKVersion.version)")) - } + #expect(userAgent?.contains("GutenbergKit/\(GBKVersion.version)") == true) } } From d6ac4787946629851f0a579830ea64b92188f0bf Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Mon, 15 Dec 2025 11:35:15 -0700 Subject: [PATCH 15/24] Fix tests --- .../GutenbergKitTests/GBWebViewTests.swift | 48 ++++--------------- 1 file changed, 10 insertions(+), 38 deletions(-) diff --git a/ios/Tests/GutenbergKitTests/GBWebViewTests.swift b/ios/Tests/GutenbergKitTests/GBWebViewTests.swift index 0620acc8..983ec4e6 100644 --- a/ios/Tests/GutenbergKitTests/GBWebViewTests.swift +++ b/ios/Tests/GutenbergKitTests/GBWebViewTests.swift @@ -1,47 +1,19 @@ -import Foundation import Testing import WebKit @testable import GutenbergKit -@Suite("GBWebView Tests") struct GBWebViewTests { - - @Test("Application name for user agent is set correctly") + + @MainActor func testApplicationNameForUserAgent() async throws { - // Given - let webView = GBWebView() - - // When - webView.applicationNameForUserAgent = "GutenbergKit/\(GBKVersion.version)" - - // Then - #expect(webView.applicationNameForUserAgent == "GutenbergKit/\(GBKVersion.version)") - } - - @Test("Version constant exists and is valid") - func testVersionConstantExists() async throws { - // Then - #expect(!GBKVersion.version.isEmpty) - #expect(GBKVersion.version.contains(".")) + let result = try await GBWebView().evaluateJavaScript("navigator.userAgent") + let string = try #require(result as? String) + + #expect(string.hasSuffix("GutenbergKit/\(GBKVersion.version)")) } - - @Test("Navigator user agent includes GutenbergKit identifier") - func testNavigatorUserAgentIncludesGutenbergKit() async throws { - // Given - let webView = GBWebView() - webView.applicationNameForUserAgent = "GutenbergKit/\(GBKVersion.version)" - - // Load a simple HTML page to ensure the WebView is ready - let html = "Test" - webView.loadHTMLString(html, baseURL: nil) - - // Wait for the page to load - try await Task.sleep(for: .milliseconds(500)) - - // When - evaluate navigator.userAgent in the WebView - let userAgent = try await webView.evaluateJavaScript("navigator.userAgent") as? String - - // Then - #expect(userAgent?.contains("GutenbergKit/\(GBKVersion.version)") == true) + + func testVersionConstantExists() { + #expect(GBKVersion.version.isEmpty, "Version constant should not be empty") + #expect(GBKVersion.version.contains("."), "Version should be in semantic versioning format") } } From 920788b8bbf2fbd2fd1e27ad9f4c5a2d4a82b4c0 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Sun, 21 Dec 2025 08:36:39 -0500 Subject: [PATCH 16/24] task: Include GutenbergKit user agent for native requests --- .../gutenberg/EditorAssetsLibrary.kt | 9 ++- .../Sources/EditorHTTPClient.swift | 13 +++++ .../EditorHTTPClientTests.swift | 57 +++++++++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/EditorAssetsLibrary.kt b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/EditorAssetsLibrary.kt index 9aea8503..208aa859 100644 --- a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/EditorAssetsLibrary.kt +++ b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/EditorAssetsLibrary.kt @@ -44,6 +44,9 @@ class EditorAssetsLibrary( try { connection.requestMethod = "GET" + val defaultUserAgent = System.getProperty("http.agent") ?: "" + connection.setRequestProperty("User-Agent", "$defaultUserAgent GutenbergKit/${GutenbergKitVersion.VERSION}") + // Set headers from configuration if (configuration.authHeader.isNotEmpty()) { connection.setRequestProperty("Authorization", configuration.authHeader) @@ -102,6 +105,10 @@ class EditorAssetsLibrary( val connection = URL(httpURL).openConnection() as HttpURLConnection try { connection.requestMethod = "GET" + + val defaultUserAgent = System.getProperty("http.agent") ?: "" + connection.setRequestProperty("User-Agent", "$defaultUserAgent GutenbergKit/${GutenbergKitVersion.VERSION}") + connection.connectTimeout = 30000 connection.readTimeout = 30000 @@ -149,7 +156,7 @@ class EditorAssetsLibrary( */ fun cacheAssetInBackground(url: String) { if (!shouldCacheUrl(url)) return - + scope.launch { try { cacheAsset(url) diff --git a/ios/Sources/GutenbergKit/Sources/EditorHTTPClient.swift b/ios/Sources/GutenbergKit/Sources/EditorHTTPClient.swift index dec766db..90c958b3 100644 --- a/ios/Sources/GutenbergKit/Sources/EditorHTTPClient.swift +++ b/ios/Sources/GutenbergKit/Sources/EditorHTTPClient.swift @@ -41,6 +41,18 @@ public actor EditorHTTPClient: EditorHTTPClientProtocol { case unknown(response: Data, statusCode: Int) } + /// The base user agent string identifying the platform. + private static let baseUserAgent: String = { + let version = ProcessInfo.processInfo.operatingSystemVersion + #if os(iOS) + return "iOS/\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)" + #elseif os(macOS) + return "macOS/\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)" + #else + return "Darwin" + #endif + }() + private let urlSession: URLSessionProtocol private let authHeader: String private let delegate: EditorHTTPClientDelegate? @@ -99,6 +111,7 @@ public actor EditorHTTPClient: EditorHTTPClientProtocol { private func configureRequest(_ request: URLRequest) -> URLRequest { var mutableRequest = request mutableRequest.addValue(self.authHeader, forHTTPHeaderField: "Authorization") + mutableRequest.addValue("\(Self.baseUserAgent) GutenbergKit/\(GBKVersion.version)", forHTTPHeaderField: "User-Agent") if let requestTimeout { mutableRequest.timeoutInterval = requestTimeout diff --git a/ios/Tests/GutenbergKitTests/EditorHTTPClientTests.swift b/ios/Tests/GutenbergKitTests/EditorHTTPClientTests.swift index dbe2d91b..f319a282 100644 --- a/ios/Tests/GutenbergKitTests/EditorHTTPClientTests.swift +++ b/ios/Tests/GutenbergKitTests/EditorHTTPClientTests.swift @@ -335,6 +335,63 @@ struct EditorHTTPClientTests { #expect(lastCall.request.value(forHTTPHeaderField: "Authorization") == authHeader) #expect(lastCall.request.httpShouldHandleCookies == false) } + + // MARK: - User-Agent Header Tests + + @Test("perform() sets User-Agent header with GutenbergKit identifier") + func performSetsUserAgentHeader() async throws { + let spySession = SpyURLSession() + let client = EditorHTTPClient( + urlSession: spySession, + authHeader: "Bearer token" + ) + + let request = URLRequest(url: URL(string: "https://example.com/wp-json/wp/v2/posts")!) + _ = try await client.perform(request) + + let capturedRequest = try #require(spySession.lastCapturedRequest) + let userAgent = try #require(capturedRequest.value(forHTTPHeaderField: "User-Agent")) + #expect(userAgent.contains("GutenbergKit/")) + #expect(userAgent.contains(GBKVersion.version)) + } + + @Test("download() sets User-Agent header with GutenbergKit identifier") + func downloadSetsUserAgentHeader() async throws { + let spySession = SpyURLSession() + let client = EditorHTTPClient( + urlSession: spySession, + authHeader: "Bearer token" + ) + + let request = URLRequest(url: URL(string: "https://example.com/wp-content/file.js")!) + _ = try await client.download(request) + + let capturedRequest = try #require(spySession.lastCapturedRequest) + let userAgent = try #require(capturedRequest.value(forHTTPHeaderField: "User-Agent")) + #expect(userAgent.contains("GutenbergKit/")) + #expect(userAgent.contains(GBKVersion.version)) + } + + @Test("User-Agent header includes platform identifier") + func userAgentIncludesPlatformIdentifier() async throws { + let spySession = SpyURLSession() + let client = EditorHTTPClient( + urlSession: spySession, + authHeader: "Bearer token" + ) + + let request = URLRequest(url: URL(string: "https://example.com/wp-json/wp/v2/posts")!) + _ = try await client.perform(request) + + let capturedRequest = try #require(spySession.lastCapturedRequest) + let userAgent = try #require(capturedRequest.value(forHTTPHeaderField: "User-Agent")) + + #if os(iOS) + #expect(userAgent.contains("iOS/")) + #elseif os(macOS) + #expect(userAgent.contains("macOS/")) + #endif + } } fileprivate extension EditorResponseData { From 4b639a4b149da59fa32a806bd958254b75180529 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Sun, 21 Dec 2025 08:47:22 -0500 Subject: [PATCH 17/24] refactor: Align version module naming --- .../wordpress/gutenberg/EditorAssetsLibrary.kt | 4 ++-- .../{GutenbergKitVersion.kt => GutenbergKit.kt} | 6 +++--- .../org/wordpress/gutenberg/GutenbergView.kt | 2 +- .../org/wordpress/gutenberg/GutenbergViewTest.kt | 2 +- bin/generate-version.js | 16 ++++++++-------- .../GutenbergKit/Sources/EditorHTTPClient.swift | 2 +- .../Sources/EditorViewController.swift | 2 +- ...enbergKitVersion.swift => GutenbergKit.swift} | 6 +++--- .../Views/HTMLPreview/HTMLWebViewRenderer.swift | 2 +- .../EditorHTTPClientTests.swift | 4 ++-- ios/Tests/GutenbergKitTests/GBWebViewTests.swift | 6 +++--- 11 files changed, 26 insertions(+), 26 deletions(-) rename android/Gutenberg/src/main/java/org/wordpress/gutenberg/{GutenbergKitVersion.kt => GutenbergKit.kt} (64%) rename ios/Sources/GutenbergKit/Sources/{GutenbergKitVersion.swift => GutenbergKit.swift} (56%) diff --git a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/EditorAssetsLibrary.kt b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/EditorAssetsLibrary.kt index 208aa859..58a468c0 100644 --- a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/EditorAssetsLibrary.kt +++ b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/EditorAssetsLibrary.kt @@ -45,7 +45,7 @@ class EditorAssetsLibrary( connection.requestMethod = "GET" val defaultUserAgent = System.getProperty("http.agent") ?: "" - connection.setRequestProperty("User-Agent", "$defaultUserAgent GutenbergKit/${GutenbergKitVersion.VERSION}") + connection.setRequestProperty("User-Agent", "$defaultUserAgent GutenbergKit/${GutenbergKit.VERSION}") // Set headers from configuration if (configuration.authHeader.isNotEmpty()) { @@ -107,7 +107,7 @@ class EditorAssetsLibrary( connection.requestMethod = "GET" val defaultUserAgent = System.getProperty("http.agent") ?: "" - connection.setRequestProperty("User-Agent", "$defaultUserAgent GutenbergKit/${GutenbergKitVersion.VERSION}") + connection.setRequestProperty("User-Agent", "$defaultUserAgent GutenbergKit/${GutenbergKit.VERSION}") connection.connectTimeout = 30000 connection.readTimeout = 30000 diff --git a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKitVersion.kt b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKit.kt similarity index 64% rename from android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKitVersion.kt rename to android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKit.kt index 0386dbfd..e7e8ac6b 100644 --- a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKitVersion.kt +++ b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKit.kt @@ -4,11 +4,11 @@ package org.wordpress.gutenberg /** - * GutenbergKit version information + * GutenbergKit library constants and utilities. */ -object GutenbergKitVersion { +object GutenbergKit { /** - * The GutenbergKit version string + * The current version of GutenbergKit. */ const val VERSION = "0.11.1" } diff --git a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt index 572b484a..7d3a4c54 100644 --- a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt +++ b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt @@ -128,7 +128,7 @@ class GutenbergView : WebView { // Set custom user agent val defaultUserAgent = this.settings.userAgentString - this.settings.userAgentString = "$defaultUserAgent GutenbergKit/${GutenbergKitVersion.VERSION}" + this.settings.userAgentString = "$defaultUserAgent GutenbergKit/${GutenbergKit.VERSION}" this.addJavascriptInterface(this, "editorDelegate") this.visibility = View.GONE diff --git a/android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt b/android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt index b070e14a..2f92d4c9 100644 --- a/android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt +++ b/android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt @@ -161,6 +161,6 @@ class GutenbergViewTest { assertTrue("User agent should contain GutenbergKit identifier", userAgent.contains("GutenbergKit/")) assertTrue("User agent should contain version number", - userAgent.contains("GutenbergKit/${GutenbergKitVersion.VERSION}")) + userAgent.contains("GutenbergKit/${GutenbergKit.VERSION}")) } } diff --git a/bin/generate-version.js b/bin/generate-version.js index 3613a73d..e085c50e 100755 --- a/bin/generate-version.js +++ b/bin/generate-version.js @@ -33,16 +33,16 @@ info( `Generating version files for version ${ version }` ); const iosVersionContent = `// This file is auto-generated by bin/generate-version.js // Do not edit manually - changes will be overwritten -/// GutenbergKit version information -public struct GBKVersion { - /// The GutenbergKit version string +/// GutenbergKit library constants and utilities. +public enum GutenbergKit { + /// The current version of GutenbergKit. public static let version = "${ version }" } `; const iosVersionPath = join( rootDir, - 'ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift' + 'ios/Sources/GutenbergKit/Sources/GutenbergKit.swift' ); writeFileSync( iosVersionPath, iosVersionContent, 'utf8' ); info( `Generated ${ iosVersionPath }` ); @@ -54,11 +54,11 @@ const androidVersionContent = `// This file is auto-generated by bin/generate-ve package org.wordpress.gutenberg /** - * GutenbergKit version information + * GutenbergKit library constants and utilities. */ -object GutenbergKitVersion { +object GutenbergKit { /** - * The GutenbergKit version string + * The current version of GutenbergKit. */ const val VERSION = "${ version }" } @@ -66,7 +66,7 @@ object GutenbergKitVersion { const androidVersionPath = join( rootDir, - 'android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKitVersion.kt' + 'android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKit.kt' ); writeFileSync( androidVersionPath, androidVersionContent, 'utf8' ); info( `Generated ${ androidVersionPath }` ); diff --git a/ios/Sources/GutenbergKit/Sources/EditorHTTPClient.swift b/ios/Sources/GutenbergKit/Sources/EditorHTTPClient.swift index 90c958b3..3739b5f2 100644 --- a/ios/Sources/GutenbergKit/Sources/EditorHTTPClient.swift +++ b/ios/Sources/GutenbergKit/Sources/EditorHTTPClient.swift @@ -111,7 +111,7 @@ public actor EditorHTTPClient: EditorHTTPClientProtocol { private func configureRequest(_ request: URLRequest) -> URLRequest { var mutableRequest = request mutableRequest.addValue(self.authHeader, forHTTPHeaderField: "Authorization") - mutableRequest.addValue("\(Self.baseUserAgent) GutenbergKit/\(GBKVersion.version)", forHTTPHeaderField: "User-Agent") + mutableRequest.addValue("\(Self.baseUserAgent) GutenbergKit/\(GutenbergKit.version)", forHTTPHeaderField: "User-Agent") if let requestTimeout { mutableRequest.timeoutInterval = requestTimeout diff --git a/ios/Sources/GutenbergKit/Sources/EditorViewController.swift b/ios/Sources/GutenbergKit/Sources/EditorViewController.swift index 9403c36c..346d10a7 100644 --- a/ios/Sources/GutenbergKit/Sources/EditorViewController.swift +++ b/ios/Sources/GutenbergKit/Sources/EditorViewController.swift @@ -177,7 +177,7 @@ public final class EditorViewController: UIViewController, GutenbergEditorContro self.webView = GBWebView(frame: .zero, configuration: config) self.webView.scrollView.keyboardDismissMode = .interactive - self.webView.applicationNameForUserAgent = "GutenbergKit/\(GBKVersion.version)" + self.webView.applicationNameForUserAgent = "GutenbergKit/\(GutenbergKit.version)" self.isWarmupMode = isWarmupMode diff --git a/ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift b/ios/Sources/GutenbergKit/Sources/GutenbergKit.swift similarity index 56% rename from ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift rename to ios/Sources/GutenbergKit/Sources/GutenbergKit.swift index 35b1ffc3..daf83191 100644 --- a/ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift +++ b/ios/Sources/GutenbergKit/Sources/GutenbergKit.swift @@ -1,8 +1,8 @@ // This file is auto-generated by bin/generate-version.js // Do not edit manually - changes will be overwritten -/// GutenbergKit version information -public struct GBKVersion { - /// The GutenbergKit version string +/// GutenbergKit library constants and utilities. +public enum GutenbergKit { + /// The current version of GutenbergKit. public static let version = "0.11.1" } diff --git a/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift b/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift index a3891172..c44532b8 100644 --- a/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift +++ b/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift @@ -94,7 +94,7 @@ final class HTMLWebViewRenderer { // Create web view with small initial frame for off-screen rendering // Frame will be adjusted per render based on viewport width and content height webView = WKWebView(frame: CGRect(x: 0, y: 0, width: 1200, height: 100), configuration: config) - webView.applicationNameForUserAgent = "GutenbergKit/\(GBKVersion.version)" + webView.applicationNameForUserAgent = "GutenbergKit/\(GutenbergKit.version)" delegate = RenderDelegate() webView.navigationDelegate = delegate diff --git a/ios/Tests/GutenbergKitTests/EditorHTTPClientTests.swift b/ios/Tests/GutenbergKitTests/EditorHTTPClientTests.swift index f319a282..9e4b1b63 100644 --- a/ios/Tests/GutenbergKitTests/EditorHTTPClientTests.swift +++ b/ios/Tests/GutenbergKitTests/EditorHTTPClientTests.swift @@ -352,7 +352,7 @@ struct EditorHTTPClientTests { let capturedRequest = try #require(spySession.lastCapturedRequest) let userAgent = try #require(capturedRequest.value(forHTTPHeaderField: "User-Agent")) #expect(userAgent.contains("GutenbergKit/")) - #expect(userAgent.contains(GBKVersion.version)) + #expect(userAgent.contains(GutenbergKit.version)) } @Test("download() sets User-Agent header with GutenbergKit identifier") @@ -369,7 +369,7 @@ struct EditorHTTPClientTests { let capturedRequest = try #require(spySession.lastCapturedRequest) let userAgent = try #require(capturedRequest.value(forHTTPHeaderField: "User-Agent")) #expect(userAgent.contains("GutenbergKit/")) - #expect(userAgent.contains(GBKVersion.version)) + #expect(userAgent.contains(GutenbergKit.version)) } @Test("User-Agent header includes platform identifier") diff --git a/ios/Tests/GutenbergKitTests/GBWebViewTests.swift b/ios/Tests/GutenbergKitTests/GBWebViewTests.swift index 983ec4e6..d592f31e 100644 --- a/ios/Tests/GutenbergKitTests/GBWebViewTests.swift +++ b/ios/Tests/GutenbergKitTests/GBWebViewTests.swift @@ -9,11 +9,11 @@ struct GBWebViewTests { let result = try await GBWebView().evaluateJavaScript("navigator.userAgent") let string = try #require(result as? String) - #expect(string.hasSuffix("GutenbergKit/\(GBKVersion.version)")) + #expect(string.hasSuffix("GutenbergKit/\(GutenbergKit.version)")) } func testVersionConstantExists() { - #expect(GBKVersion.version.isEmpty, "Version constant should not be empty") - #expect(GBKVersion.version.contains("."), "Version should be in semantic versioning format") + #expect(GutenbergKit.version.isEmpty, "Version constant should not be empty") + #expect(GutenbergKit.version.contains("."), "Version should be in semantic versioning format") } } From 69bb9fe98eb4741733db7aaa57d16fcf86ff4234 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Sun, 21 Dec 2025 08:51:40 -0500 Subject: [PATCH 18/24] test: Fix erroneously inverted test assertion --- ios/Tests/GutenbergKitTests/GBWebViewTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Tests/GutenbergKitTests/GBWebViewTests.swift b/ios/Tests/GutenbergKitTests/GBWebViewTests.swift index d592f31e..e43c7136 100644 --- a/ios/Tests/GutenbergKitTests/GBWebViewTests.swift +++ b/ios/Tests/GutenbergKitTests/GBWebViewTests.swift @@ -13,7 +13,7 @@ struct GBWebViewTests { } func testVersionConstantExists() { - #expect(GutenbergKit.version.isEmpty, "Version constant should not be empty") + #expect(!GutenbergKit.version.isEmpty, "Version constant should not be empty") #expect(GutenbergKit.version.contains("."), "Version should be in semantic versioning format") } } From 28dc6aa85da315dd08db99977494705a9936aebe Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Sun, 21 Dec 2025 08:54:23 -0500 Subject: [PATCH 19/24] refactor: Add generated version files to .gitignore --- .gitignore | 4 ++++ .../java/org/wordpress/gutenberg/GutenbergKit.kt | 14 -------------- .../GutenbergKit/Sources/GutenbergKit.swift | 8 -------- 3 files changed, 4 insertions(+), 22 deletions(-) delete mode 100644 android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKit.kt delete mode 100644 ios/Sources/GutenbergKit/Sources/GutenbergKit.swift diff --git a/.gitignore b/.gitignore index 152f1095..19bee24e 100644 --- a/.gitignore +++ b/.gitignore @@ -198,3 +198,7 @@ src/translations/ # Claude .claude/settings.local.json + +# Auto-generated version files (regenerated by npm postinstall) +ios/Sources/GutenbergKit/Sources/GutenbergKit.swift +android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKit.kt diff --git a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKit.kt b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKit.kt deleted file mode 100644 index e7e8ac6b..00000000 --- a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKit.kt +++ /dev/null @@ -1,14 +0,0 @@ -// This file is auto-generated by bin/generate-version.js -// Do not edit manually - changes will be overwritten - -package org.wordpress.gutenberg - -/** - * GutenbergKit library constants and utilities. - */ -object GutenbergKit { - /** - * The current version of GutenbergKit. - */ - const val VERSION = "0.11.1" -} diff --git a/ios/Sources/GutenbergKit/Sources/GutenbergKit.swift b/ios/Sources/GutenbergKit/Sources/GutenbergKit.swift deleted file mode 100644 index daf83191..00000000 --- a/ios/Sources/GutenbergKit/Sources/GutenbergKit.swift +++ /dev/null @@ -1,8 +0,0 @@ -// This file is auto-generated by bin/generate-version.js -// Do not edit manually - changes will be overwritten - -/// GutenbergKit library constants and utilities. -public enum GutenbergKit { - /// The current version of GutenbergKit. - public static let version = "0.11.1" -} From d8e2b303f1aac52ca999b0e7afe8b567f0a02ab3 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Sun, 21 Dec 2025 09:04:11 -0500 Subject: [PATCH 20/24] fix: Repair `applicationNameForUserAgent` usage This is a value on the WebView configuration, not the WebView itself. --- ios/Sources/GutenbergKit/Sources/EditorViewController.swift | 2 +- .../Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/Sources/GutenbergKit/Sources/EditorViewController.swift b/ios/Sources/GutenbergKit/Sources/EditorViewController.swift index 346d10a7..67c4ab4f 100644 --- a/ios/Sources/GutenbergKit/Sources/EditorViewController.swift +++ b/ios/Sources/GutenbergKit/Sources/EditorViewController.swift @@ -177,7 +177,7 @@ public final class EditorViewController: UIViewController, GutenbergEditorContro self.webView = GBWebView(frame: .zero, configuration: config) self.webView.scrollView.keyboardDismissMode = .interactive - self.webView.applicationNameForUserAgent = "GutenbergKit/\(GutenbergKit.version)" + self.webView.configuration.applicationNameForUserAgent = "GutenbergKit/\(GutenbergKit.version)" self.isWarmupMode = isWarmupMode diff --git a/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift b/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift index c44532b8..01792132 100644 --- a/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift +++ b/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift @@ -94,7 +94,7 @@ final class HTMLWebViewRenderer { // Create web view with small initial frame for off-screen rendering // Frame will be adjusted per render based on viewport width and content height webView = WKWebView(frame: CGRect(x: 0, y: 0, width: 1200, height: 100), configuration: config) - webView.applicationNameForUserAgent = "GutenbergKit/\(GutenbergKit.version)" + webView.configuration.applicationNameForUserAgent = "GutenbergKit/\(GutenbergKit.version)" delegate = RenderDelegate() webView.navigationDelegate = delegate From e2e6ef44eddb15a04089ac0ec0faf53492892d90 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Sun, 21 Dec 2025 09:13:34 -0500 Subject: [PATCH 21/24] refactor: Rename to avoid module name conflict Address iOS build failures. --- .gitignore | 4 ++-- .../org/wordpress/gutenberg/EditorAssetsLibrary.kt | 4 ++-- .../java/org/wordpress/gutenberg/GutenbergView.kt | 2 +- .../org/wordpress/gutenberg/GutenbergViewTest.kt | 2 +- bin/generate-version.js | 12 ++++++------ .../GutenbergKit/Sources/EditorHTTPClient.swift | 2 +- .../GutenbergKit/Sources/EditorViewController.swift | 2 +- .../Views/HTMLPreview/HTMLWebViewRenderer.swift | 2 +- .../GutenbergKitTests/EditorHTTPClientTests.swift | 4 ++-- ios/Tests/GutenbergKitTests/GBWebViewTests.swift | 6 +++--- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index 19bee24e..bb30d222 100644 --- a/.gitignore +++ b/.gitignore @@ -200,5 +200,5 @@ src/translations/ .claude/settings.local.json # Auto-generated version files (regenerated by npm postinstall) -ios/Sources/GutenbergKit/Sources/GutenbergKit.swift -android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKit.kt +ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift +android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKitVersion.kt diff --git a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/EditorAssetsLibrary.kt b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/EditorAssetsLibrary.kt index 58a468c0..208aa859 100644 --- a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/EditorAssetsLibrary.kt +++ b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/EditorAssetsLibrary.kt @@ -45,7 +45,7 @@ class EditorAssetsLibrary( connection.requestMethod = "GET" val defaultUserAgent = System.getProperty("http.agent") ?: "" - connection.setRequestProperty("User-Agent", "$defaultUserAgent GutenbergKit/${GutenbergKit.VERSION}") + connection.setRequestProperty("User-Agent", "$defaultUserAgent GutenbergKit/${GutenbergKitVersion.VERSION}") // Set headers from configuration if (configuration.authHeader.isNotEmpty()) { @@ -107,7 +107,7 @@ class EditorAssetsLibrary( connection.requestMethod = "GET" val defaultUserAgent = System.getProperty("http.agent") ?: "" - connection.setRequestProperty("User-Agent", "$defaultUserAgent GutenbergKit/${GutenbergKit.VERSION}") + connection.setRequestProperty("User-Agent", "$defaultUserAgent GutenbergKit/${GutenbergKitVersion.VERSION}") connection.connectTimeout = 30000 connection.readTimeout = 30000 diff --git a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt index 7d3a4c54..572b484a 100644 --- a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt +++ b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt @@ -128,7 +128,7 @@ class GutenbergView : WebView { // Set custom user agent val defaultUserAgent = this.settings.userAgentString - this.settings.userAgentString = "$defaultUserAgent GutenbergKit/${GutenbergKit.VERSION}" + this.settings.userAgentString = "$defaultUserAgent GutenbergKit/${GutenbergKitVersion.VERSION}" this.addJavascriptInterface(this, "editorDelegate") this.visibility = View.GONE diff --git a/android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt b/android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt index 2f92d4c9..b070e14a 100644 --- a/android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt +++ b/android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt @@ -161,6 +161,6 @@ class GutenbergViewTest { assertTrue("User agent should contain GutenbergKit identifier", userAgent.contains("GutenbergKit/")) assertTrue("User agent should contain version number", - userAgent.contains("GutenbergKit/${GutenbergKit.VERSION}")) + userAgent.contains("GutenbergKit/${GutenbergKitVersion.VERSION}")) } } diff --git a/bin/generate-version.js b/bin/generate-version.js index e085c50e..c50ef18f 100755 --- a/bin/generate-version.js +++ b/bin/generate-version.js @@ -33,8 +33,8 @@ info( `Generating version files for version ${ version }` ); const iosVersionContent = `// This file is auto-generated by bin/generate-version.js // Do not edit manually - changes will be overwritten -/// GutenbergKit library constants and utilities. -public enum GutenbergKit { +/// GutenbergKit version information. +public enum GutenbergKitVersion { /// The current version of GutenbergKit. public static let version = "${ version }" } @@ -42,7 +42,7 @@ public enum GutenbergKit { const iosVersionPath = join( rootDir, - 'ios/Sources/GutenbergKit/Sources/GutenbergKit.swift' + 'ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift' ); writeFileSync( iosVersionPath, iosVersionContent, 'utf8' ); info( `Generated ${ iosVersionPath }` ); @@ -54,9 +54,9 @@ const androidVersionContent = `// This file is auto-generated by bin/generate-ve package org.wordpress.gutenberg /** - * GutenbergKit library constants and utilities. + * GutenbergKit version information. */ -object GutenbergKit { +object GutenbergKitVersion { /** * The current version of GutenbergKit. */ @@ -66,7 +66,7 @@ object GutenbergKit { const androidVersionPath = join( rootDir, - 'android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKit.kt' + 'android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKitVersion.kt' ); writeFileSync( androidVersionPath, androidVersionContent, 'utf8' ); info( `Generated ${ androidVersionPath }` ); diff --git a/ios/Sources/GutenbergKit/Sources/EditorHTTPClient.swift b/ios/Sources/GutenbergKit/Sources/EditorHTTPClient.swift index 3739b5f2..431a0382 100644 --- a/ios/Sources/GutenbergKit/Sources/EditorHTTPClient.swift +++ b/ios/Sources/GutenbergKit/Sources/EditorHTTPClient.swift @@ -111,7 +111,7 @@ public actor EditorHTTPClient: EditorHTTPClientProtocol { private func configureRequest(_ request: URLRequest) -> URLRequest { var mutableRequest = request mutableRequest.addValue(self.authHeader, forHTTPHeaderField: "Authorization") - mutableRequest.addValue("\(Self.baseUserAgent) GutenbergKit/\(GutenbergKit.version)", forHTTPHeaderField: "User-Agent") + mutableRequest.addValue("\(Self.baseUserAgent) GutenbergKit/\(GutenbergKitVersion.version)", forHTTPHeaderField: "User-Agent") if let requestTimeout { mutableRequest.timeoutInterval = requestTimeout diff --git a/ios/Sources/GutenbergKit/Sources/EditorViewController.swift b/ios/Sources/GutenbergKit/Sources/EditorViewController.swift index 67c4ab4f..487fae5a 100644 --- a/ios/Sources/GutenbergKit/Sources/EditorViewController.swift +++ b/ios/Sources/GutenbergKit/Sources/EditorViewController.swift @@ -177,7 +177,7 @@ public final class EditorViewController: UIViewController, GutenbergEditorContro self.webView = GBWebView(frame: .zero, configuration: config) self.webView.scrollView.keyboardDismissMode = .interactive - self.webView.configuration.applicationNameForUserAgent = "GutenbergKit/\(GutenbergKit.version)" + self.webView.configuration.applicationNameForUserAgent = "GutenbergKit/\(GutenbergKitVersion.version)" self.isWarmupMode = isWarmupMode diff --git a/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift b/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift index 01792132..11c71512 100644 --- a/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift +++ b/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift @@ -94,7 +94,7 @@ final class HTMLWebViewRenderer { // Create web view with small initial frame for off-screen rendering // Frame will be adjusted per render based on viewport width and content height webView = WKWebView(frame: CGRect(x: 0, y: 0, width: 1200, height: 100), configuration: config) - webView.configuration.applicationNameForUserAgent = "GutenbergKit/\(GutenbergKit.version)" + webView.configuration.applicationNameForUserAgent = "GutenbergKit/\(GutenbergKitVersion.version)" delegate = RenderDelegate() webView.navigationDelegate = delegate diff --git a/ios/Tests/GutenbergKitTests/EditorHTTPClientTests.swift b/ios/Tests/GutenbergKitTests/EditorHTTPClientTests.swift index 9e4b1b63..ba8edafb 100644 --- a/ios/Tests/GutenbergKitTests/EditorHTTPClientTests.swift +++ b/ios/Tests/GutenbergKitTests/EditorHTTPClientTests.swift @@ -352,7 +352,7 @@ struct EditorHTTPClientTests { let capturedRequest = try #require(spySession.lastCapturedRequest) let userAgent = try #require(capturedRequest.value(forHTTPHeaderField: "User-Agent")) #expect(userAgent.contains("GutenbergKit/")) - #expect(userAgent.contains(GutenbergKit.version)) + #expect(userAgent.contains(GutenbergKitVersion.version)) } @Test("download() sets User-Agent header with GutenbergKit identifier") @@ -369,7 +369,7 @@ struct EditorHTTPClientTests { let capturedRequest = try #require(spySession.lastCapturedRequest) let userAgent = try #require(capturedRequest.value(forHTTPHeaderField: "User-Agent")) #expect(userAgent.contains("GutenbergKit/")) - #expect(userAgent.contains(GutenbergKit.version)) + #expect(userAgent.contains(GutenbergKitVersion.version)) } @Test("User-Agent header includes platform identifier") diff --git a/ios/Tests/GutenbergKitTests/GBWebViewTests.swift b/ios/Tests/GutenbergKitTests/GBWebViewTests.swift index e43c7136..bb5185b2 100644 --- a/ios/Tests/GutenbergKitTests/GBWebViewTests.swift +++ b/ios/Tests/GutenbergKitTests/GBWebViewTests.swift @@ -9,11 +9,11 @@ struct GBWebViewTests { let result = try await GBWebView().evaluateJavaScript("navigator.userAgent") let string = try #require(result as? String) - #expect(string.hasSuffix("GutenbergKit/\(GutenbergKit.version)")) + #expect(string.hasSuffix("GutenbergKit/\(GutenbergKitVersion.version)")) } func testVersionConstantExists() { - #expect(!GutenbergKit.version.isEmpty, "Version constant should not be empty") - #expect(GutenbergKit.version.contains("."), "Version should be in semantic versioning format") + #expect(!GutenbergKitVersion.version.isEmpty, "Version constant should not be empty") + #expect(GutenbergKitVersion.version.contains("."), "Version should be in semantic versioning format") } } From 5996461b07faf9fe139a3d6adc282f6306bb4056 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Sun, 21 Dec 2025 09:16:07 -0500 Subject: [PATCH 22/24] fix: Set user agent configuration before creating WebView Configuration updates have no impact on existing WebViews. --- ios/Sources/GutenbergKit/Sources/EditorViewController.swift | 3 ++- .../Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ios/Sources/GutenbergKit/Sources/EditorViewController.swift b/ios/Sources/GutenbergKit/Sources/EditorViewController.swift index 487fae5a..8018b4c7 100644 --- a/ios/Sources/GutenbergKit/Sources/EditorViewController.swift +++ b/ios/Sources/GutenbergKit/Sources/EditorViewController.swift @@ -175,9 +175,10 @@ public final class EditorViewController: UIViewController, GutenbergEditorContro self.bundleProvider.bind(to: config) + config.applicationNameForUserAgent = "GutenbergKit/\(GutenbergKitVersion.version)" + self.webView = GBWebView(frame: .zero, configuration: config) self.webView.scrollView.keyboardDismissMode = .interactive - self.webView.configuration.applicationNameForUserAgent = "GutenbergKit/\(GutenbergKitVersion.version)" self.isWarmupMode = isWarmupMode diff --git a/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift b/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift index 11c71512..d5e15615 100644 --- a/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift +++ b/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLWebViewRenderer.swift @@ -90,11 +90,11 @@ final class HTMLWebViewRenderer { init() { let config = WKWebViewConfiguration() config.suppressesIncrementalRendering = true + config.applicationNameForUserAgent = "GutenbergKit/\(GutenbergKitVersion.version)" // Create web view with small initial frame for off-screen rendering // Frame will be adjusted per render based on viewport width and content height webView = WKWebView(frame: CGRect(x: 0, y: 0, width: 1200, height: 100), configuration: config) - webView.configuration.applicationNameForUserAgent = "GutenbergKit/\(GutenbergKitVersion.version)" delegate = RenderDelegate() webView.navigationDelegate = delegate From c3bf06992872bb0dc9095c81e4d52ae9d3357cf5 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Sun, 21 Dec 2025 10:19:05 -0500 Subject: [PATCH 23/24] test: Ensure generated version files are present for test runs The npm postinstall hook generates the required native files. Also, it does not appear the Swift tests need the build output, so it's replaced with solely the npm dependencies target. --- .buildkite/pipeline.yml | 2 +- Makefile | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index d57fb340..0a5065dd 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -35,5 +35,5 @@ steps: plugins: *plugins - label: ':swift: Test Swift Package' - command: swift test + command: make test-swift-package plugins: *plugins diff --git a/Makefile b/Makefile index 7882bbef..a02a6690 100644 --- a/Makefile +++ b/Makefile @@ -140,11 +140,11 @@ test-js-watch: npm-dependencies ## Run JavaScript tests in watch mode npm run test:unit:watch .PHONY: test-swift-package -test-swift-package: build ## Run Swift package tests +test-swift-package: npm-dependencies ## Run Swift package tests $(call XCODEBUILD_CMD, test) .PHONY: test-android -test-android: ## Run Android tests +test-android: npm-dependencies ## Run Android tests @echo "--- :android: Running Android Tests" ./android/gradlew -p ./android :gutenberg:test From 7544b3b648896bb3f78dd6b4975da560db9209cb Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Sun, 21 Dec 2025 10:33:16 -0500 Subject: [PATCH 24/24] refactor: Track GutenbergKit version files in Git Ignoring these files caused CI failures with CodeQL managed by GitHub. We could possibly add custom CodeQL configuration to ensure the version files are generated for these CI tasks, but the simplest fix is tracking them for the time being. --- .buildkite/pipeline.yml | 2 +- .gitignore | 4 ---- Makefile | 4 ++-- .../org/wordpress/gutenberg/GutenbergKitVersion.kt | 14 ++++++++++++++ .../GutenbergKit/Sources/GutenbergKitVersion.swift | 8 ++++++++ 5 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKitVersion.kt create mode 100644 ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 0a5065dd..d57fb340 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -35,5 +35,5 @@ steps: plugins: *plugins - label: ':swift: Test Swift Package' - command: make test-swift-package + command: swift test plugins: *plugins diff --git a/.gitignore b/.gitignore index bb30d222..152f1095 100644 --- a/.gitignore +++ b/.gitignore @@ -198,7 +198,3 @@ src/translations/ # Claude .claude/settings.local.json - -# Auto-generated version files (regenerated by npm postinstall) -ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift -android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKitVersion.kt diff --git a/Makefile b/Makefile index a02a6690..7882bbef 100644 --- a/Makefile +++ b/Makefile @@ -140,11 +140,11 @@ test-js-watch: npm-dependencies ## Run JavaScript tests in watch mode npm run test:unit:watch .PHONY: test-swift-package -test-swift-package: npm-dependencies ## Run Swift package tests +test-swift-package: build ## Run Swift package tests $(call XCODEBUILD_CMD, test) .PHONY: test-android -test-android: npm-dependencies ## Run Android tests +test-android: ## Run Android tests @echo "--- :android: Running Android Tests" ./android/gradlew -p ./android :gutenberg:test diff --git a/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKitVersion.kt b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKitVersion.kt new file mode 100644 index 00000000..ad69da25 --- /dev/null +++ b/android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergKitVersion.kt @@ -0,0 +1,14 @@ +// This file is auto-generated by bin/generate-version.js +// Do not edit manually - changes will be overwritten + +package org.wordpress.gutenberg + +/** + * GutenbergKit version information. + */ +object GutenbergKitVersion { + /** + * The current version of GutenbergKit. + */ + const val VERSION = "0.11.1" +} diff --git a/ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift b/ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift new file mode 100644 index 00000000..e07cb3fe --- /dev/null +++ b/ios/Sources/GutenbergKit/Sources/GutenbergKitVersion.swift @@ -0,0 +1,8 @@ +// This file is auto-generated by bin/generate-version.js +// Do not edit manually - changes will be overwritten + +/// GutenbergKit version information. +public enum GutenbergKitVersion { + /// The current version of GutenbergKit. + public static let version = "0.11.1" +}