[2026-07-resubmission] Add guest browsing flow with App Attest session for App Review resubmission

Adds a pre-login public store locator (guest session via DeviceCheck/App
Attest, keychain-backed token storage) so the app no longer forces sign-in
before showing any content, plus updated support URL metadata.
This commit is contained in:
Daniel Arantes Loverde
2026-07-30 11:35:25 -03:00
parent 3e93196b92
commit 017bd7168f
21 changed files with 1140 additions and 136 deletions

View File

@@ -57,19 +57,30 @@ struct ApiRequest: Sendable {
let requiresAuth: Bool
let queryItems: [URLQueryItem]
let body: Data?
/// Overrides `ApiConfig.baseURL` (Atomenta) for this single request used
/// to reach the PediFoods BFF (`ApiConfig.pediFoodsBFFURL`) instead.
let baseURLOverride: URL?
/// Sent as `Authorization: Bearer <token>` instead of the customer JWT.
/// Used for guest-session-authenticated public locator calls, which must
/// never touch `TokenStore`'s customer session.
let customBearerToken: String?
init(path: String,
method: String = "GET",
module: ApiModule = .none,
requiresAuth: Bool = true,
queryItems: [URLQueryItem] = [],
body: Data? = nil) {
body: Data? = nil,
baseURLOverride: URL? = nil,
customBearerToken: String? = nil) {
self.path = path
self.method = method
self.module = module
self.requiresAuth = requiresAuth
self.queryItems = queryItems
self.body = body
self.baseURLOverride = baseURLOverride
self.customBearerToken = customBearerToken
}
}
@@ -120,7 +131,7 @@ final class ApiClient: @unchecked Sendable {
private extension ApiClient {
#if canImport(LCEssentials) && os(iOS)
func sendWithLCEssentials<T: Decodable>(_ request: ApiRequest) async throws -> T {
let urlString = try buildURL(path: request.path, query: request.queryItems).absoluteString
let urlString = try buildURL(path: request.path, query: request.queryItems, baseURL: request.baseURLOverride ?? ApiConfig.baseURL).absoluteString
let method = request.method
let headers = buildHeaders(for: request)
let params = request.body
@@ -228,7 +239,7 @@ private extension ApiClient {
#endif
func sendWithURLSession<T: Decodable>(_ request: ApiRequest) async throws -> T {
let url = try buildURL(path: request.path, query: request.queryItems)
let url = try buildURL(path: request.path, query: request.queryItems, baseURL: request.baseURLOverride ?? ApiConfig.baseURL)
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = request.method
urlRequest.httpBody = request.body
@@ -314,7 +325,9 @@ private extension ApiClient {
if let token = ApiConfig.token(for: request.module) {
headers["Atomenta-Token"] = token
}
if request.requiresAuth, let jwt = tokenStore.jwt {
if let customBearerToken = request.customBearerToken {
headers["Authorization"] = "Bearer \(customBearerToken)"
} else if request.requiresAuth, let jwt = tokenStore.jwt {
headers["Authorization"] = "Bearer \(jwt)"
}
return headers
@@ -474,8 +487,8 @@ private extension ApiClient {
return trimmed
}
func buildURL(path: String, query: [URLQueryItem]) throws -> URL {
guard var components = URLComponents(url: ApiConfig.baseURL, resolvingAgainstBaseURL: false) else {
func buildURL(path: String, query: [URLQueryItem], baseURL: URL) throws -> URL {
guard var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false) else {
throw NetworkError.invalidURL
}
components.path = components.path.appending(path)