-
Notifications
You must be signed in to change notification settings - Fork 135
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: capture network payloads (internal alpha) (#886)
rrweb has network payloads queued up as a feature... but it's taking a while. The easiest way to test it is to adopt it ourselves. This adds a copy of the plugin proposed for rrweb, and uses it to wrap xhr and fetch. We can match performance timings and these new NetworkRequests based on URL and timings used by PostHog/posthog#18562 for now this can only be enabled via decide response, which allows header and body capture to be configured separately, that config is only enabled via flag while we test internally
- Loading branch information
1 parent
69a078d
commit 80e45cb
Showing
7 changed files
with
703 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { defaultConfig } from '../../../posthog-core' | ||
import { buildNetworkRequestOptions } from '../../../extensions/replay/config' | ||
|
||
describe('config', () => { | ||
describe('network request options', () => { | ||
describe('maskRequestFn', () => { | ||
it('can enable header recording remotely', () => { | ||
const networkOptions = buildNetworkRequestOptions(defaultConfig(), { recordHeaders: true }) | ||
expect(networkOptions.recordHeaders).toBe(true) | ||
expect(networkOptions.recordBody).toBe(undefined) | ||
}) | ||
|
||
it('can enable body recording remotely', () => { | ||
const networkOptions = buildNetworkRequestOptions(defaultConfig(), { recordBody: true }) | ||
expect(networkOptions.recordHeaders).toBe(undefined) | ||
expect(networkOptions.recordBody).toBe(true) | ||
}) | ||
|
||
it('client can force disable recording', () => { | ||
const posthogConfig = defaultConfig() | ||
posthogConfig.session_recording.recordHeaders = false | ||
posthogConfig.session_recording.recordBody = false | ||
const networkOptions = buildNetworkRequestOptions(posthogConfig, { | ||
recordHeaders: true, | ||
recordBody: true, | ||
}) | ||
expect(networkOptions.recordHeaders).toBe(false) | ||
expect(networkOptions.recordBody).toBe(false) | ||
}) | ||
|
||
it('should remove the Authorization header from requests even if no other config is set', () => { | ||
const networkOptions = buildNetworkRequestOptions(defaultConfig(), {}) | ||
const cleaned = networkOptions.maskRequestFn!({ | ||
url: 'something', | ||
requestHeaders: { | ||
Authorization: 'Bearer 123', | ||
'content-type': 'application/json', | ||
}, | ||
}) | ||
expect(cleaned?.requestHeaders).toEqual({ | ||
'content-type': 'application/json', | ||
}) | ||
}) | ||
|
||
it('should cope with no headers when even if no other config is set', () => { | ||
const networkOptions = buildNetworkRequestOptions(defaultConfig(), {}) | ||
const cleaned = networkOptions.maskRequestFn!({ | ||
url: 'something', | ||
requestHeaders: undefined, | ||
}) | ||
expect(cleaned?.requestHeaders).toBeUndefined() | ||
}) | ||
|
||
it('should remove the Authorization header from requests even when a mask request fn is set', () => { | ||
const posthogConfig = defaultConfig() | ||
posthogConfig.session_recording.maskNetworkRequestFn = (data) => { | ||
return { | ||
...data, | ||
requestHeaders: { | ||
...(data.requestHeaders ? data.requestHeaders : {}), | ||
'content-type': 'edited', | ||
}, | ||
} | ||
} | ||
const networkOptions = buildNetworkRequestOptions(posthogConfig, {}) | ||
|
||
const cleaned = networkOptions.maskRequestFn!({ | ||
url: 'something', | ||
requestHeaders: { | ||
Authorization: 'Bearer 123', | ||
'content-type': 'application/json', | ||
}, | ||
}) | ||
expect(cleaned?.requestHeaders).toEqual({ | ||
'content-type': 'edited', | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { NetworkRecordOptions, NetworkRequest, PostHogConfig } from '../../types' | ||
import { _isFunction } from '../../utils/type-utils' | ||
|
||
export const defaultNetworkOptions: NetworkRecordOptions = { | ||
initiatorTypes: [ | ||
'audio', | ||
'beacon', | ||
'body', | ||
'css', | ||
'early-hint', | ||
'embed', | ||
'fetch', | ||
'frame', | ||
'iframe', | ||
'icon', | ||
'image', | ||
'img', | ||
'input', | ||
'link', | ||
'navigation', | ||
'object', | ||
'ping', | ||
'script', | ||
'track', | ||
'video', | ||
'xmlhttprequest', | ||
], | ||
maskRequestFn: (data: NetworkRequest) => data, | ||
recordHeaders: false, | ||
recordBody: false, | ||
recordInitialRequests: false, | ||
} | ||
|
||
const removeAuthorizationHeader = (data: NetworkRequest): NetworkRequest => { | ||
delete data.requestHeaders?.['Authorization'] | ||
return data | ||
} | ||
|
||
/** | ||
* whether a maskRequestFn is provided or not, | ||
* we ensure that we remove the Authorization header from requests | ||
* we _never_ want to record that header by accident | ||
* if someone complains then we'll add an opt-in to let them override it | ||
*/ | ||
export const buildNetworkRequestOptions = ( | ||
instanceConfig: PostHogConfig, | ||
remoteNetworkOptions: Pick<NetworkRecordOptions, 'recordHeaders' | 'recordBody'> | ||
): NetworkRecordOptions => { | ||
const config = instanceConfig.session_recording as NetworkRecordOptions | ||
// client can always disable despite remote options | ||
const canRecordHeaders = config.recordHeaders === false ? false : remoteNetworkOptions.recordHeaders | ||
const canRecordBody = config.recordBody === false ? false : remoteNetworkOptions.recordBody | ||
|
||
config.maskRequestFn = _isFunction(instanceConfig.session_recording.maskNetworkRequestFn) | ||
? (data) => { | ||
const cleanedRequest = removeAuthorizationHeader(data) | ||
return instanceConfig.session_recording.maskNetworkRequestFn?.(cleanedRequest) ?? undefined | ||
} | ||
: undefined | ||
|
||
if (!config.maskRequestFn) { | ||
config.maskRequestFn = removeAuthorizationHeader | ||
} | ||
|
||
return { | ||
...defaultNetworkOptions, | ||
...config, | ||
recordHeaders: canRecordHeaders, | ||
recordBody: canRecordBody, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.