chadtmiller.com/knotless
git that lives in your Atmosphere account
1
10
The iOS app sends a + in a query as a +, so a +page.svelte diff loads
ios/Knotless/API/Client.swift+16−6
| @@ -18,6 +18,16 @@ actor Client { | ||
static let shared = Client() | ||
nonisolated static let site = URL(string: "https://knotless.dev/")! | ||
| ||
/// `base` asked with `items`. URLComponents leaves a `+` in a value as it | ||
/// is, which a server reads as a space: a path such as | ||
/// `routes/+page.svelte` would name a file that isn't there. | ||
nonisolated static func url(_ base: URL, _ items: [URLQueryItem]) -> URL? { | ||
guard var parts = URLComponents(url: base, resolvingAgainstBaseURL: false) else { return nil } | ||
parts.queryItems = items | ||
parts.percentEncodedQuery = parts.percentEncodedQuery?.replacingOccurrences(of: "+", with: "%2B") | ||
return parts.url | ||
} | ||
| ||
private let decoder = JSONDecoder() | ||
private let session: URLSession = { | ||
// The cookie is sent by hand: none is ever kept in a shared store. | ||
| @@ -43,9 +53,10 @@ actor Client { | ||
return (try? decoder.decode(Found.self, from: answer))?.actors ?? [] | ||
} | ||
#endif | ||
var parts = URLComponents(string: "https://typeahead.waow.tech/xrpc/tech.waow.typeahead.searchActors")! | ||
parts.queryItems = [URLQueryItem(name: "q", value: text), URLQueryItem(name: "limit", value: "6")] | ||
var request = URLRequest(url: parts.url!) | ||
let url = Self.url( | ||
URL(string: "https://typeahead.waow.tech/xrpc/tech.waow.typeahead.searchActors")!, | ||
[URLQueryItem(name: "q", value: text), URLQueryItem(name: "limit", value: "6")])! | ||
var request = URLRequest(url: url) | ||
request.timeoutInterval = 10 | ||
do { | ||
let (body, response) = try await session.data(for: request) | ||
| @@ -71,9 +82,8 @@ actor Client { | ||
return try decoder.decode(T.self, from: recorded.answer(method, params)) | ||
} | ||
#endif | ||
var parts = URLComponents(url: Self.site.appendingPathComponent("xrpc/" + nsid), resolvingAgainstBaseURL: false)! | ||
parts.queryItems = items.sorted { $0.name < $1.name } | ||
var request = URLRequest(url: parts.url!) | ||
let url = Self.url(Self.site.appendingPathComponent("xrpc/" + nsid), items.sorted { $0.name < $1.name })! | ||
var request = URLRequest(url: url) | ||
request.timeoutInterval = 40 | ||
authorize(&request, nsid) | ||
return try await send(request) | ||
ios/Knotless/Support/MarkdownPlace.swift+2−0
| @@ -46,6 +46,8 @@ struct MarkdownPlace: Hashable, Sendable { | ||
var parts = URLComponents(string: "\(Self.site)/xrpc/dev.knotless.getAttachment") | ||
else { return nil } | ||
parts.queryItems = [.init(name: "repo", value: repo.uri), .init(name: "uri", value: subject), .init(name: "cid", value: cid)] | ||
// As Client.url does: a server reads a bare + as a space. | ||
parts.percentEncodedQuery = parts.percentEncodedQuery?.replacingOccurrences(of: "+", with: "%2B") | ||
return parts.url | ||
} | ||
} | ||
ios/Knotless/Views/RunViews.swift+4−3
| @@ -914,9 +914,10 @@ struct LiveRun: Sendable { | ||
static func follow(repo: String, uri: String) -> AsyncStream<LiveRun> { | ||
AsyncStream { continuation in | ||
let task = Task { | ||
var parts = URLComponents(url: Client.site.appendingPathComponent("_live/check"), resolvingAgainstBaseURL: false) | ||
parts?.queryItems = [URLQueryItem(name: "repo", value: repo), URLQueryItem(name: "uri", value: uri)] | ||
guard let url = parts?.url else { return continuation.finish() } | ||
guard let url = Client.url( | ||
Client.site.appendingPathComponent("_live/check"), | ||
[URLQueryItem(name: "repo", value: repo), URLQueryItem(name: "uri", value: uri)]) | ||
else { return continuation.finish() } | ||
var request = URLRequest(url: url) | ||
request.setValue("text/event-stream", forHTTPHeaderField: "Accept") | ||
request.timeoutInterval = 660 | ||