Skip to content

Commit

Permalink
feat: signal we have wrapped fetch (#1083)
Browse files Browse the repository at this point in the history
* feat: on network ready callback

* fix

* fix

* without changing feature flag callback

* reset

* make tests run locally for me

* first let's make similar things the same

* resolve the mess

* fix

* better message

* fix

* massively unwind this
  • Loading branch information
pauldambra authored Mar 22, 2024
1 parent 77148cb commit 123bd01
Show file tree
Hide file tree
Showing 10 changed files with 91 additions and 31 deletions.
49 changes: 46 additions & 3 deletions cypress/e2e/session-recording.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function ensureActivitySendsSnapshots() {
.then(() => {
cy.phCaptures({ full: true }).then((captures) => {
expect(captures.map((c) => c.event)).to.deep.equal(['$snapshot'])
expect(captures[0]['properties']['$snapshot_data']).to.have.length.above(14).and.below(39)
expect(captures[0]['properties']['$snapshot_data']).to.have.length.above(14).and.below(40)
// a meta and then a full snapshot
expect(captures[0]['properties']['$snapshot_data'][0].type).to.equal(4) // meta
expect(captures[0]['properties']['$snapshot_data'][1].type).to.equal(2) // full_snapshot
Expand Down Expand Up @@ -64,7 +64,7 @@ describe('Session recording', () => {
// should be a pageview and a $snapshot
expect(captures.map((c) => c.event)).to.deep.equal(['$pageview', '$snapshot'])

expect(captures[1]['properties']['$snapshot_data']).to.have.length.above(33).and.below(39)
expect(captures[1]['properties']['$snapshot_data']).to.have.length.above(33).and.below(40)
// a meta and then a full snapshot
expect(captures[1]['properties']['$snapshot_data'][0].type).to.equal(4) // meta
expect(captures[1]['properties']['$snapshot_data'][1].type).to.equal(2) // full_snapshot
Expand All @@ -78,6 +78,49 @@ describe('Session recording', () => {
})
})

describe('with network capture', () => {
beforeEach(() => {
start({
decideResponseOverrides: {
config: { enable_collect_everything: false },
isAuthenticated: false,
sessionRecording: {
endpoint: '/ses/',
networkPayloadCapture: { recordBody: true },
},
capturePerformance: true,
},
url: './playground/cypress',
options: {
loaded: (ph) => {
ph.sessionRecording._forceAllowLocalhostNetworkCapture = true
},
},
})

cy.wait('@recorder')
})

it('it sends network payloads', () => {
cy.intercept('https://example.com', 'success').as('example.com')
cy.get('[data-cy-network-call-button]').click()
cy.wait('@example.com')
cy.wait('@session-recording')
cy.phCaptures({ full: true }).then((captures) => {
const snapshots = captures.filter((c) => c.event === '$snapshot')

const snapshotTypes: number[] = []
for (const snapshot of snapshots) {
for (const snapshotData of snapshot.properties['$snapshot_data']) {
snapshotTypes.push(snapshotData.type)
}
}
// yay, includes type 6 network data
expect(snapshotTypes.filter((x) => x === 6)).to.have.length.above(0)
})
})
})

describe('array.js', () => {
beforeEach(() => {
start({
Expand Down Expand Up @@ -112,7 +155,7 @@ describe('Session recording', () => {
cy.posthog().then((ph) => {
ph.stopSessionRecording()
})
cy.resetPhCaptures()

ensureRecordingIsStopped()

// restarting recording
Expand Down
4 changes: 4 additions & 0 deletions playground/cypress/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
Send custom event
</button>

<button data-cy-network-call-button onclick="fetch('https://example.com')">
Make network call
</button>

<br />

<button data-cy-feature-flag-button onclick="console.log(posthog.isFeatureEnabled('some-feature'))">
Expand Down
24 changes: 12 additions & 12 deletions src/__tests__/extensions/replay/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('config', () => {
const cleaned = networkOptions.maskRequestFn!({
name: 'something',
requestHeaders: undefined,
})
} as Partial<CapturedNetworkRequest> as CapturedNetworkRequest)
expect(cleaned).toEqual({
name: 'something',
requestHeaders: undefined,
Expand All @@ -57,7 +57,7 @@ describe('config', () => {
Authorization: 'Bearer 123',
'content-type': 'application/json',
},
})
} as Partial<CapturedNetworkRequest> as CapturedNetworkRequest)
expect(cleaned).toEqual({
name: 'edited',
requestHeaders: {
Expand Down Expand Up @@ -102,7 +102,7 @@ describe('config', () => {
],
])('ignores ingestion paths', (capturedRequest, expected) => {
const networkOptions = buildNetworkRequestOptions(defaultConfig(), {})
const x = networkOptions.maskRequestFn!(capturedRequest)
const x = networkOptions.maskRequestFn!(capturedRequest as CapturedNetworkRequest)
expect(x).toEqual(expected)
})

Expand All @@ -115,7 +115,7 @@ describe('config', () => {
'content-length': '1000001',
},
requestBody: 'something very large',
})
} as Partial<CapturedNetworkRequest> as CapturedNetworkRequest)
expect(cleaned).toEqual({
name: 'something',
requestHeaders: {
Expand All @@ -135,7 +135,7 @@ describe('config', () => {
'content-length': '1000001',
},
responseBody: 'something very large',
})
} as Partial<CapturedNetworkRequest> as CapturedNetworkRequest)
expect(cleaned).toEqual({
name: 'something',
responseHeaders: {
Expand All @@ -154,7 +154,7 @@ describe('config', () => {
'content-type': 'application/json',
},
requestBody: 'some body that has no content length',
})
} as Partial<CapturedNetworkRequest> as CapturedNetworkRequest)
expect(cleaned).toEqual({
name: 'something',
requestHeaders: {
Expand All @@ -172,7 +172,7 @@ describe('config', () => {
'content-type': 'application/json',
},
requestBody: 'a'.repeat(1000001),
})
} as Partial<CapturedNetworkRequest> as CapturedNetworkRequest)
expect(cleaned).toEqual({
name: 'something',
requestHeaders: {
Expand All @@ -193,7 +193,7 @@ describe('config', () => {
Authorization: 'Bearer 123',
'content-type': 'application/json',
},
})
} as Partial<CapturedNetworkRequest> as CapturedNetworkRequest)
expect(cleaned).toEqual({
name: 'something',
requestHeaders: {
Expand Down Expand Up @@ -221,7 +221,7 @@ describe('config', () => {
Authorization: 'Bearer 123',
'content-type': 'application/json',
},
})
} as Partial<CapturedNetworkRequest> as CapturedNetworkRequest)
expect(cleaned).toEqual({
name: 'something',
requestHeaders: {
Expand All @@ -240,7 +240,7 @@ describe('config', () => {
},
requestBody: 'some body with password',
responseBody: 'some body with password',
})
} as Partial<CapturedNetworkRequest> as CapturedNetworkRequest)
expect(cleaned).toEqual({
name: 'something',
requestHeaders: {
Expand Down Expand Up @@ -293,7 +293,7 @@ describe('config', () => {
AuThOrIzAtIoN: 'Bearer 123',
'content-type': 'application/json',
},
})
} as Partial<CapturedNetworkRequest> as CapturedNetworkRequest)
expect(cleaned).toEqual({
name: 'something',
requestHeaders: {
Expand All @@ -312,7 +312,7 @@ describe('config', () => {
},
requestBody: 'take payment with CC 4242 4242 4242 4242',
responseBody: 'take payment with CC 4242 4242 4242 4242',
})
} as Partial<CapturedNetworkRequest> as CapturedNetworkRequest)
expect(cleaned).toEqual({
name: 'something',
requestHeaders: {
Expand Down
1 change: 0 additions & 1 deletion src/__tests__/extensions/replay/sessionrecording.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -848,7 +848,6 @@ describe('SessionRecording', () => {
describe('onSessionId Callbacks', () => {
let mockCallback: Mock<SessionIdChangedCallback>
let unsubscribeCallback: () => void

beforeEach(() => {
sessionManager = new SessionIdManager(config, new PostHogPersistence(config))
posthog.sessionManager = sessionManager
Expand Down
1 change: 0 additions & 1 deletion src/__tests__/extensions/surveys.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'regenerator-runtime/runtime'
import { generateSurveys, renderSurveysPreview, renderFeedbackWidgetPreview } from '../../extensions/surveys'
import { createShadow } from '../../extensions/surveys/surveys-utils'
import { Survey, SurveyQuestionType, SurveyType } from '../../posthog-surveys-types'
Expand Down
5 changes: 2 additions & 3 deletions src/__tests__/helpers/posthog-instance.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
// The library depends on having the module initialized before it can be used.

import { v4 } from 'uuid'
import { PostHog } from '../../posthog-core'
import 'regenerator-runtime/runtime'
import { PostHogConfig } from '../../types'
import { uuidv7 } from '../../uuidv7'

export const createPosthogInstance = async (
token: string = v4(),
token: string = uuidv7(),
config: Partial<PostHogConfig> = {}
): Promise<PostHog> => {
// We need to create a new instance of the library for each test, to ensure
Expand Down
6 changes: 3 additions & 3 deletions src/__tests__/identify.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { v4 } from 'uuid'
import { createPosthogInstance } from './helpers/posthog-instance'
import { logger } from '../utils/logger'
import { uuidv7 } from '../uuidv7'
jest.mock('../utils/logger')

describe('identify', () => {
Expand All @@ -10,7 +10,7 @@ describe('identify', () => {

it('should persist the distinct_id', async () => {
// arrange
const token = v4()
const token = uuidv7()
const posthog = await createPosthogInstance(token)
const distinctId = '123'

Expand All @@ -25,7 +25,7 @@ describe('identify', () => {

it('should convert a numeric distinct_id to a string', async () => {
// arrange
const token = v4()
const token = uuidv7()
const posthog = await createPosthogInstance(token)
const distinctIdNum = 123
const distinctIdString = '123'
Expand Down
14 changes: 10 additions & 4 deletions src/extensions/replay/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,17 @@ export const buildNetworkRequestOptions = (
instanceConfig: PostHogConfig,
remoteNetworkOptions: Pick<NetworkRecordOptions, 'recordHeaders' | 'recordBody' | 'recordPerformance'>
): NetworkRecordOptions => {
const config = instanceConfig.session_recording as NetworkRecordOptions
const config: NetworkRecordOptions = {
payloadSizeLimitBytes: defaultNetworkOptions.payloadSizeLimitBytes,
performanceEntryTypeToObserve: [...defaultNetworkOptions.performanceEntryTypeToObserve],
}
// 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
const canRecordHeaders =
instanceConfig.session_recording.recordHeaders === false ? false : remoteNetworkOptions.recordHeaders
const canRecordBody =
instanceConfig.session_recording.recordBody === false ? false : remoteNetworkOptions.recordBody
const canRecordPerformance =
instanceConfig.capture_performance === false ? false : remoteNetworkOptions.recordPerformance

const payloadLimiter = limitPayloadSize(config)

Expand Down
4 changes: 4 additions & 0 deletions src/loader-recorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ export function patch(
enumerable: false,
value: original,
},
__posthog_wrapped__: {
enumerable: false,
value: true,
},
})
}

Expand Down
14 changes: 10 additions & 4 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -404,12 +404,18 @@ export type NetworkRecordOptions = {
recordHeaders?: boolean | { request: boolean; response: boolean }
recordBody?: boolean | string[] | { request: boolean | string[]; response: boolean | string[] }
recordInitialRequests?: boolean
// whether to record PerformanceEntry events for network requests
/**
* whether to record PerformanceEntry events for network requests
*/
recordPerformance?: boolean
// the PerformanceObserver will only observe these entry types
/**
* the PerformanceObserver will only observe these entry types
*/
performanceEntryTypeToObserve: string[]
// the maximum size of the request/response body to record
// NB this will be at most 1MB even if set larger
/**
* the maximum size of the request/response body to record
* NB this will be at most 1MB even if set larger
*/
payloadSizeLimitBytes: number
}

Expand Down

0 comments on commit 123bd01

Please sign in to comment.