diff --git a/src/__tests__/compression.js b/src/__tests__/compression.js index 53a5b6688..333070435 100644 --- a/src/__tests__/compression.js +++ b/src/__tests__/compression.js @@ -1,8 +1,4 @@ -import sinon from 'sinon' -import { autocapture } from '../autocapture' import { decideCompression, compressData } from '../compression' -import { Decide } from '../decide' -import { AUTOCAPTURE_DISABLED_SERVER_SIDE } from '../constants' describe('decideCompression()', () => { given('subject', () => decideCompression(given.compressionSupport)) @@ -46,68 +42,3 @@ describe('compressData()', () => { expect(given.subject).toMatchSnapshot() }) }) - -describe('Payload Compression', () => { - afterEach(() => { - document.getElementsByTagName('html')[0].innerHTML = '' - }) - - describe('compression', () => { - let lib, sandbox - - beforeEach(() => { - document.title = 'test page' - sandbox = sinon.createSandbox() - autocapture._initializedTokens = [] - lib = { - debug: true, - _prepare_callback: sandbox.spy((callback) => callback), - _send_request: sandbox.spy((url, params, options, callback) => { - if (url === 'https://test.com/decide/?v=3') { - callback({ config: { enable_collect_everything: true }, supportedCompression: ['gzip-js'] }) - } else { - throw new Error('Should not get here') - } - }), - config: { - api_host: 'https://test.com', - token: 'testtoken', - }, - token: 'testtoken', - get_distinct_id() { - return 'distinctid' - }, - getGroups: () => ({}), - - toolbar: { - maybeLoadToolbar: jest.fn(), - afterDecideResponse: jest.fn(), - }, - sessionRecording: { - afterDecideResponse: jest.fn(), - }, - featureFlags: { - receivedFeatureFlags: jest.fn(), - setReloadingPaused: jest.fn(), - _startReloadTimer: jest.fn(), - }, - _hasBootstrappedFeatureFlags: jest.fn(), - get_property: (property_key) => - property_key === AUTOCAPTURE_DISABLED_SERVER_SIDE - ? given.$autocapture_disabled_server_side - : undefined, - } - }) - given('$autocapture_disabled_server_side', () => false) - - afterEach(() => { - sandbox.restore() - }) - - it('should save supported compression in instance', () => { - new Decide(lib).call() - autocapture.init(lib) - expect(lib.compression).toEqual({ 'gzip-js': true }) - }) - }) -}) diff --git a/src/__tests__/decide.js b/src/__tests__/decide.js index 234f9601e..30b55254b 100644 --- a/src/__tests__/decide.js +++ b/src/__tests__/decide.js @@ -31,6 +31,7 @@ describe('Decide', () => { get_property: (key) => given.posthog.persistence.props[key], capture: jest.fn(), _addCaptureHook: jest.fn(), + _afterDecideResponse: jest.fn(), _prepare_callback: jest.fn().mockImplementation((callback) => callback), get_distinct_id: jest.fn().mockImplementation(() => 'distinctid'), _send_request: jest @@ -154,33 +155,10 @@ describe('Decide', () => { expect(given.posthog.sessionRecording.afterDecideResponse).toHaveBeenCalledWith(given.decideResponse) expect(given.posthog.toolbar.afterDecideResponse).toHaveBeenCalledWith(given.decideResponse) expect(given.posthog.featureFlags.receivedFeatureFlags).toHaveBeenCalledWith(given.decideResponse) + expect(given.posthog._afterDecideResponse).toHaveBeenCalledWith(given.decideResponse) expect(autocapture.afterDecideResponse).toHaveBeenCalledWith(given.decideResponse, given.posthog) }) - it('enables compression from decide response', () => { - given('decideResponse', () => ({ supportedCompression: ['gzip', 'lz64'] })) - given.subject() - - expect(given.posthog.compression['gzip']).toBe(true) - expect(given.posthog.compression['lz64']).toBe(true) - }) - - it('enables compression from decide response when only one received', () => { - given('decideResponse', () => ({ supportedCompression: ['lz64'] })) - given.subject() - - expect(given.posthog.compression).not.toHaveProperty('gzip') - expect(given.posthog.compression['lz64']).toBe(true) - }) - - it('does not enable compression from decide response if compression is disabled', () => { - given('config', () => ({ disable_compression: true, persistence: 'memory' })) - given('decideResponse', () => ({ supportedCompression: ['gzip', 'lz64'] })) - given.subject() - - expect(given.posthog.compression).toEqual({}) - }) - it('Make sure receivedFeatureFlags is not called if the decide response fails', () => { given('decideResponse', () => ({ status: 0 })) window.POSTHOG_DEBUG = true diff --git a/src/__tests__/posthog-core.js b/src/__tests__/posthog-core.js index f92215eea..2e6252bc5 100644 --- a/src/__tests__/posthog-core.js +++ b/src/__tests__/posthog-core.js @@ -32,6 +32,7 @@ describe('capture()', () => { ) given('config', () => ({ + api_host: 'https://app.posthog.com', property_blacklist: [], _onCapture: jest.fn(), get_device_id: jest.fn().mockReturnValue('device-id'), @@ -57,6 +58,7 @@ describe('capture()', () => { update_config: jest.fn(), properties: jest.fn(), }, + _send_request: jest.fn(), compression: {}, __captureHooks: [], rateLimiter: { @@ -191,6 +193,91 @@ describe('capture()', () => { const captureResult = given.lib.capture('event-name', { foo: 'bar', length: 0 }) expect(captureResult.properties).toEqual(expect.objectContaining({ foo: 'bar', length: 0 })) }) + + it('sends payloads to /e/ by default', () => { + given.lib.capture('event-name', { foo: 'bar', length: 0 }) + expect(given.lib._send_request).toHaveBeenCalledWith( + 'https://app.posthog.com/e/', + expect.any(Object), + expect.any(Object), + undefined + ) + }) + + it('sends payloads to alternative endpoint if given', () => { + given.lib._afterDecideResponse({ analytics: { endpoint: '/i/v0/e/' } }) + given.lib.capture('event-name', { foo: 'bar', length: 0 }) + + expect(given.lib._send_request).toHaveBeenCalledWith( + 'https://app.posthog.com/i/v0/e/', + expect.any(Object), + expect.any(Object), + undefined + ) + }) + + it('sends payloads to overriden endpoint if given', () => { + given.lib.capture('event-name', { foo: 'bar', length: 0 }, { endpoint: '/s/' }) + expect(given.lib._send_request).toHaveBeenCalledWith( + 'https://app.posthog.com/s/', + expect.any(Object), + expect.any(Object), + undefined + ) + }) + + it('sends payloads to overriden endpoint, even if alternative endpoint is set', () => { + given.lib._afterDecideResponse({ analytics: { endpoint: '/i/v0/e/' } }) + given.lib.capture('event-name', { foo: 'bar', length: 0 }, { endpoint: '/s/' }) + expect(given.lib._send_request).toHaveBeenCalledWith( + 'https://app.posthog.com/s/', + expect.any(Object), + expect.any(Object), + undefined + ) + }) +}) + +describe('_afterDecideResponse', () => { + given('subject', () => () => given.lib._afterDecideResponse(given.decideResponse)) + + it('enables compression from decide response', () => { + given('decideResponse', () => ({ supportedCompression: ['gzip', 'lz64'] })) + given.subject() + + expect(given.lib.compression['gzip']).toBe(true) + expect(given.lib.compression['lz64']).toBe(true) + }) + + it('enables compression from decide response when only one received', () => { + given('decideResponse', () => ({ supportedCompression: ['lz64'] })) + given.subject() + + expect(given.lib.compression).not.toHaveProperty('gzip') + expect(given.lib.compression['lz64']).toBe(true) + }) + + it('does not enable compression from decide response if compression is disabled', () => { + given('config', () => ({ disable_compression: true, persistence: 'memory' })) + given('decideResponse', () => ({ supportedCompression: ['gzip', 'lz64'] })) + given.subject() + + expect(given.lib.compression).toEqual({}) + }) + + it('defaults to /e if no endpoint is given', () => { + given('decideResponse', () => ({})) + given.subject() + + expect(given.lib.analyticsDefaultEndpoint).toEqual('/e/') + }) + + it('uses the specified analytics endpoint if given', () => { + given('decideResponse', () => ({ analytics: { endpoint: '/i/v0/e/' } })) + given.subject() + + expect(given.lib.analyticsDefaultEndpoint).toEqual('/i/v0/e/') + }) }) describe('_calculate_event_properties()', () => { diff --git a/src/decide.ts b/src/decide.ts index e265ee238..297000bde 100644 --- a/src/decide.ts +++ b/src/decide.ts @@ -1,7 +1,7 @@ import { autocapture } from './autocapture' import { _base64Encode, _isUndefined, loadScript, logger } from './utils' import { PostHog } from './posthog-core' -import { Compression, DecideResponse } from './types' +import { DecideResponse } from './types' import { STORED_GROUP_PROPERTIES_KEY, STORED_PERSON_PROPERTIES_KEY } from './constants' export class Decide { @@ -59,20 +59,12 @@ export class Decide { this.instance.sessionRecording?.afterDecideResponse(response) autocapture.afterDecideResponse(response, this.instance) this.instance.webPerformance?.afterDecideResponse(response) + this.instance._afterDecideResponse(response) if (!this.instance.config.advanced_disable_feature_flags_on_first_load) { this.instance.featureFlags.receivedFeatureFlags(response) } - this.instance['compression'] = {} - if (response['supportedCompression'] && !this.instance.config.disable_compression) { - const compression: Partial> = {} - for (const method of response['supportedCompression']) { - compression[method] = true - } - this.instance['compression'] = compression - } - // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const surveysGenerator = window?.extendPostHogWithSurveys diff --git a/src/posthog-core.ts b/src/posthog-core.ts index e8f98c9bb..e6ae3802f 100644 --- a/src/posthog-core.ts +++ b/src/posthog-core.ts @@ -40,6 +40,7 @@ import { CaptureOptions, CaptureResult, Compression, + DecideResponse, EarlyAccessFeatureCallback, GDPROptions, isFeatureEnabledOptions, @@ -289,6 +290,7 @@ export class PostHog { __request_queue: [url: string, data: Record, options: XHROptions, callback?: RequestCallback][] __autocapture: boolean | AutocaptureConfig | undefined decideEndpointWasHit: boolean + analyticsDefaultEndpoint: string SentryIntegration: typeof SentryIntegration segmentIntegration: () => any @@ -311,6 +313,7 @@ export class PostHog { this.__loaded_recorder_version = undefined this.__autocapture = undefined this._jsc = function () {} as JSC + this.analyticsDefaultEndpoint = '/e/' this.featureFlags = new PostHogFeatureFlags(this) this.toolbar = new Toolbar(this) @@ -524,6 +527,21 @@ export class PostHog { // Private methods + _afterDecideResponse(response: DecideResponse) { + this.compression = {} + if (response.supportedCompression && !this.config.disable_compression) { + const compression: Partial> = {} + for (const method of response['supportedCompression']) { + compression[method] = true + } + this.compression = compression + } + + if (response.analytics?.endpoint) { + this.analyticsDefaultEndpoint = response.analytics.endpoint + } + } + _loaded(): void { // Pause `reloadFeatureFlags` calls in config.loaded callback. // These feature flags are loaded in the decide call made right @@ -874,7 +892,7 @@ export class PostHog { logger.info('send', data) const jsonData = JSON.stringify(data) - const url = this.config.api_host + (options.endpoint || '/e/') + const url = this.config.api_host + (options.endpoint || this.analyticsDefaultEndpoint) const has_unique_traits = options !== __NOOPTIONS diff --git a/src/types.ts b/src/types.ts index 4572c6588..06b57b55b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -221,6 +221,9 @@ export interface DecideResponse { errorsWhileComputingFlags: boolean autocapture_opt_out?: boolean capturePerformance?: boolean + analytics?: { + endpoint?: string + } // this is currently in development and may have breaking changes without a major version bump autocaptureExceptions?: | boolean