From 14822e8b65dadb1bd7a64eeca2e6332c571dfe57 Mon Sep 17 00:00:00 2001 From: Stephen Cooper <1970249+scooper4711@users.noreply.github.com> Date: Tue, 20 Oct 2026 17:14:06 +0200 Subject: [PATCH] feat: Name the app's version and project in its user agent Every request now ends its Safari user agent with Scrollkeeper/ (+https://github.com/scooper4711/scrollkeeper) instead of a fixed Scrollkeeper/1.0, so Paizo can tell releases apart, measure the app's load and block it if it ever has to. --- .../Networking/URLSessionHTTPClient.swift | 15 ++++++++++++--- .../SystemIntegrationTests.swift | 7 +++++++ docs/specs/library-manager/design.md | 3 +++ docs/specs/library-manager/requirements.md | 3 +++ 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/Sources/ScrollkeeperKit/Networking/URLSessionHTTPClient.swift b/Sources/ScrollkeeperKit/Networking/URLSessionHTTPClient.swift index 5a46b4d..6f61908 100644 --- a/Sources/ScrollkeeperKit/Networking/URLSessionHTTPClient.swift +++ b/Sources/ScrollkeeperKit/Networking/URLSessionHTTPClient.swift @@ -18,7 +18,7 @@ public final class URLSessionHTTPClient: HTTPClient { configuration.urlCache = nil configuration.requestCachePolicy = .reloadIgnoringLocalCacheData configuration.timeoutIntervalForRequest = 180 - configuration.httpAdditionalHeaders = ["User-Agent": userAgent] + configuration.httpAdditionalHeaders = ["User-Agent": userAgent(version: appVersion)] return configuration } @@ -68,8 +68,17 @@ public final class URLSessionHTTPClient: HTTPClient { (response as? HTTPURLResponse)?.statusCode ?? 0 } - private static let userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 " - + "(KHTML, like Gecko) Version/18.0 Safari/605.1.15 Scrollkeeper/1.0" + /// Safari's user agent, so paizo.com treats the app like a browser, then the app's name, version and project + /// page, so Paizo can tell its requests apart, measure them and block them if it ever has to. + static func userAgent(version: String) -> String { + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 " + + "Safari/605.1.15 Scrollkeeper/\(version) (+https://github.com/scooper4711/scrollkeeper)" + } + + /// The running app's version; "0" where there is no app bundle, as in the unit tests. + private static var appVersion: String { + Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "0" + } } /// Reports the fraction completed of one download task. diff --git a/Tests/ScrollkeeperKitTests/SystemIntegrationTests.swift b/Tests/ScrollkeeperKitTests/SystemIntegrationTests.swift index 3beddfd..8130293 100644 --- a/Tests/ScrollkeeperKitTests/SystemIntegrationTests.swift +++ b/Tests/ScrollkeeperKitTests/SystemIntegrationTests.swift @@ -77,6 +77,13 @@ final class StubURLProtocol: URLProtocol { #expect(configuration.urlCache == nil) #expect(configuration.requestCachePolicy == .reloadIgnoringLocalCacheData) } + + @Test func userAgentNamesTheAppItsVersionAndItsProjectAfterSafari() { + let agent = URLSessionHTTPClient.userAgent(version: "1.4.2") + #expect(agent.hasPrefix("Mozilla/5.0 ")) + #expect(agent.contains(" Safari/")) + #expect(agent.hasSuffix(" Scrollkeeper/1.4.2 (+https://github.com/scooper4711/scrollkeeper)")) + } } @Suite struct ResumeDataStoreTests { diff --git a/docs/specs/library-manager/design.md b/docs/specs/library-manager/design.md index d5edbaf..75229e1 100644 --- a/docs/specs/library-manager/design.md +++ b/docs/specs/library-manager/design.md @@ -130,6 +130,9 @@ Views ──► LibraryStore ──► CatalogSynchronizer ──► LibraryCata - `HTTPClient` — protocol with `send(_:)` and `download(_:to:progress:)`. `URLSessionHTTPClient` is the production implementation; tests use a stub. + Its user agent is Safari's, so paizo.com serves the app as it serves a + browser, followed by `Scrollkeeper/ + (+https://github.com/scooper4711/scrollkeeper)` (requirement 9.4). - `PaizoSession` — actor. Signs in and hands out customer tokens, reusing one until shortly before the expiry it carries. - `FlightPayloadParser` — extracts the `LibraryPage` from library page HTML. diff --git a/docs/specs/library-manager/requirements.md b/docs/specs/library-manager/requirements.md index 884b9db..76adb83 100644 --- a/docs/specs/library-manager/requirements.md +++ b/docs/specs/library-manager/requirements.md @@ -202,6 +202,9 @@ once, online or off. 9.2 No account data, tokens or credentials are written to logs or committed to the repository. 9.3 The app does not change anything on the Paizo account. +9.4 Every request names the app, its version and its project page in the user + agent, after Safari's, so Paizo can tell the app's traffic apart, measure + it and block it if it ever has to. ## 10. iPad