Skip to content
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
15 changes: 12 additions & 3 deletions Sources/ScrollkeeperKit/Networking/URLSessionHTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions Tests/ScrollkeeperKitTests/SystemIntegrationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions docs/specs/library-manager/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<CFBundleShortVersionString>
(+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.
Expand Down
3 changes: 3 additions & 0 deletions docs/specs/library-manager/requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading