diff --git a/Outpost/Support/AvatarBadge.swift b/Outpost/Support/AvatarBadge.swift index a5920a7..13da9dd 100644 --- a/Outpost/Support/AvatarBadge.swift +++ b/Outpost/Support/AvatarBadge.swift @@ -1,4 +1,5 @@ import SwiftUI +import OutlineKit #if os(macOS) import AppKit @@ -45,16 +46,27 @@ struct AvatarBadge: View { } } - /// A freshly-uploaded attachment's redirect URL can 404/fail on the very - /// first request right after upload (self-hosted reverse-proxied - /// storage, not necessarily instantly consistent) — since `.task(id:)` - /// only ever runs once per URL and never retries on its own, a single - /// transient failure right after uploading a new avatar would leave the - /// placeholder showing forever, even though the exact same URL works - /// fine moments later. Two short-delayed retries before giving up. + /// The real fix, confirmed against a live network capture: every other + /// request this app makes attaches `Authorization: Bearer ` — + /// this one never did, sending a bare unauthenticated GET. Outline's + /// browser session authenticates `attachments.redirect` via cookies + /// instead, which a native app doesn't have; the API-token equivalent + /// is the same Bearer header every RPC call already uses. Almost + /// certainly means no avatar image (not just a freshly-uploaded one) + /// has ever actually loaded in this app — a 401 and a "no avatar set" + /// look identical here, both just fall back to the placeholder icon + /// with nothing on screen to flag it as an error. + /// + /// The retry loop is a secondary, independent hardening — cheap + /// insurance against a self-hosted reverse-proxied storage backend not + /// being instantly consistent right after an upload — kept alongside + /// the auth fix rather than instead of it. private static func loadImage(from url: URL) async -> PlatformImage? { var request = URLRequest(url: url) request.cachePolicy = .reloadIgnoringLocalCacheData + if let token = try? KeychainTokenStore().token() { + request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + } for attempt in 0..<3 { if attempt > 0 {