From 06c36c4352f47655cb39a5042a7e5fd475c118e1 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Wed, 15 Apr 2026 15:29:57 -0500 Subject: feat: add custom hutch user-agent to api calls Implements: https://todo.sr.ht/~ccleberg/hutch/64 --- Hutch/Extensions/Bundle+UserAgent.swift | 14 ++++++++++++++ Hutch/Networking/HutchStatsService.swift | 5 ++++- Hutch/Networking/ManPageService.swift | 4 +++- Hutch/Networking/SRHTClient.swift | 5 +++++ Hutch/Networking/SystemStatusService.swift | 9 +-------- 5 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 Hutch/Extensions/Bundle+UserAgent.swift (limited to 'Hutch') diff --git a/Hutch/Extensions/Bundle+UserAgent.swift b/Hutch/Extensions/Bundle+UserAgent.swift new file mode 100644 index 0000000..2200576 --- /dev/null +++ b/Hutch/Extensions/Bundle+UserAgent.swift @@ -0,0 +1,14 @@ +import Foundation + +extension Bundle { + /// The HTTP `User-Agent` string sent with all Hutch network requests. + /// + /// Format: `Hutch/` + var hutchUserAgent: String { + let name = (object(forInfoDictionaryKey: "CFBundleDisplayName") as? String) + ?? (object(forInfoDictionaryKey: "CFBundleName") as? String) + ?? "Hutch" + let version = (object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String) ?? "dev" + return "\(name)/\(version)" + } +} diff --git a/Hutch/Networking/HutchStatsService.swift b/Hutch/Networking/HutchStatsService.swift index aead9c6..fbac5be 100644 --- a/Hutch/Networking/HutchStatsService.swift +++ b/Hutch/Networking/HutchStatsService.swift @@ -54,9 +54,12 @@ struct HutchStatsService: ContributionCalendarServing { throw URLError(.badURL) } + var request = URLRequest(url: url) + request.setValue(Bundle.main.hutchUserAgent, forHTTPHeaderField: "User-Agent") + let (data, response): (Data, URLResponse) do { - (data, response) = try await session.data(from: url) + (data, response) = try await session.data(for: request) } catch { throw SRHTError.networkError(error) } diff --git a/Hutch/Networking/ManPageService.swift b/Hutch/Networking/ManPageService.swift index 07d5c2d..4453572 100644 --- a/Hutch/Networking/ManPageService.swift +++ b/Hutch/Networking/ManPageService.swift @@ -18,7 +18,9 @@ struct ManPageService { throw URLError(.badURL) } - let (data, response) = try await URLSession.shared.data(from: url) + var request = URLRequest(url: url) + request.setValue(Bundle.main.hutchUserAgent, forHTTPHeaderField: "User-Agent") + let (data, response) = try await URLSession.shared.data(for: request) if let http = response as? HTTPURLResponse, !(200...299).contains(http.statusCode) { throw URLError(.badServerResponse) diff --git a/Hutch/Networking/SRHTClient.swift b/Hutch/Networking/SRHTClient.swift index 70c7650..b4f8ee0 100644 --- a/Hutch/Networking/SRHTClient.swift +++ b/Hutch/Networking/SRHTClient.swift @@ -66,6 +66,7 @@ final class SRHTClient: Sendable { // Build request var request = URLRequest(url: service.url) request.httpMethod = "POST" + request.setValue(Bundle.main.hutchUserAgent, forHTTPHeaderField: "User-Agent") request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") @@ -181,6 +182,7 @@ final class SRHTClient: Sendable { var request = URLRequest(url: service.url) request.httpMethod = "POST" + request.setValue(Bundle.main.hutchUserAgent, forHTTPHeaderField: "User-Agent") request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") @@ -315,6 +317,7 @@ final class SRHTClient: Sendable { var request = URLRequest(url: service.url) request.httpMethod = "POST" + request.setValue(Bundle.main.hutchUserAgent, forHTTPHeaderField: "User-Agent") request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") @@ -436,6 +439,7 @@ final class SRHTClient: Sendable { var request = URLRequest(url: service.url) request.httpMethod = "POST" + request.setValue(Bundle.main.hutchUserAgent, forHTTPHeaderField: "User-Agent") request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") @@ -535,6 +539,7 @@ final class SRHTClient: Sendable { } var request = URLRequest(url: url) + request.setValue(Bundle.main.hutchUserAgent, forHTTPHeaderField: "User-Agent") request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") let (data, response): (Data, URLResponse) diff --git a/Hutch/Networking/SystemStatusService.swift b/Hutch/Networking/SystemStatusService.swift index ec2d233..35b82a1 100644 --- a/Hutch/Networking/SystemStatusService.swift +++ b/Hutch/Networking/SystemStatusService.swift @@ -60,14 +60,7 @@ struct SystemStatusService: Sendable { return data } - private var userAgent: String { - let bundle = Bundle.main - let name = (bundle.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String) - ?? (bundle.object(forInfoDictionaryKey: "CFBundleName") as? String) - ?? "Hutch" - let version = (bundle.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String) ?? "dev" - return "\(name)/\(version) (System Status)" - } + private var userAgent: String { Bundle.main.hutchUserAgent } } extension SystemStatusService: SystemStatusServing {} -- cgit v1.2.3