Files
purchases-ios/Sources/Networking/RemoteConfigSignatureContextProvider.swift
T
Rick b82223e5a1 feat(remote-config): add static fallback config endpoint (#7182)
* feat(remote-config): add static fallback config endpoint

* refactor(remote-config): clarify static fallback path name

* test(remote-config): cover static fallback signature failures

* fix(remote-config): use static fallback only without cache

* test(remote-config): cover static fallback external blobs

* refactor(remote-config): align static fallback naming

* refactor(remote-config): scope static fallback path

* fix(remote-config): mark fallback no content fresh

* fix(remote-config): align fallback accept headers

* refactor(remote-config): rename fallback endpoint types

* fix(remote-config): enable etags for fallback config

* fix(remote-config): skip fallback when proxy is configured

* fix(remote-config): rely on etag cache for fallback

* refactor(remote-config): reuse fallback retry eligibility

* fix(remote-config): disable after fallback client errors
2026-07-10 14:17:31 +02:00

70 lines
2.3 KiB
Swift

//
// RemoteConfigSignatureContextProvider.swift
// RevenueCat
//
// Created by RevenueCat.
// Copyright © 2026 RevenueCat, Inc. All rights reserved.
import Foundation
/// Provides the signature inputs for remote config RC Container responses.
///
/// The response signature covers the config element's decoded payload bytes. A `204 No Content`
/// response verifies the request context with an empty payload.
struct RemoteConfigSignatureContextProvider: ResponseSignatureContextProvider {
func responsePayloadForSignature(from body: Data?, statusCode: HTTPStatusCode) throws -> Data? {
guard statusCode != .noContent else {
return Data()
}
return try Self.configPayload(from: body)
}
func requestBodyForSignature(for request: HTTPRequest) -> HTTPRequestBody? {
return nil
}
}
/// Provides signature inputs for static JSON remote config fallback responses.
///
/// Fallback config signs the JSON response body directly. It does not include request body
/// parameters because the fallback endpoint is a fallback `GET`.
struct FallbackSignatureContextProvider: ResponseSignatureContextProvider {
func responsePayloadForSignature(from body: Data?, statusCode: HTTPStatusCode) throws -> Data? {
guard let body else {
throw RCContainer.Parser.FormatError.missingBody
}
return body
}
func requestBodyForSignature(for request: HTTPRequest) -> HTTPRequestBody? {
return nil
}
}
private extension RemoteConfigSignatureContextProvider {
/// Extracts the signed payload from a remote config RC Container response.
///
/// Remote config defines the first RC Container element as the config element. The backend signs
/// that element's payload bytes, not the full container body or stored checksum. This intentionally
/// parses only the first element so verification can fail before the full response is decoded later.
static func configPayload(from data: Data?) throws -> Data {
guard let data = data else {
throw RCContainer.Parser.FormatError.missingBody
}
let configElement = try RemoteConfigContainer.configElement(from: data)
return try configElement.withDecodedPayloadBytes { bytes in
Data(bytes)
}
}
}