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

feat: payload capture - move timing into copied plugin #902

Merged
merged 14 commits into from
Nov 21, 2023
2 changes: 1 addition & 1 deletion src/__tests__/extensions/replay/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe('config', () => {

it('should remove the Authorization header from requests even when a mask request fn is set', () => {
const posthogConfig = defaultConfig()
posthogConfig.session_recording.maskNetworkRequestFn = (data) => {
posthogConfig.session_recording.maskCapturedNetworkRequestFn = (data) => {
return {
...data,
requestHeaders: {
Expand Down
161 changes: 0 additions & 161 deletions src/__tests__/extensions/replay/web-performance.test.ts

This file was deleted.

1 change: 0 additions & 1 deletion src/decide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ export class Decide {
this.instance.toolbar.afterDecideResponse(response)
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) {
Expand Down
44 changes: 35 additions & 9 deletions src/extensions/replay/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NetworkRecordOptions, NetworkRequest, PostHogConfig } from '../../types'
import { CapturedNetworkRequest, NetworkRecordOptions, PostHogConfig } from '../../types'
import { _isFunction } from '../../utils/type-utils'
import { convertToURL } from '../../utils/request-utils'

export const defaultNetworkOptions: NetworkRecordOptions = {
initiatorTypes: [
Expand All @@ -25,10 +26,20 @@ export const defaultNetworkOptions: NetworkRecordOptions = {
'video',
'xmlhttprequest',
],
maskRequestFn: (data: NetworkRequest) => data,
maskRequestFn: (data: CapturedNetworkRequest) => data,
recordHeaders: false,
recordBody: false,
recordInitialRequests: false,
recordPerformance: false,
performanceEntryTypeToObserve: [
// 'event', // This is too noisy as it covers all browser events
'first-input',
// 'mark', // Mark is used too liberally. We would need to filter for specific marks
// 'measure', // Measure is used too liberally. We would need to filter for specific measures
'navigation',
'paint',
'resource',
],
}

const HEADER_DENYLIST = [
Expand All @@ -47,43 +58,58 @@ const HEADER_DENYLIST = [
'x-xsrf-token',
]

const removeAuthorizationHeader = (data: NetworkRequest): NetworkRequest => {
const removeAuthorizationHeader = (data: CapturedNetworkRequest): CapturedNetworkRequest => {
Object.keys(data.requestHeaders ?? {}).forEach((header) => {
if (HEADER_DENYLIST.includes(header.toLowerCase())) delete data.requestHeaders?.[header]
})
return data
}

const POSTHOG_PATHS_TO_IGNORE = ['/s/', '/e/', '/i/vo/e/']
// want to ignore posthog paths when capturing requests, or we can get trapped in a loop
const ignorePostHogPaths = (data: CapturedNetworkRequest): CapturedNetworkRequest | undefined => {
const url = convertToURL(data.name)
if (url && url.pathname && POSTHOG_PATHS_TO_IGNORE.includes(url.pathname)) {
return undefined
}
return data
}

/**
* whether a maskRequestFn is provided or not,
* we ensure that we remove the Authorization header from requests
* we ensure that we remove the denied 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'>
remoteNetworkOptions: Pick<NetworkRecordOptions, 'recordHeaders' | 'recordBody' | 'recordPerformance'>
): 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
const canRecordPerformance = config.recordPerformance === false ? false : remoteNetworkOptions.recordPerformance

config.maskRequestFn = _isFunction(instanceConfig.session_recording.maskNetworkRequestFn)
config.maskRequestFn = _isFunction(instanceConfig.session_recording.maskCapturedNetworkRequestFn)
? (data) => {
const cleanedRequest = removeAuthorizationHeader(data)
return instanceConfig.session_recording.maskNetworkRequestFn?.(cleanedRequest) ?? undefined
const cleanedRequest = ignorePostHogPaths(removeAuthorizationHeader(data))
return cleanedRequest
? instanceConfig.session_recording.maskCapturedNetworkRequestFn?.(cleanedRequest) ?? undefined
: undefined
}
: undefined

if (!config.maskRequestFn) {
config.maskRequestFn = removeAuthorizationHeader
config.maskRequestFn = (data) => ignorePostHogPaths(removeAuthorizationHeader(data))
}

return {
...defaultNetworkOptions,
...config,
recordHeaders: canRecordHeaders,
recordBody: canRecordBody,
recordPerformance: canRecordPerformance,
recordInitialRequests: canRecordPerformance,
}
}
18 changes: 15 additions & 3 deletions src/extensions/replay/sessionrecording.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,11 @@ export class SessionRecording {
return recordingVersion_client_side || recordingVersion_server_side || 'v1'
}

private get networkPayloadCapture(): Pick<NetworkRecordOptions, 'recordHeaders' | 'recordBody'> | undefined {
// network payload capture config has three parts
// each can be configured server side or client side
private get networkPayloadCapture():
| Pick<NetworkRecordOptions, 'recordHeaders' | 'recordBody' | 'recordPerformance'>
| undefined {
const networkPayloadCapture_server_side = this.instance.get_property(SESSION_RECORDING_NETWORK_PAYLOAD_CAPTURE)
const networkPayloadCapture_client_side = {
recordHeaders: this.instance.config.session_recording?.recordHeaders,
Expand All @@ -158,7 +162,12 @@ export class SessionRecording {
networkPayloadCapture_client_side?.recordHeaders || networkPayloadCapture_server_side?.recordHeaders
const bodyEnabled =
networkPayloadCapture_client_side?.recordBody || networkPayloadCapture_server_side?.recordBody
return headersEnabled || bodyEnabled ? { recordHeaders: headersEnabled, recordBody: bodyEnabled } : undefined
const performanceEnabled =
this.instance.config.capture_performance || networkPayloadCapture_server_side?.capturePerformance

return headersEnabled || bodyEnabled || performanceEnabled
? { recordHeaders: headersEnabled, recordBody: bodyEnabled, recordPerformance: performanceEnabled }
: undefined
}

/**
Expand Down Expand Up @@ -265,7 +274,10 @@ export class SessionRecording {
[SESSION_RECORDING_ENABLED_SERVER_SIDE]: !!response['sessionRecording'],
[CONSOLE_LOG_RECORDING_ENABLED_SERVER_SIDE]: response.sessionRecording?.consoleLogRecordingEnabled,
[SESSION_RECORDING_RECORDER_VERSION_SERVER_SIDE]: response.sessionRecording?.recorderVersion,
[SESSION_RECORDING_NETWORK_PAYLOAD_CAPTURE]: response.sessionRecording?.networkPayloadCapture,
[SESSION_RECORDING_NETWORK_PAYLOAD_CAPTURE]: {
capturePerformance: response.capturePerformance,
...response.sessionRecording?.networkPayloadCapture,
},
})
}

Expand Down
Loading
Loading