Add network scaffolding for remote config endpoint#3435
Conversation
Adds GET /v1/subscribers/{appUserId}/config to fetch SDK configuration
(api_sources, asset_sources, manifest of asset topics) that will be
consumed by future PRs.
Includes:
- Endpoint.GetRemoteConfig with signing+nonce and fallback URL support
- @serializable RemoteConfigResponse with custom TopicsMapSerializer
that drops unknown topic names so future backend additions don't
break older SDKs
- Backend.getRemoteConfig() background-aware method modeled on
getVirtualCurrencies
- Unit tests covering parsing, unknown-field tolerance, errors, and
call deduplication
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #3435 +/- ##
==========================================
- Coverage 79.47% 79.46% -0.01%
==========================================
Files 362 363 +1
Lines 14547 14625 +78
Branches 1977 1993 +16
==========================================
+ Hits 11561 11622 +61
- Misses 2190 2196 +6
- Partials 796 807 +11 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Drop config_version, api_sources, and AssetSource.{test_url, blacklist_time_seconds};
rename asset_sources to sources; rename TopicEntry.asset_blob_ref to blob_ref and drop
content_type/prefetch. Rename Kotlin AssetSource to Source now that it has no peer.
These fields can be reintroduced when consumers need them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
39308f9 to
01f4829
Compare
Set REMOTE_CONFIG_BASE_URL in local.properties to redirect only the GET /v1/subscribers/<id>/config request to a developer-controlled host (e.g. http://localhost:8080/). Other endpoints continue to hit appConfig.baseURL. Empty by default, and only takes effect in debug builds, so production traffic is unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The remote-config server contract changed: the URL drops the user id (/v2/config) and the response root renames `sources` to `blob_sources` plus adds a new `api_sources` array describing API host candidates. - Endpoint.GetRemoteConfig becomes a parameterless object. - Backend.getRemoteConfig drops the appUserID parameter. - RemoteConfigResponse: `sources: List<Source>` -> `blobSources: List<BlobSource>`, add `apiSources: List<ApiSource>` with the new shape. - Stub appConfig.isDebugBuild in BackendGetRemoteConfigTest setUp (was missing since the override-base-URL commit, causing pre-existing MockKExceptions in non-success-path tests).
| override fun getPath(useFallback: Boolean) = pathTemplate.format(Uri.encode(userId)) | ||
| } | ||
| object GetRemoteConfig : Endpoint( | ||
| pathTemplate = "/v2/config", |
There was a problem hiding this comment.
This is not final... but we can iterate on it in future PRs.
Also, we need to decide whether this endpoint will be available in the fallback url... But again, we can leave this for once this is more advanced
| val path = endpoint.getPath() | ||
| val cacheKey = BackgroundAwareCallbackCacheKey(listOf(path), appInBackground) | ||
|
|
||
| val overrideURL = BuildConfig.REMOTE_CONFIG_BASE_URL |
There was a problem hiding this comment.
Keeping this while we test things, but the intention is for it to go away in the final version.
ajpallares
left a comment
There was a problem hiding this comment.
Looking good! I only have a couple of comments.
Also, I think the PR description is a bit stale:
- It still references the previous
GET /v1/subscribers/{appUserID}/config. - It says "concurrent calls for the same user dedupe". But the cache key doesn't really depend on the appUserID anymore, so it'd just be "concurrent calls dedupe"
- The current bullet still mentions
RemoteConfigResponse(sources, manifest)and a singleSource(id, urlFormat, priority, weight).
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit a7f33ec. Configure here.
| } | ||
|
|
||
| private fun <K, V> entry(key: K, value: V): Map.Entry<K, V> = | ||
| java.util.AbstractMap.SimpleEntry(key, value) |
There was a problem hiding this comment.
Inline fully-qualified reference instead of import
Low Severity
The entry helper uses the fully-qualified inline reference java.util.AbstractMap.SimpleEntry instead of importing java.util.AbstractMap (or java.util.AbstractMap.SimpleEntry) at the top of the file. This violates the rule requiring FQN imports over inline fully-qualified references.
Triggered by learned rule: Use FQN imports, not inline fully-qualified references
Reviewed by Cursor Bugbot for commit a7f33ec. Configure here.
**This is an automatic release.** ## RevenueCat SDK ### 📦 Dependency Updates * [RENOVATE] Update dependency gradle to v8.14.5 (#3459) via RevenueCat Git Bot (@RCGitBot) ## RevenueCatUI SDK ### ✨ New Features * Pre-warm image cache for workflow step states (#3447) via Cesar de la Vega (@vegaro) ### Paywallv2 #### ✨ New Features * Add `close_workflow` button action (#3453) via Cesar de la Vega (@vegaro) #### 🐞 Bugfixes * Fix preload VideoComponent fallback override images (#3449) via Cesar de la Vega (@vegaro) ### 🔄 Other Changes * Select blob source by priority and weighted random (#3458) via Toni Rico (@tonidero) * [AUTOMATIC] Update golden test files for backend integration tests (#3473) via RevenueCat Git Bot (@RCGitBot) * Clean up unreferenced topic files after successful remote-config refresh (#3439) via Toni Rico (@tonidero) * Cache remote config response in memory with TTL and persist to disk (#3457) via Toni Rico (@tonidero) * build(deps): bump fastlane from 2.233.1 to 2.234.0 (#3463) via dependabot[bot] (@dependabot[bot]) * Update codelabs links (#3460) via Jaewoong Eum (@skydoves) * Add RemoteConfigManager and TopicFetcher (#3437) via Toni Rico (@tonidero) * Add exit offers support to workflows (#3452) via Cesar de la Vega (@vegaro) * Update baseline profiles (#3461) via RevenueCat Git Bot (@RCGitBot) * Add network scaffolding for remote config endpoint (#3435) via Toni Rico (@tonidero) * test: cover singleStepFallbackId == initialStepId edge case (#3445) via Facundo Menzella (@facumenzella) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: this is a release/versioning update (SNAPSHOT -> final) plus docs deployment path changes, with no functional code changes beyond version constants. > > **Overview** > Finalizes the `10.6.0` release by switching all version references from `10.6.0-SNAPSHOT` to `10.6.0` (root `.version`, `gradle.properties`, `Config.frameworkVersion`, and sample/test app `libs.versions.toml` files). > > Updates documentation publishing to point at the `10.6.0` docs path (CircleCI S3 sync target and `docs/index.html` redirect), and prepends the `10.6.0` section to `CHANGELOG.md`/`CHANGELOG.latest.md`. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 4da1697. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->



Motivation
First step in landing remote-config support. The backend exposes
GET /v2/config, which returns a manifest describing remote-controlled topics (e.g. product-entitlement mapping) plus the asset sources for downloading them. Wiring up the endpoint and its response models is a prerequisite for the orchestration and on-disk cache work in the rest of the stack.Description
Endpoint.GetRemoteConfigto the sealed endpoint hierarchy andBackend.getRemoteConfig(appUserID, appInBackground, onSuccess, onError)to dispatch the call. Reuses the existingBackgroundAwareCallbackCacheKey/RemoteConfigCallbackcallback-coalescing pattern.com.revenuecat.purchases.common.remoteconfig:RemoteConfigResponse(apiSources, blobSources, manifest)ApiSource(id, url, priority, weight)BlobSource(id, urlFormat, priority, weight)Manifest(topics)Topicenum (PRODUCT_ENTITLEMENT_MAPPING) withfromKey(key)for wire-key lookupsTopicEntry(blobRef)TopicsMapSerializer— custom kotlinx-serializationKSerializerthat drops unknown topic wire keys at decode time and re-emits the enum's wire key on encode.BackendGetRemoteConfigTestfor the endpoint plumbing and error paths.REMOTE_CONFIG_BASE_URLinlocal.properties(e.g.http://localhost:8080/) redirects onlygetRemoteConfigto that host. Empty by default and gated to debug builds, so CI / release traffic is unaffected. Plumbed via a newBuildConfig.REMOTE_CONFIG_BASE_URLfield, mirroring the existingENABLE_EXTRA_REQUEST_LOGGINGpattern.All new types are
internal; no public API surface change.Stack
Add RemoteConfigManager and TopicFetcher: Add RemoteConfigManager and TopicFetcher #3437Clean up unreferenced topic files after successful remote-config refresh: Clean up unreferenced topic files after successful remote-config refresh #3439Checklist
purchases-ios/ hybrids (deferred — feature is not yet wired to any consumer)Note
Medium Risk
Adds new backend networking path (
GET /v2/config) and callback coalescing logic inBackend, plus a debug-only base URL override; issues here could affect request routing or parsing for this new call.Overview
Adds initial remote-config networking support by introducing
Endpoint.GetRemoteConfig(/v2/config) and a newBackend.getRemoteConfigcall that coalesces concurrent requests and parses a typedRemoteConfigResponse.Introduces internal remote-config response models (including a custom serializer that drops unknown topic keys) and adds unit tests covering endpoint properties, parsing/unknown-field behavior, error propagation, and request de-duping.
Adds a debug-only
REMOTE_CONFIG_BASE_URLBuildConfigfield (documented inlocal.properties.example) to optionally route only the remote-config GET request to a local host.Reviewed by Cursor Bugbot for commit a7f33ec. Bugbot is set up for automated code reviews on this repo. Configure here.