feat(remote-config): add network scaffolding for GET /v2/config endpoint (#6854)

* feat(remote-config): add network scaffolding for GET /v2/config endpoint

Adds RemoteConfigResponse model, GetRemoteConfigOperation, RemoteConfigAPI,
and wires everything into Backend. Unknown topic keys in the manifest are
silently dropped for forward compatibility. Mirrors Android PR #3435.

* fix(remote-config): use lowercase "default" variant key in test fixtures

Aligns with Android convention (DEFAULT_ENTRY_ID = "default") established
in purchases-android PR #3437.

* test(remote-config): add snapshot files for all CI OS versions

BackendGetRemoteConfigTests was only recording snapshots for iOS 26.
CI also runs tests on iOS 14/15/16/17/18, tvOS 18, macOS, and watchOS,
each of which requires its own snapshot file.

Derived from existing snapshot patterns in BackendGetRewardVerificationStatusTests.

* fix(remote-config): log warning when unknown topic key is received

Follows the codebase convention of logging unknown backend values
(e.g. WorkflowsResponse unknown_workflow_trigger_type) rather than
silently dropping them.

* refactor(remote-config): rename wireKey to rawValue, add WIP comment for signature verification

Aligns Topic with Swift's RawRepresentable convention and matches the
Android SDK's comment indicating signature verification will be enabled
once the remote config endpoint is finalized.

* refactor(remote-config): enable etag and simplify HTTPRequestPath cases
This commit is contained in:
Rick authored and GitHub committed 2026-06-02 17:19:05 +02:00
1 parent d30edd35e9
commit 7ee15c73a2
103 files changed
+3478 -10

No files matched your search

@@ -109,6 +109,8 @@ extension HTTPRequest {
case postCreateTicket
case isPurchaseAllowedByRestoreBehavior(appUserID: String)
case rewardVerificationStatus(appUserID: String, clientTransactionID: String)
// WIP: endpoint path and signing requirements subject to change
case getRemoteConfig
}
@@ -197,7 +199,8 @@ extension HTTPRequest.Path: HTTPRequestPath {
.appHealthReport,
.postCreateTicket,
.isPurchaseAllowedByRestoreBehavior,
.rewardVerificationStatus:
.rewardVerificationStatus,
.getRemoteConfig:
return true
case .health,
@@ -226,7 +229,8 @@ extension HTTPRequest.Path: HTTPRequestPath {
.appHealthReport,
.postCreateTicket,
.isPurchaseAllowedByRestoreBehavior,
.rewardVerificationStatus:
.rewardVerificationStatus,
.getRemoteConfig:
return true
case .health,
.appHealthReportAvailability:
@@ -256,7 +260,9 @@ extension HTTPRequest.Path: HTTPRequestPath {
.postOfferForSigning,
.postRedeemWebPurchase,
.getCustomerCenterConfig,
.postCreateTicket:
.postCreateTicket,
// WIP: Move to true when we have the final endpoint for remote config, and we can remove the fallback
.getRemoteConfig:
return false
case .rewardVerificationStatus:
return true
@@ -286,13 +292,19 @@ extension HTTPRequest.Path: HTTPRequestPath {
.getProductEntitlementMapping,
.getCustomerCenterConfig,
.appHealthReport,
.postCreateTicket:
.postCreateTicket,
.getRemoteConfig:
return false
}
}
var relativePath: String {
return "/v1/\(self.pathComponent)"
switch self {
case .getRemoteConfig:
return "/v2/\(self.pathComponent)"
default:
return "/v1/\(self.pathComponent)"
}
}
var pathComponent: String {
@@ -362,6 +374,9 @@ extension HTTPRequest.Path: HTTPRequestPath {
case let .rewardVerificationStatus(appUserID, clientTransactionID):
return "subscribers/\(Self.escape(appUserID))/ads/reward_verifications/\(Self.escape(clientTransactionID))"
case .getRemoteConfig:
return "config"
}
}
@@ -428,6 +443,9 @@ extension HTTPRequest.Path: HTTPRequestPath {
case .rewardVerificationStatus:
return "get_reward_verification_status"
case .getRemoteConfig:
return "get_remote_config"
}
}