## Summary - Reworks the iOS onboarding, native sign-in, and paywall flow so first-install and cold-start entry stay native, adaptive, and aligned with the current login/subscription gates. - Removes onboarding swipe paging, refines onboarding/paywall layout behavior, and keeps AI/paywall entry behavior consistent for logged-in and subscribed users. - Adds the new mobile all-docs empty states with localized copy and dialog entry points, and closes the remaining review follow-ups by removing the onboarding plan artifact and dropping the iOS AI subscription bypass. ## Test plan - Built the iOS app for simulator with `xcodebuild -workspace App.xcworkspace -scheme App -configuration Debug -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' ARCHS=arm64 ONLY_ACTIVE_ARCH=YES CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO build` during the native onboarding/sign-in flow work. - Let the repo pre-commit hooks run on the latest follow-up commit (`prettier` + `eslint --fix`). - Checked diagnostics for the edited TS/TSX files after the review follow-up changes. - Manually iterated on onboarding/native sign-in/paywall UI states in simulator during implementation. --------- Co-authored-by: DarkSky <darksky2048@gmail.com>
70 lines
2.0 KiB
Swift
70 lines
2.0 KiB
Swift
import Foundation
|
|
|
|
public final class AuthAccessTokenCache: @unchecked Sendable {
|
|
public static let shared = AuthAccessTokenCache()
|
|
|
|
private struct Entry {
|
|
let token: String
|
|
let expiresAt: Date
|
|
}
|
|
|
|
private let lock = NSLock()
|
|
private var entries: [String: Entry] = [:]
|
|
|
|
private init() {}
|
|
|
|
public func set(_ token: String, expiresAt: Date, for endpoint: String) {
|
|
guard let key = Self.canonicalOrigin(endpoint) else { return }
|
|
lock.lock()
|
|
entries[key] = Entry(token: token, expiresAt: expiresAt)
|
|
lock.unlock()
|
|
}
|
|
|
|
public func token(for endpoint: URL, minimumValidity: TimeInterval = 0) -> String? {
|
|
token(for: endpoint, matching: nil, minimumValidity: minimumValidity)
|
|
}
|
|
|
|
public func token(
|
|
for endpoint: URL,
|
|
matching origin: URL?,
|
|
minimumValidity: TimeInterval = 0
|
|
) -> String? {
|
|
guard let key = Self.canonicalOrigin(endpoint) else { return nil }
|
|
if let origin, Self.canonicalOrigin(origin) != key { return nil }
|
|
lock.lock()
|
|
defer { lock.unlock() }
|
|
|
|
guard let entry = entries[key], entry.expiresAt.timeIntervalSinceNow > minimumValidity else {
|
|
return nil
|
|
}
|
|
return entry.token
|
|
}
|
|
|
|
public func remove(for endpoint: String) {
|
|
guard let key = Self.canonicalOrigin(endpoint) else { return }
|
|
lock.lock()
|
|
entries[key] = nil
|
|
lock.unlock()
|
|
}
|
|
|
|
private static func canonicalOrigin(_ endpoint: String) -> String? {
|
|
guard let url = URL(string: endpoint) else { return nil }
|
|
return canonicalOrigin(url)
|
|
}
|
|
|
|
private static func canonicalOrigin(_ url: URL) -> String? {
|
|
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
|
|
let scheme = components.scheme?.lowercased(),
|
|
let host = components.host?.lowercased()
|
|
else {
|
|
return nil
|
|
}
|
|
|
|
let defaultPort = scheme == "https" ? 443 : (scheme == "http" ? 80 : nil)
|
|
if let port = components.port, port != defaultPort {
|
|
return "\(scheme)://\(host):\(port)"
|
|
}
|
|
return "\(scheme)://\(host)"
|
|
}
|
|
}
|