From 926d82e4707adb6f55c89e45acff6c5b2bcd1af5 Mon Sep 17 00:00:00 2001 From: Anders Asheim Hennum Date: Fri, 14 Nov 2025 15:07:49 +0100 Subject: [PATCH] send device_id on feature flag requests --- .../src/__tests__/featureflags.test.ts | 130 ++++++++++++++++++ packages/browser/src/posthog-featureflags.ts | 7 + 2 files changed, 137 insertions(+) diff --git a/packages/browser/src/__tests__/featureflags.test.ts b/packages/browser/src/__tests__/featureflags.test.ts index 0a941283da..fd319ed030 100644 --- a/packages/browser/src/__tests__/featureflags.test.ts +++ b/packages/browser/src/__tests__/featureflags.test.ts @@ -1168,6 +1168,136 @@ describe('featureflags', () => { }) }) + describe('device_id in flags requests', () => { + beforeEach(() => { + // Clear persistence before each test in this suite + instance.persistence.unregister('$device_id') + instance.persistence.unregister('$stored_person_properties') + instance.persistence.unregister('$stored_group_properties') + + instance._send_request = jest.fn().mockImplementation(({ callback }) => + callback({ + statusCode: 200, + json: { + featureFlags: { + first: 'variant-1', + second: true, + }, + }, + }) + ) + }) + + afterEach(() => { + // Clean up after each test + instance.persistence.unregister('$device_id') + instance.persistence.unregister('$stored_person_properties') + instance.persistence.unregister('$stored_group_properties') + }) + + it('should include device_id in flags request when available', () => { + instance.persistence.register({ + $device_id: 'test-device-uuid-123', + }) + + featureFlags.reloadFeatureFlags() + jest.runAllTimers() + + expect(instance._send_request).toHaveBeenCalledTimes(1) + expect(instance._send_request.mock.calls[0][0].data).toEqual({ + token: 'random fake token', + distinct_id: 'blah id', + $device_id: 'test-device-uuid-123', + person_properties: {}, + }) + }) + + it('should omit device_id when it is null (cookieless mode)', () => { + instance.persistence.register({ + $device_id: null, + }) + + featureFlags.reloadFeatureFlags() + jest.runAllTimers() + + expect(instance._send_request).toHaveBeenCalledTimes(1) + expect(instance._send_request.mock.calls[0][0].data).toEqual({ + token: 'random fake token', + distinct_id: 'blah id', + person_properties: {}, + }) + expect(instance._send_request.mock.calls[0][0].data).not.toHaveProperty('$device_id') + }) + + it('should omit device_id when it is undefined', () => { + // Don't register device_id at all + featureFlags.reloadFeatureFlags() + jest.runAllTimers() + + expect(instance._send_request).toHaveBeenCalledTimes(1) + expect(instance._send_request.mock.calls[0][0].data).toEqual({ + token: 'random fake token', + distinct_id: 'blah id', + person_properties: {}, + }) + expect(instance._send_request.mock.calls[0][0].data).not.toHaveProperty('$device_id') + }) + + it('should include device_id along with $anon_distinct_id on identify', () => { + instance.persistence.register({ + $device_id: 'device-uuid-456', + }) + + featureFlags.setAnonymousDistinctId('anon_id_789') + featureFlags.reloadFeatureFlags() + jest.runAllTimers() + + expect(instance._send_request).toHaveBeenCalledTimes(1) + expect(instance._send_request.mock.calls[0][0].data).toEqual({ + token: 'random fake token', + distinct_id: 'blah id', + $device_id: 'device-uuid-456', + $anon_distinct_id: 'anon_id_789', + person_properties: {}, + }) + }) + + it('should include device_id with person_properties', () => { + instance.persistence.register({ + $device_id: 'device-uuid-999', + }) + + featureFlags.setPersonPropertiesForFlags({ plan: 'pro', beta_tester: true }) + jest.runAllTimers() + + expect(instance._send_request).toHaveBeenCalledTimes(1) + expect(instance._send_request.mock.calls[0][0].data).toEqual({ + token: 'random fake token', + distinct_id: 'blah id', + $device_id: 'device-uuid-999', + person_properties: { plan: 'pro', beta_tester: true }, + }) + }) + + it('should include device_id with group_properties', () => { + instance.persistence.register({ + $device_id: 'device-uuid-888', + }) + + featureFlags.setGroupPropertiesForFlags({ company: { name: 'Acme', seats: 50 } }) + jest.runAllTimers() + + expect(instance._send_request).toHaveBeenCalledTimes(1) + expect(instance._send_request.mock.calls[0][0].data).toEqual({ + token: 'random fake token', + distinct_id: 'blah id', + $device_id: 'device-uuid-888', + person_properties: {}, + group_properties: { company: { name: 'Acme', seats: 50 } }, + }) + }) + }) + describe('reloadFeatureFlags', () => { beforeEach(() => { instance._send_request = jest.fn().mockImplementation(({ callback }) => diff --git a/packages/browser/src/posthog-featureflags.ts b/packages/browser/src/posthog-featureflags.ts index 3853b91750..f441458b06 100644 --- a/packages/browser/src/posthog-featureflags.ts +++ b/packages/browser/src/posthog-featureflags.ts @@ -394,6 +394,8 @@ export class PostHogFeatureFlags { return } const token = this._instance.config.token + const deviceId = this._instance.get_property('$device_id') + const data: Record = { token: token, distinct_id: this._instance.get_distinct_id(), @@ -406,6 +408,11 @@ export class PostHogFeatureFlags { group_properties: this._instance.get_property(STORED_GROUP_PROPERTIES_KEY), } + // Add device_id if available (handle cookieless mode where it's null) + if (deviceId !== null && deviceId !== undefined) { + data.$device_id = deviceId + } + if (options?.disableFlags || this._instance.config.advanced_disable_feature_flags) { data.disable_flags = true }