Skip to content

Commit 128e2ba

Browse files
authored
feat(flags): Add method for fetching decrypted remote config flag payload (#84)
* feat(flags): Add method for fetching decrypted remote config flag payload * fix tests * fix broken test * Address comment around user agent header * tweak * bump minor version * Use flag key instead of id * Add json examples * address comment * Clean up method name
1 parent b26aef8 commit 128e2ba

8 files changed

+103
-10
lines changed

config.go

+2
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ type Config struct {
9292
maxConcurrentRequests int
9393
}
9494

95+
const SdkName = "posthog-go"
96+
9597
// This constant sets the default endpoint to which client instances send
9698
// messages if none was explictly set.
9799
const DefaultEndpoint = "https://app.posthog.com"

examples/featureflags.go

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"time"
67

@@ -12,7 +13,7 @@ func TestIsFeatureEnabled() {
1213
Interval: 30 * time.Second,
1314
BatchSize: 100,
1415
Verbose: true,
15-
PersonalApiKey: "phx_n79cT52OfsxAWDhZs9j3w67aRoBCZ7l5ksRRKmAi5nr",
16+
PersonalApiKey: "phx_DvugINPCOSM3Ko929TaeywnUlRC5FeF4X7KV60IgyXWGTLw",
1617
Endpoint: "http://localhost:8000",
1718
DefaultFeatureFlagsPollingInterval: 5 * time.Second,
1819
FeatureFlagRequestTimeout: 3 * time.Second,
@@ -61,4 +62,21 @@ func TestIsFeatureEnabled() {
6162
if variantErr != nil || variantResult == true {
6263
fmt.Println("error:", variantErr)
6364
}
65+
66+
// Encrypted remote config flag (string payload)
67+
stringPayloadResult, stringPayloadErr := client.GetRemoteConfigPayload("my_secret_flag_value")
68+
fmt.Println("stringPayloadResult:", stringPayloadResult)
69+
if stringPayloadErr != nil {
70+
fmt.Println("error:", stringPayloadErr)
71+
}
72+
73+
// Encrypted remote config flag (json object payload)
74+
jsonObjectPayloadResult, _ := client.GetRemoteConfigPayload("my_secret_flag_json_object_value")
75+
var jsonPayloadMap map[string]interface{}
76+
json.Unmarshal([]byte(jsonObjectPayloadResult), &jsonPayloadMap)
77+
78+
// Encrypted remote config flag (json array payload)
79+
jsonArrayPayloadResult, _ := client.GetRemoteConfigPayload("my_secret_flag_json_array_value")
80+
var jsonArrayPayload []string
81+
json.Unmarshal([]byte(jsonArrayPayloadResult), &jsonArrayPayload)
6482
}

feature_flags_test.go

+27
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,33 @@ func TestGetFeatureFlagPayload(t *testing.T) {
945945
}
946946
}
947947

948+
func TestGetRemoteConfigPayload(t *testing.T) {
949+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
950+
w.Write([]byte(fixture("test-remote-config.json")))
951+
}))
952+
953+
defer server.Close()
954+
955+
client, _ := NewWithConfig("Csyjlnlun3OzyNJAafdlv", Config{
956+
PersonalApiKey: "some very secret key",
957+
Endpoint: server.URL,
958+
})
959+
defer client.Close()
960+
961+
payload, _ := client.GetRemoteConfigPayload("flag_key")
962+
963+
var payloadMap map[string]interface{}
964+
err := json.Unmarshal([]byte(payload), &payloadMap)
965+
if err != nil {
966+
t.Error("Failed to decode payload")
967+
}
968+
969+
if payloadMap["foo"] != "bar" || payloadMap["baz"] != float64(42) {
970+
t.Error("Should match")
971+
}
972+
}
973+
974+
948975
func TestFlagWithVariantOverrides(t *testing.T) {
949976

950977
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

featureflags.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -900,7 +900,7 @@ func (poller *FeatureFlagsPoller) request(method string, url *url.URL, requestDa
900900

901901
version := getVersion()
902902

903-
req.Header.Add("User-Agent", "posthog-go (version: "+version+")")
903+
req.Header.Add("User-Agent", SdkName+"/"+version)
904904
req.Header.Add("Content-Type", "application/json")
905905
req.Header.Add("Content-Length", fmt.Sprintf("%d", len(requestData)))
906906

fixtures/test-remote-config.json

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"{\"foo\": \"bar\",\"baz\": 42}"

posthog.go

+47-2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ type Client interface {
4848
// Method returns feature flag payload value matching key for user (supports multivariate flags).
4949
GetFeatureFlagPayload(FeatureFlagPayload) (string, error)
5050
//
51+
// Method returns decrypted feature flag payload value for remote config flags.
52+
GetRemoteConfigPayload(string) (string, error)
53+
//
5154
// Get all flags - returns all flags for a user
5255
GetAllFlags(FeatureFlagPayloadNoKey) (map[string]interface{}, error)
5356
//
@@ -358,6 +361,10 @@ func (c *client) GetFeatureFlag(flagConfig FeatureFlagPayload) (interface{}, err
358361
return flagValue, err
359362
}
360363

364+
func (c *client) GetRemoteConfigPayload(flagKey string) (string, error) {
365+
return c.makeRemoteConfigRequest(flagKey)
366+
}
367+
361368
func (c *client) GetFeatureFlags() ([]FeatureFlag, error) {
362369
if c.featureFlagsPoller == nil {
363370
errorMessage := "specifying a PersonalApiKey is required for using feature flags"
@@ -472,7 +479,7 @@ func (c *client) upload(b []byte) error {
472479

473480
version := getVersion()
474481

475-
req.Header.Add("User-Agent", "posthog-go (version: "+version+")")
482+
req.Header.Add("User-Agent", SdkName+"/"+version)
476483
req.Header.Add("Content-Type", "application/json")
477484
req.Header.Add("Content-Length", fmt.Sprintf("%d", len(b)))
478485

@@ -653,7 +660,7 @@ func (c *client) makeDecideRequest(distinctId string, groups Groups, personPrope
653660
}
654661

655662
req.Header.Set("Content-Type", "application/json")
656-
req.Header.Set("User-Agent", "posthog-go (version: "+Version+")")
663+
req.Header.Set("User-Agent", "posthog-go/"+Version)
657664

658665
res, err := c.http.Do(req)
659666
if err != nil {
@@ -679,6 +686,44 @@ func (c *client) makeDecideRequest(distinctId string, groups Groups, personPrope
679686
return &decideResponse, nil
680687
}
681688

689+
func (c *client) makeRemoteConfigRequest(flagKey string) (string, error) {
690+
remoteConfigEndpoint := fmt.Sprintf("api/projects/@current/feature_flags/%s/remote_config/", flagKey)
691+
url, err := url.Parse(c.Endpoint + "/" + remoteConfigEndpoint)
692+
if err != nil {
693+
return "", fmt.Errorf("creating url: %v", err)
694+
}
695+
696+
req, err := http.NewRequest("GET", url.String(), nil)
697+
if err != nil {
698+
return "", fmt.Errorf("creating request: %v", err)
699+
}
700+
701+
req.Header.Set("Authorization", "Bearer "+c.PersonalApiKey)
702+
req.Header.Set("Content-Type", "application/json")
703+
req.Header.Set("User-Agent", "posthog-go/"+Version)
704+
705+
res, err := c.http.Do(req)
706+
if err != nil {
707+
return "", fmt.Errorf("sending request: %v", err)
708+
}
709+
defer res.Body.Close()
710+
711+
if res.StatusCode != http.StatusOK {
712+
return "", fmt.Errorf("unexpected status code from %s: %d", remoteConfigEndpoint, res.StatusCode)
713+
}
714+
715+
resBody, err := io.ReadAll(res.Body)
716+
if err != nil {
717+
return "", fmt.Errorf("error reading response from /remote_config/: %v", err)
718+
}
719+
720+
var responseData string
721+
if err := json.Unmarshal(resBody, &responseData); err != nil {
722+
return "", fmt.Errorf("error parsing JSON response from /remote_config/: %v", err)
723+
}
724+
return responseData, nil
725+
}
726+
682727
func (c *client) getFeatureFlagFromDecide(key string, distinctId string, groups Groups, personProperties Properties, groupProperties map[string]Properties) (interface{}, error) {
683728
decideResponse, err := c.makeDecideRequest(distinctId, groups, personProperties, groupProperties)
684729
if err != nil {

posthog_test.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,6 @@ func ExampleHistoricalMigrationCapture() {
264264
Endpoint: server.URL,
265265
BatchSize: 1,
266266
now: mockTime,
267-
uid: mockId,
268267
HistoricalMigration: true,
269268
})
270269
defer client.Close()
@@ -299,7 +298,8 @@ func ExampleHistoricalMigrationCapture() {
299298
// },
300299
// "send_feature_flags": false,
301300
// "timestamp": "2009-11-10T23:00:00Z",
302-
// "type": "capture"
301+
// "type": "capture",
302+
// "uuid": ""
303303
// }
304304
// ],
305305
// "historical_migration": true
@@ -1100,7 +1100,7 @@ func TestGetFeatureFlagPayloadWithNoPersonalApiKey(t *testing.T) {
11001100
if r.Header.Get("Content-Type") != "application/json" {
11011101
t.Errorf("Expected Content-Type: application/json, got %s", r.Header.Get("Content-Type"))
11021102
}
1103-
if !strings.HasPrefix(r.Header.Get("User-Agent"), "posthog-go (version: ") {
1103+
if !strings.HasPrefix(r.Header.Get("User-Agent"), "posthog-go/") {
11041104
t.Errorf("Unexpected User-Agent: %s", r.Header.Get("User-Agent"))
11051105
}
11061106

@@ -1300,7 +1300,7 @@ func TestGetFeatureFlagWithNoPersonalApiKey(t *testing.T) {
13001300
if r.Header.Get("Content-Type") != "application/json" {
13011301
t.Errorf("Expected Content-Type: application/json, got %s", r.Header.Get("Content-Type"))
13021302
}
1303-
if !strings.HasPrefix(r.Header.Get("User-Agent"), "posthog-go (version: ") {
1303+
if !strings.HasPrefix(r.Header.Get("User-Agent"), "posthog-go/") {
13041304
t.Errorf("Unexpected User-Agent: %s", r.Header.Get("User-Agent"))
13051305
}
13061306

@@ -1437,7 +1437,7 @@ func TestGetAllFeatureFlagsWithNoPersonalApiKey(t *testing.T) {
14371437
if r.Header.Get("Content-Type") != "application/json" {
14381438
t.Errorf("Expected Content-Type: application/json, got %s", r.Header.Get("Content-Type"))
14391439
}
1440-
if !strings.HasPrefix(r.Header.Get("User-Agent"), "posthog-go (version: ") {
1440+
if !strings.HasPrefix(r.Header.Get("User-Agent"), "posthog-go/") {
14411441
t.Errorf("Unexpected User-Agent: %s", r.Header.Get("User-Agent"))
14421442
}
14431443

version.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package posthog
33
import "flag"
44

55
// Version of the client.
6-
const Version = "1.2.24"
6+
const Version = "1.3.0"
77

88
// make tests easier by using a constant version
99
func getVersion() string {

0 commit comments

Comments
 (0)