11package com.posthog.server
22
33import com.posthog.PostHogStateless
4- import com.posthog.PostHogStatelessInterface
4+ import com.posthog.server.internal.EvaluationSource
5+ import com.posthog.server.internal.FeatureFlagResultContext
56import com.posthog.server.internal.PostHogFeatureFlags
67
7- public class PostHog : PostHogInterface {
8- private var instance: PostHogStatelessInterface ? = null
9- private var config: PostHogConfig ? = null
10- private var featureFlags: PostHogFeatureFlags ? = null
8+ public class PostHog : PostHogInterface , PostHogStateless () {
9+ private var serverConfig: PostHogConfig ? = null
1110
1211 override fun <T : PostHogConfig > setup (config : T ) {
13- this .config = config
14- instance = PostHogStateless .with (config.asCoreConfig())
15- featureFlags = config.featureFlags
12+ super .setup(config.asCoreConfig())
13+ this .serverConfig = config
1614 }
1715
1816 override fun close () {
19- instance? .close()
17+ super .close()
2018 }
2119
2220 override fun identify (
2321 distinctId : String ,
2422 userProperties : Map <String , Any >? ,
2523 userPropertiesSetOnce : Map <String , Any >? ,
2624 ) {
27- instance? .identify(
25+ super < PostHogStateless > .identify(
2826 distinctId,
2927 userProperties,
3028 userPropertiesSetOnce,
3129 )
3230 }
3331
3432 override fun flush () {
35- instance? .flush()
33+ super .flush()
3634 }
3735
3836 override fun debug (enable : Boolean ) {
39- instance? .debug(enable)
37+ super .debug(enable)
4038 }
4139
4240 override fun capture (
@@ -49,16 +47,22 @@ public class PostHog : PostHogInterface {
4947 timestamp : java.util.Date ? ,
5048 sendFeatureFlags : PostHogSendFeatureFlagOptions ? ,
5149 ) {
52- val updatedProperties = if (sendFeatureFlags == null && properties == null ) {
53- null
54- } else {
55- mutableMapOf<String , Any >().apply {
56- properties?.let { putAll(it) }
57- }.also { updatedProperties ->
58- featureFlags?.appendFlagEventProperties(distinctId, updatedProperties, groups, sendFeatureFlags)
50+ val updatedProperties =
51+ if (sendFeatureFlags == null ) {
52+ properties
53+ } else {
54+ mutableMapOf<String , Any >().apply {
55+ properties?.let { putAll(it) }
56+ }.also { props ->
57+ appendFlagCaptureProperties(
58+ distinctId,
59+ props,
60+ groups,
61+ sendFeatureFlags,
62+ )
63+ }
5964 }
60- }
61- instance?.captureStateless(
65+ super .captureStateless(
6266 event,
6367 distinctId,
6468 updatedProperties,
@@ -77,14 +81,24 @@ public class PostHog : PostHogInterface {
7781 personProperties : Map <String , String >? ,
7882 groupProperties : Map <String , String >? ,
7983 ): Boolean {
80- return instance?.isFeatureEnabledStateless(
81- distinctId,
82- key,
83- defaultValue,
84- groups,
85- personProperties,
86- groupProperties,
87- ) ? : false
84+ (featureFlags as ? PostHogFeatureFlags )?.let { featureFlags ->
85+ val result =
86+ featureFlags.resolveFeatureFlag(
87+ key,
88+ distinctId,
89+ groups,
90+ personProperties,
91+ groupProperties,
92+ )
93+ sendFeatureFlagCalled(
94+ distinctId,
95+ key,
96+ result,
97+ )
98+ val flag = result?.results?.get(key)
99+ return flag?.enabled ? : defaultValue
100+ }
101+ return defaultValue
88102 }
89103
90104 override fun getFeatureFlag (
@@ -95,14 +109,24 @@ public class PostHog : PostHogInterface {
95109 personProperties : Map <String , String >? ,
96110 groupProperties : Map <String , String >? ,
97111 ): Any? {
98- return instance?.getFeatureFlagStateless(
99- distinctId,
100- key,
101- defaultValue,
102- groups,
103- personProperties,
104- groupProperties,
105- )
112+ (featureFlags as ? PostHogFeatureFlags )?.let { featureFlags ->
113+ val result =
114+ featureFlags.resolveFeatureFlag(
115+ key,
116+ distinctId,
117+ groups,
118+ personProperties,
119+ groupProperties,
120+ )
121+ sendFeatureFlagCalled(
122+ distinctId,
123+ key,
124+ result,
125+ )
126+ val flag = result?.results?.get(key)
127+ return flag?.variant ? : flag?.enabled ? : defaultValue
128+ }
129+ return defaultValue
106130 }
107131
108132 override fun getFeatureFlagPayload (
@@ -113,14 +137,24 @@ public class PostHog : PostHogInterface {
113137 personProperties : Map <String , String >? ,
114138 groupProperties : Map <String , String >? ,
115139 ): Any? {
116- return instance?.getFeatureFlagPayloadStateless(
117- distinctId,
118- key,
119- defaultValue,
120- groups,
121- personProperties,
122- groupProperties,
123- )
140+ (featureFlags as ? PostHogFeatureFlags )?.let { featureFlags ->
141+ val result =
142+ featureFlags.resolveFeatureFlag(
143+ key,
144+ distinctId,
145+ groups,
146+ personProperties,
147+ groupProperties,
148+ )
149+ sendFeatureFlagCalled(
150+ distinctId,
151+ key,
152+ result,
153+ )
154+ val flag = result?.results?.get(key)
155+ return flag?.metadata?.payload ? : defaultValue
156+ }
157+ return defaultValue
124158 }
125159
126160 override fun group (
@@ -129,7 +163,7 @@ public class PostHog : PostHogInterface {
129163 key : String ,
130164 groupProperties : Map <String , Any >? ,
131165 ) {
132- instance? .groupStateless(
166+ super .groupStateless(
133167 distinctId,
134168 type,
135169 key,
@@ -141,12 +175,112 @@ public class PostHog : PostHogInterface {
141175 distinctId : String ,
142176 alias : String ,
143177 ) {
144- instance? .aliasStateless(
178+ super .aliasStateless(
145179 distinctId,
146180 alias,
147181 )
148182 }
149183
184+ private fun sendFeatureFlagCalled (
185+ distinctId : String ,
186+ key : String ,
187+ resultContext : FeatureFlagResultContext ? ,
188+ ) {
189+ if (serverConfig?.sendFeatureFlagEvent == false || distinctId.isEmpty() || key.isEmpty() || resultContext == null ) {
190+ return
191+ }
192+
193+ if (config?.sendFeatureFlagEvent == true ) {
194+ val requestedFlag = resultContext.results?.get(key)
195+ val requestedFlagValue = requestedFlag?.variant ? : requestedFlag?.enabled
196+ val isNewlySeen = featureFlagsCalled?.add(distinctId, key, requestedFlagValue) ? : false
197+ if (isNewlySeen) {
198+ val props = mutableMapOf<String , Any >()
199+ props[" \$ feature_flag" ] = key
200+ props[" \$ feature_flag_response" ] = requestedFlagValue ? : " "
201+ resultContext.requestId?.let {
202+ props[" \$ feature_flag_request_id" ] = it
203+ }
204+ requestedFlag?.metadata?.let {
205+ props[" \$ feature_flag_id" ] = it.id
206+ props[" \$ feature_flag_version" ] = it.version
207+ }
208+ props[" \$ feature_flag_reason" ] = requestedFlag?.reason?.description ? : " "
209+ resultContext.source?.let {
210+ props[" \$ feature_flag_source" ] = it.toString()
211+ if (it == EvaluationSource .LOCAL ) {
212+ props[" locally_evaluated" ] = true
213+ }
214+ }
215+
216+ var allFlags = resultContext.results
217+ if (! resultContext.exhaustive) {
218+ // we only have partial results so we'll need to resolve the rest
219+ resultContext.parameters?.let { params ->
220+ // this will be cached or evaluated locally
221+ val response =
222+ (featureFlags as ? PostHogFeatureFlags )?.resolveFeatureFlags(
223+ distinctId,
224+ params.groups,
225+ params.personProperties,
226+ params.groupProperties,
227+ params.onlyEvaluateLocally,
228+ )
229+ if (response != null ) {
230+ allFlags = response.results
231+ }
232+ }
233+ }
234+
235+ allFlags?.let { flags ->
236+ val activeFeatureFlags = mutableListOf<String >()
237+ flags.values.forEach { flag ->
238+ val flagValue = flag.variant ? : flag.enabled
239+ props[" \$ feature/${flag.key} " ] = flagValue
240+ if (flagValue != false ) {
241+ activeFeatureFlags.add(flag.key)
242+ }
243+ }
244+ props[" \$ active_feature_flags" ] = activeFeatureFlags.toList()
245+ }
246+
247+ captureStateless(" \$ feature_flag_called" , distinctId, properties = props)
248+ }
249+ }
250+ }
251+
252+ private fun appendFlagCaptureProperties (
253+ distinctId : String ,
254+ properties : MutableMap <String , Any >? ,
255+ groups : Map <String , String >? ,
256+ options : PostHogSendFeatureFlagOptions ? ,
257+ ) {
258+ if (options == null || properties == null ) {
259+ return
260+ }
261+
262+ val response =
263+ (featureFlags as ? PostHogFeatureFlags )?.resolveFeatureFlags(
264+ distinctId,
265+ groups,
266+ options.personProperties,
267+ options.groupProperties,
268+ options.onlyEvaluateLocally,
269+ )
270+
271+ response?.results?.values?.let {
272+ val activeFeatureFlags = mutableListOf<String >()
273+ it.forEach { flag ->
274+ val flagValue = flag.variant ? : flag.enabled
275+ properties[" \$ feature/${flag.key} " ] = flagValue
276+ if (flagValue != false ) {
277+ activeFeatureFlags.add(flag.key)
278+ }
279+ }
280+ properties[" \$ active_feature_flags" ] = activeFeatureFlags.toList()
281+ }
282+ }
283+
150284 public companion object {
151285 /* *
152286 * Set up the SDK and returns an instance that you can hold and pass it around
0 commit comments