Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: bootstrap flags do not overwrite the current values #260

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 52 additions & 13 deletions posthog-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -723,26 +723,65 @@ export abstract class PostHogCore extends PostHogCoreStateless {
this._sessionExpirationTimeSeconds = options?.sessionExpirationTimeSeconds ?? 1800 // 30 minutes
}

protected setupBootstrap(options?: Partial<PostHogCoreOptions>): void {
if (options?.bootstrap?.distinctId) {
if (options?.bootstrap?.isIdentifiedId) {
this.setPersistedProperty(PostHogPersistedProperty.DistinctId, options.bootstrap.distinctId)
protected setupBootstrap(options?: Partial<PostHogCoreOptions>, overwriteCurrentValues: boolean = true): void {
const bootstrap = options?.bootstrap
if (!bootstrap) {
return
}

// bootstrap options are only set if no persisted values are found
// this is to prevent overwriting existing values
if (bootstrap.distinctId) {
if (bootstrap.isIdentifiedId) {
const distinctId = this.getPersistedProperty(PostHogPersistedProperty.DistinctId)

if (overwriteCurrentValues || !distinctId) {
this.setPersistedProperty(PostHogPersistedProperty.DistinctId, bootstrap.distinctId)
}
} else {
this.setPersistedProperty(PostHogPersistedProperty.AnonymousId, options.bootstrap.distinctId)
const anonymousId = this.getPersistedProperty(PostHogPersistedProperty.AnonymousId)

if (overwriteCurrentValues || !anonymousId) {
this.setPersistedProperty(PostHogPersistedProperty.AnonymousId, bootstrap.distinctId)
}
}
}

if (options?.bootstrap?.featureFlags) {
const activeFlags = Object.keys(options.bootstrap?.featureFlags || {})
.filter((flag) => !!options.bootstrap?.featureFlags?.[flag])
const bootstrapfeatureFlags = bootstrap.featureFlags
if (bootstrapfeatureFlags && Object.keys(bootstrapfeatureFlags).length) {
const bootstrapFlags = Object.keys(bootstrapfeatureFlags)
.filter((flag) => !!bootstrapfeatureFlags[flag])
.reduce(
(res: Record<string, string | boolean>, key) => (
(res[key] = options.bootstrap?.featureFlags?.[key] || false), res
),
(res: Record<string, string | boolean>, key) => ((res[key] = bootstrapfeatureFlags[key] || false), res),
{}
)
this.setKnownFeatureFlags(activeFlags)
options?.bootstrap.featureFlagPayloads && this.setKnownFeatureFlagPayloads(options?.bootstrap.featureFlagPayloads)

if (Object.keys(bootstrapFlags).length) {
const currentFlags =
this.getPersistedProperty<PostHogDecideResponse['featureFlags']>(PostHogPersistedProperty.FeatureFlags) || {}

if (overwriteCurrentValues) {
this.setKnownFeatureFlags(bootstrapFlags)
} else {
const newFeatureFlags = { ...bootstrapFlags, ...currentFlags }
this.setKnownFeatureFlags(newFeatureFlags)
}
}

const bootstrapFlagPayloads = bootstrap.featureFlagPayloads
if (bootstrapFlagPayloads && Object.keys(bootstrapFlagPayloads).length) {
const currentFlagPayloads =
this.getPersistedProperty<PostHogDecideResponse['featureFlagPayloads']>(
PostHogPersistedProperty.FeatureFlagPayloads
) || {}

if (overwriteCurrentValues) {
this.setKnownFeatureFlagPayloads(bootstrapFlagPayloads)
} else {
const newFeatureFlagPayloads = { ...bootstrapFlagPayloads, ...currentFlagPayloads }
this.setKnownFeatureFlagPayloads(newFeatureFlagPayloads)
}
}
}
}

Expand Down
64 changes: 64 additions & 0 deletions posthog-core/test/posthog.featureflags.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -586,4 +586,68 @@ describe('PostHog Core', () => {
})
})
})

describe('bootstapped do not overwrite values', () => {
beforeEach(() => {
;[posthog, mocks] = createTestClient(
'TEST_API_KEY',
{
flushAt: 1,
bootstrap: {
distinctId: 'tomato',
featureFlags: { 'bootstrap-1': 'variant-1', enabled: true, disabled: false },
featureFlagPayloads: {
'bootstrap-1': {
some: 'key',
},
enabled: 200,
},
},
},
(_mocks) => {
_mocks.fetch.mockImplementation((url) => {
if (url.includes('/decide/')) {
return Promise.resolve({
status: 200,
text: () => Promise.resolve('ok'),
json: () =>
Promise.resolve({
featureFlags: createMockFeatureFlags(),
featureFlagPayloads: createMockFeatureFlagPayloads(),
}),
})
}

return Promise.resolve({
status: 200,
text: () => Promise.resolve('ok'),
json: () =>
Promise.resolve({
status: 'ok',
}),
})
})
},
{
distinct_id: '123',
feature_flags: { 'bootstrap-1': 'variant-2' },
feature_flag_payloads: { 'bootstrap-1': { some: 'other-key' } },
}
)
})

it('distinct id should not be overwritten if already there', () => {
expect(posthog.getDistinctId()).toEqual('123')
})

it('flags should not be overwritten if already there', () => {
expect(posthog.getFeatureFlag('bootstrap-1')).toEqual('variant-2')
})

it('flag payloads should not be overwritten if already there', () => {
expect(posthog.getFeatureFlagPayload('bootstrap-1')).toEqual({
some: 'other-key',
})
})
})
})
6 changes: 3 additions & 3 deletions posthog-core/test/test-utils/PostHogCoreTestClient.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PostHogCore, PostHogCoreOptions, PostHogFetchOptions, PostHogFetchResponse } from '../../src'
import { JsonType, PostHogCore, PostHogCoreOptions, PostHogFetchOptions, PostHogFetchResponse } from '../../src'

const version = '2.0.0-alpha'

Expand Down Expand Up @@ -42,9 +42,9 @@ export class PostHogCoreTestClient extends PostHogCore {
export const createTestClient = (
apiKey: string,
options?: PostHogCoreOptions,
setupMocks?: (mocks: PostHogCoreTestClientMocks) => void
setupMocks?: (mocks: PostHogCoreTestClientMocks) => void,
storageCache: { [key: string]: string | JsonType } = {}
): [PostHogCoreTestClient, PostHogCoreTestClientMocks] => {
const storageCache: { [key: string]: string | undefined } = {}
const mocks = {
fetch: jest.fn<Promise<PostHogFetchResponse>, [string, PostHogFetchOptions]>(),
storage: {
Expand Down
3 changes: 3 additions & 0 deletions posthog-react-native/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# Next

# 3.2.0 - 2024-09-12

## Changed

1. chore: default `captureMode` changed to `json`.
1. To keep using the `form` mode, just set the `captureMode` option to `form` when initializing the PostHog client.
2. fix: fix: bootstrap flags do not overwrite the current values

# 3.1.2 - 2024-08-14

Expand Down
2 changes: 1 addition & 1 deletion posthog-react-native/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "posthog-react-native",
"version": "3.1.2",
"version": "3.2.0",
"main": "lib/posthog-react-native/index.js",
"files": [
"lib/"
Expand Down
5 changes: 4 additions & 1 deletion posthog-react-native/src/posthog-rn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ export class PostHog extends PostHogCore {
}

const initAfterStorage = (): void => {
this.setupBootstrap(options)
// RN should merge the bootstrap options with the existing ones
// The existing ones should take precedence since the flags are already loaded
// and survive an app reload
this.setupBootstrap(options, false)

this._isInitialized = true
if (options?.preloadFeatureFlags !== false) {
Expand Down
Loading