Skip to content

Commit

Permalink
Merge branch 'main' into dn-fix/canvas-patches
Browse files Browse the repository at this point in the history
  • Loading branch information
daibhin committed Mar 15, 2024
2 parents f86e608 + 9df1369 commit 06cce93
Show file tree
Hide file tree
Showing 11 changed files with 689 additions and 691 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## 1.115.1 - 2024-03-15

- chore: remove v1 rrweb loading (#1078)

## 1.115.0 - 2024-03-14

- feat: track recording URL without pageview capture (#1076)
- fix: return typing of global functions (#1081)

## 1.114.2 - 2024-03-12

- fix: patch rrweb zero width canvas bug (#1075)
Expand Down
1 change: 1 addition & 0 deletions cypress/support/e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ beforeEach(() => {

cy.readFile('dist/recorder.js').then((body) => {
cy.intercept('**/static/recorder.js*', { body }).as('recorder')
cy.intercept('**/static/recorder-v2.js*', { body }).as('recorder')
})

cy.readFile('dist/surveys.js').then((body) => {
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "posthog-js",
"version": "1.114.2",
"version": "1.115.1",
"description": "Posthog-js allows you to automatically capture usage and send events to PostHog.",
"repository": "https://github.com/PostHog/posthog-js",
"author": "[email protected]",
Expand Down Expand Up @@ -88,7 +88,6 @@
"rollup-plugin-visualizer": "^5.12.0",
"rrweb": "2.0.0-alpha.11",
"rrweb-snapshot": "2.0.0-alpha.11",
"rrweb-v1": "npm:[email protected]",
"sinon": "9.0.2",
"testcafe": "1.19.0",
"testcafe-browser-provider-browserstack": "1.14.0",
Expand Down
22 changes: 0 additions & 22 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 1 addition & 6 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,8 @@ export default [
format: 'iife',
name: 'posthog',
},
],
plugins: [...plugins],
},
{
input: 'src/loader-recorder-v2.ts',
output: [
{
// Backwards compatibility for older SDK versions
file: 'dist/recorder-v2.js',
sourcemap: true,
format: 'iife',
Expand Down
106 changes: 66 additions & 40 deletions src/__tests__/extensions/replay/sessionrecording.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
SESSION_RECORDING_CANVAS_RECORDING,
SESSION_RECORDING_ENABLED_SERVER_SIDE,
SESSION_RECORDING_IS_SAMPLED,
SESSION_RECORDING_RECORDER_VERSION_SERVER_SIDE,
} from '../../../constants'
import { SessionIdManager } from '../../../sessionid'
import {
Expand All @@ -23,7 +22,7 @@ import {
RECORDING_MAX_EVENT_SIZE,
SessionRecording,
} from '../../../extensions/replay/sessionrecording'
import { assignableWindow } from '../../../utils/globals'
import { assignableWindow, window } from '../../../utils/globals'
import { RequestRouter } from '../../../utils/request-router'
import { customEvent, EventType, eventWithTime, pluginEvent } from '@rrweb/types'
import Mock = jest.Mock
Expand Down Expand Up @@ -79,6 +78,12 @@ function makeDecideResponse(partialResponse: Partial<DecideResponse>) {
return partialResponse as unknown as DecideResponse
}

const originalLocation = window!.location
function fakeNavigateTo(href: string) {
delete (window as any).location
window!.location = { href } as Location
}

describe('SessionRecording', () => {
const _addCustomEvent = jest.fn()
let _emit: any
Expand Down Expand Up @@ -145,14 +150,17 @@ describe('SessionRecording', () => {
// defaults
posthog.persistence?.register({
[SESSION_RECORDING_ENABLED_SERVER_SIDE]: true,
[SESSION_RECORDING_RECORDER_VERSION_SERVER_SIDE]: 'v2',
[CONSOLE_LOG_RECORDING_ENABLED_SERVER_SIDE]: false,
[SESSION_RECORDING_IS_SAMPLED]: undefined,
})

sessionRecording = new SessionRecording(posthog)
})

afterEach(() => {
window!.location = originalLocation
})

describe('isRecordingEnabled', () => {
it('is enabled if both the server and client config says enabled', () => {
posthog.persistence?.register({ [SESSION_RECORDING_ENABLED_SERVER_SIDE]: true })
Expand Down Expand Up @@ -193,33 +201,6 @@ describe('SessionRecording', () => {
})
})

describe('getRecordingVersion', () => {
it('uses client side setting v2 over server side', () => {
posthog.persistence?.register({ [SESSION_RECORDING_RECORDER_VERSION_SERVER_SIDE]: 'v1' })
posthog.config.session_recording.recorderVersion = 'v2'
expect(sessionRecording['recordingVersion']).toBe('v2')
})

it('uses client side setting v1 over server side', () => {
posthog.persistence?.register({ [SESSION_RECORDING_RECORDER_VERSION_SERVER_SIDE]: 'v2' })
posthog.config.session_recording.recorderVersion = 'v1'
expect(sessionRecording['recordingVersion']).toBe('v1')
})

it('uses server side setting if client side setting is not set', () => {
posthog.config.session_recording.recorderVersion = undefined

posthog.persistence?.register({ [SESSION_RECORDING_RECORDER_VERSION_SERVER_SIDE]: 'v1' })
expect(sessionRecording['recordingVersion']).toBe('v1')

posthog.persistence?.register({ [SESSION_RECORDING_RECORDER_VERSION_SERVER_SIDE]: 'v2' })
expect(sessionRecording['recordingVersion']).toBe('v2')

posthog.persistence?.register({ [SESSION_RECORDING_RECORDER_VERSION_SERVER_SIDE]: undefined })
expect(sessionRecording['recordingVersion']).toBe('v1')
})
})

describe('startRecordingIfEnabled', () => {
beforeEach(() => {
// need to cast as any to mock private methods
Expand Down Expand Up @@ -719,16 +700,7 @@ describe('SessionRecording', () => {
expect(loadScript).not.toHaveBeenCalled()
})

it('loads recording v1 script from right place', () => {
posthog.config.session_recording.recorderVersion = 'v1'

sessionRecording.startRecordingIfEnabled()

expect(loadScript).toHaveBeenCalledWith('https://test.com/static/recorder.js?v=v0.0.1', expect.anything())
})

it('loads recording v2 script from right place', () => {
posthog.persistence?.register({ [SESSION_RECORDING_RECORDER_VERSION_SERVER_SIDE]: 'v2' })
sessionRecording.startRecordingIfEnabled()

expect(loadScript).toHaveBeenCalledWith(
Expand All @@ -739,7 +711,6 @@ describe('SessionRecording', () => {

it('load correct recording version if there is a cached mismatch', () => {
posthog.__loaded_recorder_version = 'v1'
posthog.persistence?.register({ [SESSION_RECORDING_RECORDER_VERSION_SERVER_SIDE]: 'v2' })
sessionRecording.startRecordingIfEnabled()

expect(loadScript).toHaveBeenCalledWith(
Expand Down Expand Up @@ -1592,4 +1563,59 @@ describe('SessionRecording', () => {
expect(sessionRecording['_fullSnapshotTimer']).not.toBe(startTimer)
})
})

describe('when pageview capture is disabled', () => {
beforeEach(() => {
jest.spyOn(sessionRecording as any, '_tryAddCustomEvent')
posthog.config.capture_pageview = false
sessionRecording.startRecordingIfEnabled()
// clear the spy calls
;(sessionRecording as any)._tryAddCustomEvent.mockClear()
})

it('does not capture pageview on meta event', () => {
_emit(createIncrementalSnapshot({ type: META_EVENT_TYPE }))

expect((sessionRecording as any)['_tryAddCustomEvent']).not.toHaveBeenCalled()
})

it('captures pageview as expected on non-meta event', () => {
fakeNavigateTo('https://test.com')

_emit(createIncrementalSnapshot({ type: 3 }))

expect((sessionRecording as any)['_tryAddCustomEvent']).toHaveBeenCalledWith('$url_changed', {
href: 'https://test.com',
})
;(sessionRecording as any)._tryAddCustomEvent.mockClear()

_emit(createIncrementalSnapshot({ type: 3 }))
// the window href has not changed, so we don't capture another pageview
expect((sessionRecording as any)['_tryAddCustomEvent']).not.toHaveBeenCalled()

fakeNavigateTo('https://test.com/other')
_emit(createIncrementalSnapshot({ type: 3 }))

// the window href has changed, so we capture another pageview
expect((sessionRecording as any)['_tryAddCustomEvent']).toHaveBeenCalledWith('$url_changed', {
href: 'https://test.com/other',
})
})
})

describe('when pageview capture is enabled', () => {
beforeEach(() => {
jest.spyOn(sessionRecording as any, '_tryAddCustomEvent')
posthog.config.capture_pageview = true
sessionRecording.startRecordingIfEnabled()
// clear the spy calls
;(sessionRecording as any)._tryAddCustomEvent.mockClear()
})

it('does not capture pageview on rrweb events', () => {
_emit(createIncrementalSnapshot({ type: 3 }))

expect((sessionRecording as any)['_tryAddCustomEvent']).not.toHaveBeenCalled()
})
})
})
1 change: 0 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ export const EVENT_TIMERS_KEY = '__timers'
export const AUTOCAPTURE_DISABLED_SERVER_SIDE = '$autocapture_disabled_server_side'
export const SESSION_RECORDING_ENABLED_SERVER_SIDE = '$session_recording_enabled_server_side'
export const CONSOLE_LOG_RECORDING_ENABLED_SERVER_SIDE = '$console_log_recording_enabled_server_side'
export const SESSION_RECORDING_RECORDER_VERSION_SERVER_SIDE = '$session_recording_recorder_version_server_side' // follows rrweb versioning
export const SESSION_RECORDING_NETWORK_PAYLOAD_CAPTURE = '$session_recording_network_payload_capture'
export const SESSION_RECORDING_CANVAS_RECORDING = '$session_recording_canvas_recording'
export const SESSION_ID = '$sesid'
Expand Down
41 changes: 26 additions & 15 deletions src/extensions/replay/sessionrecording.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
SESSION_RECORDING_ENABLED_SERVER_SIDE,
SESSION_RECORDING_IS_SAMPLED,
SESSION_RECORDING_NETWORK_PAYLOAD_CAPTURE,
SESSION_RECORDING_RECORDER_VERSION_SERVER_SIDE,
} from '../../constants'
import {
FULL_SNAPSHOT_EVENT_TYPE,
Expand Down Expand Up @@ -138,6 +137,10 @@ export class SessionRecording {

private _fullSnapshotTimer?: ReturnType<typeof setInterval>

// if pageview capture is disabled
// then we can manually track href changes
private _lastHref?: string

// Util to help developers working on this feature manually override
_forceAllowLocalhostNetworkCapture = false

Expand Down Expand Up @@ -192,12 +195,6 @@ export class SessionRecording {
: undefined
}

private get recordingVersion() {
const recordingVersion_server_side = this.instance.get_property(SESSION_RECORDING_RECORDER_VERSION_SERVER_SIDE)
const recordingVersion_client_side = this.instance.config.session_recording?.recorderVersion
return recordingVersion_client_side || recordingVersion_server_side || 'v1'
}

// network payload capture config has three parts
// each can be configured server side or client side
private get networkPayloadCapture():
Expand Down Expand Up @@ -341,7 +338,6 @@ export class SessionRecording {
this.instance.persistence.register({
[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]: {
capturePerformance: response.capturePerformance,
...response.sessionRecording?.networkPayloadCapture,
Expand Down Expand Up @@ -429,17 +425,15 @@ export class SessionRecording {
// We want to ensure the sessionManager is reset if necessary on load of the recorder
this.sessionManager.checkAndGetSessionAndWindowId()

const recorderJS = this.recordingVersion === 'v2' ? 'recorder-v2.js' : 'recorder.js'

// If recorder.js is already loaded (if array.full.js snippet is used or posthog-js/dist/recorder is
// imported) or matches the requested recorder version, don't load script. Otherwise, remotely import
// recorder.js from cdn since it hasn't been loaded.
if (this.instance.__loaded_recorder_version !== this.recordingVersion) {
if (this.instance.__loaded_recorder_version !== 'v2') {
loadScript(
this.instance.requestRouter.endpointFor('assets', `/static/${recorderJS}?v=${Config.LIB_VERSION}`),
this.instance.requestRouter.endpointFor('assets', `/static/recorder-v2.js?v=${Config.LIB_VERSION}`),
(err) => {
if (err) {
return logger.error(LOGGER_PREFIX + ` could not load ${recorderJS}`, err)
return logger.error(LOGGER_PREFIX + ` could not load recorder-v2.js`, err)
}

this._onScriptLoaded()
Expand Down Expand Up @@ -518,13 +512,16 @@ export class SessionRecording {
return true
} catch (e) {
// Sometimes a race can occur where the recorder is not fully started yet
logger.warn(LOGGER_PREFIX + ' could not emit queued rrweb event.', e)
this.queuedRRWebEvents.length < 10 &&
if (this.queuedRRWebEvents.length < 10) {
this.queuedRRWebEvents.push({
enqueuedAt: queuedRRWebEvent.enqueuedAt || Date.now(),
attempt: queuedRRWebEvent.attempt++,
rrwebMethod: queuedRRWebEvent.rrwebMethod,
})
} else {
logger.warn(LOGGER_PREFIX + ' could not emit queued rrweb event.', e, queuedRRWebEvent)
}

return false
}
}
Expand Down Expand Up @@ -689,10 +686,13 @@ export class SessionRecording {

if (rawEvent.type === EventType.Meta) {
const href = this._maskUrl(rawEvent.data.href)
this._lastHref = href
if (!href) {
return
}
rawEvent.data.href = href
} else {
this._pageViewFallBack()
}

if (rawEvent.type === EventType.FullSnapshot) {
Expand Down Expand Up @@ -734,6 +734,17 @@ export class SessionRecording {
}
}

private _pageViewFallBack() {
if (this.instance.config.capture_pageview || !window) {
return
}
const currentUrl = this._maskUrl(window.location.href)
if (this._lastHref !== currentUrl) {
this._tryAddCustomEvent('$url_changed', { href: currentUrl })
this._lastHref = currentUrl
}
}

private _processQueuedEvents() {
if (this.queuedRRWebEvents.length) {
// if rrweb isn't ready to accept events earlier then we queued them up
Expand Down
Loading

0 comments on commit 06cce93

Please sign in to comment.