diff --git a/CHANGELOG.md b/CHANGELOG.md index 47bbfcb4..92de99f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Next +## 3.11.3 - 2025-02-25 + +- feat: support quota limiting for feature flags ([#228](https://github.com/PostHog/posthog-android/pull/228)) + ## 3.11.2 - 2025-02-04 - fix: touches fall back to single touches if out of bounds ([#221](https://github.com/PostHog/posthog-android/pull/221)) diff --git a/posthog-android/lint-baseline.xml b/posthog-android/lint-baseline.xml index 3b6ffb53..1abed766 100644 --- a/posthog-android/lint-baseline.xml +++ b/posthog-android/lint-baseline.xml @@ -36,7 +36,7 @@ - - - - diff --git a/posthog/src/main/java/com/posthog/internal/PostHogDecideResponse.kt b/posthog/src/main/java/com/posthog/internal/PostHogDecideResponse.kt index c664981b..83a770e2 100644 --- a/posthog/src/main/java/com/posthog/internal/PostHogDecideResponse.kt +++ b/posthog/src/main/java/com/posthog/internal/PostHogDecideResponse.kt @@ -7,6 +7,7 @@ import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement * @property errorsWhileComputingFlags if there were errors computing feature flags * @property featureFlags the feature flags * @property featureFlagPayloads the feature flag payloads + * @property quotaLimited array of quota limited features */ @IgnoreJRERequirement internal data class PostHogDecideResponse( @@ -16,4 +17,5 @@ internal data class PostHogDecideResponse( val featureFlagPayloads: Map?, // its either a boolean or a map, see https://github.com/PostHog/posthog-js/blob/10fd7f4fa083f997d31a4a4c7be7d311d0a95e74/src/types.ts#L235-L243 val sessionRecording: Any? = false, + val quotaLimited: List? = null, ) diff --git a/posthog/src/main/java/com/posthog/internal/PostHogFeatureFlags.kt b/posthog/src/main/java/com/posthog/internal/PostHogFeatureFlags.kt index b7022fdf..20dfe83a 100644 --- a/posthog/src/main/java/com/posthog/internal/PostHogFeatureFlags.kt +++ b/posthog/src/main/java/com/posthog/internal/PostHogFeatureFlags.kt @@ -89,6 +89,17 @@ internal class PostHogFeatureFlags( response?.let { synchronized(featureFlagsLock) { + if (response.quotaLimited?.contains("feature_flags") == true) { + config.logger.log("Feature flags are quota limited, clearing existing flags") + this.featureFlags = null + this.featureFlagPayloads = null + config.cachePreferences?.let { preferences -> + preferences.remove(FEATURE_FLAGS) + preferences.remove(FEATURE_FLAGS_PAYLOAD) + } + return@let + } + if (response.errorsWhileComputingFlags) { // if not all flags were computed, we upsert flags instead of replacing them this.featureFlags = diff --git a/posthog/src/test/java/com/posthog/internal/PostHogFeatureFlagsTest.kt b/posthog/src/test/java/com/posthog/internal/PostHogFeatureFlagsTest.kt index 12e5341f..7f33791f 100644 --- a/posthog/src/test/java/com/posthog/internal/PostHogFeatureFlagsTest.kt +++ b/posthog/src/test/java/com/posthog/internal/PostHogFeatureFlagsTest.kt @@ -434,4 +434,49 @@ internal class PostHogFeatureFlagsTest { sut.clear() } + + @Test + fun `clear flags when quota limited`() { + val http = + mockHttp( + response = + MockResponse().setBody( + """ + { + "featureFlags": {"flag1": true}, + "featureFlagPayloads": {"flag1": "payload1"} + } + """.trimIndent(), + ), + ) + val url = http.url("/") + val sut = getSut(host = url.toString()) + + // Load initial flags + sut.loadFeatureFlags("test_id", null, null, null) + executor.awaitExecution() + + // Verify flags are loaded + assertNotNull(sut.getFeatureFlags()) + assertNotNull(preferences.getValue(FEATURE_FLAGS)) + + // Send quota limited response + http.enqueue( + MockResponse().setBody( + """ + { + "quotaLimited": ["feature_flags"] + } + """.trimIndent(), + ), + ) + + // Reload flags + sut.loadFeatureFlags("test_id", null, null, null) + executor.awaitExecution() + + // Verify flags are cleared + assertNull(sut.getFeatureFlags()) + assertNull(preferences.getValue(FEATURE_FLAGS)) + } }