Skip to content

Commit

Permalink
Merge branch 'main' into feat/allow-different-scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
pauldambra authored Nov 28, 2024
2 parents 14ff83b + cc59e66 commit 45fdae5
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 23 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 1.192.1 - 2024-11-28



## 1.192.0 - 2024-11-28

- feat: Start tracking timezone offset and language prefix (#1568)

## 1.191.0 - 2024-11-28

- feat: different default and max idle period (#1558)
Expand Down
1 change: 1 addition & 0 deletions cypress/e2e/session-recording.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,7 @@ describe('Session recording', () => {
capturedSnapshot['properties']['$snapshot_data'][3],
capturedSnapshot['properties']['$snapshot_data'][4],
])

expectPageViewCustomEvent(customEvents[0])
expectPostHogConfigCustomEvent(customEvents[1])
expectSessionOptionsCustomEvent(customEvents[2])
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "posthog-js",
"version": "1.191.0",
"version": "1.192.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
84 changes: 70 additions & 14 deletions src/__tests__/extensions/replay/sessionrecording.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,7 @@ describe('SessionRecording', () => {
let sessionIdGeneratorMock: Mock
let windowIdGeneratorMock: Mock
let onFeatureFlagsCallback: ((flags: string[], variants: Record<string, string | boolean>) => void) | null
let removeCaptureHookMock: Mock
let addCaptureHookMock: Mock
let removePageviewCaptureHookMock: Mock
let simpleEventEmitter: SimpleEventEmitter

const addRRwebToWindow = () => {
Expand All @@ -208,6 +207,7 @@ describe('SessionRecording', () => {
}

beforeEach(() => {
removePageviewCaptureHookMock = jest.fn()
sessionId = 'sessionId' + uuidv7()

config = {
Expand Down Expand Up @@ -241,10 +241,6 @@ describe('SessionRecording', () => {
windowIdGeneratorMock
)

// add capture hook returns an unsubscribe function
removeCaptureHookMock = jest.fn()
addCaptureHookMock = jest.fn().mockImplementation(() => removeCaptureHookMock)

simpleEventEmitter = new SimpleEventEmitter()
// TODO we really need to make this a real posthog instance :cry:
posthog = {
Expand All @@ -262,17 +258,17 @@ describe('SessionRecording', () => {
},
sessionManager: sessionManager,
requestRouter: new RequestRouter({ config } as any),
_addCaptureHook: addCaptureHookMock,
consent: {
isOptedOut(): boolean {
return false
},
} as unknown as ConsentManager,
register_for_session() {},
_internalEventEmitter: simpleEventEmitter,
on: (event, cb) => {
return simpleEventEmitter.on(event, cb)
},
on: jest.fn().mockImplementation((event, cb) => {
const unsubscribe = simpleEventEmitter.on(event, cb)
return removePageviewCaptureHookMock.mockImplementation(unsubscribe)
}),
} as Partial<PostHog> as PostHog

loadScriptMock.mockImplementation((_ph, _path, callback) => {
Expand Down Expand Up @@ -423,21 +419,21 @@ describe('SessionRecording', () => {
sessionRecording.startIfEnabledOrStop()

expect(sessionRecording['_removePageViewCaptureHook']).not.toBeUndefined()
expect(posthog._addCaptureHook).toHaveBeenCalledTimes(1)
expect(posthog.on).toHaveBeenCalledTimes(1)

// calling a second time doesn't add another capture hook
sessionRecording.startIfEnabledOrStop()
expect(posthog._addCaptureHook).toHaveBeenCalledTimes(1)
expect(posthog.on).toHaveBeenCalledTimes(1)
})

it('removes the pageview capture hook on stop', () => {
sessionRecording.startIfEnabledOrStop()
expect(sessionRecording['_removePageViewCaptureHook']).not.toBeUndefined()

expect(removeCaptureHookMock).not.toHaveBeenCalled()
expect(removePageviewCaptureHookMock).not.toHaveBeenCalled()
sessionRecording.stopRecording()

expect(removeCaptureHookMock).toHaveBeenCalledTimes(1)
expect(removePageviewCaptureHookMock).toHaveBeenCalledTimes(1)
expect(sessionRecording['_removePageViewCaptureHook']).toBeUndefined()
})

Expand Down Expand Up @@ -1783,6 +1779,66 @@ describe('SessionRecording', () => {
expect(sessionRecording['_linkedFlagSeen']).toEqual(true)
expect(sessionRecording['status']).toEqual('active')
})

/**
* this is partly a regression test, with a running rrweb,
* if you don't pause while buffering
* the browser can be trapped in an infinite loop of pausing
* while trying to report it is paused 🙈
*/
it('can be paused while waiting for flag', () => {
fakeNavigateTo('https://test.com/blocked')

expect(sessionRecording['_linkedFlag']).toEqual(null)
expect(sessionRecording['_linkedFlagSeen']).toEqual(false)
expect(sessionRecording['status']).toEqual('buffering')

sessionRecording.afterDecideResponse(
makeDecideResponse({
sessionRecording: {
endpoint: '/s/',
linkedFlag: 'the-flag-key',
urlBlocklist: [
{
matching: 'regex',
url: '/blocked',
},
],
},
})
)

expect(sessionRecording['_linkedFlag']).toEqual('the-flag-key')
expect(sessionRecording['_linkedFlagSeen']).toEqual(false)
expect(sessionRecording['status']).toEqual('buffering')
expect(sessionRecording['paused']).toBeUndefined()

const snapshotEvent = {
event: 123,
type: INCREMENTAL_SNAPSHOT_EVENT_TYPE,
data: {
source: 1,
},
timestamp: new Date().getTime(),
}
_emit(snapshotEvent)

expect(sessionRecording['_linkedFlag']).toEqual('the-flag-key')
expect(sessionRecording['_linkedFlagSeen']).toEqual(false)
expect(sessionRecording['status']).toEqual('paused')

sessionRecording.overrideLinkedFlag()

expect(sessionRecording['_linkedFlagSeen']).toEqual(true)
expect(sessionRecording['status']).toEqual('paused')

fakeNavigateTo('https://test.com/allowed')

expect(sessionRecording['status']).toEqual('paused')

_emit(snapshotEvent)
expect(sessionRecording['status']).toEqual('active')
})
})

describe('buffering minimum duration', () => {
Expand Down
31 changes: 31 additions & 0 deletions src/__tests__/utils/event-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,4 +344,35 @@ describe(`event-utils`, () => {
}
})
})

describe('timezones', () => {
it('should compute timezone', () => {
const timezone = Info.timezone()
expect(typeof timezone).toBe('string')
})

it('should compute timezone offset as a number', () => {
const offset = Info.timezoneOffset()
expect(typeof offset).toBe('number')
})
})

describe('browser language', () => {
let languageGetter: jest.SpyInstance

beforeEach(() => {
languageGetter = jest.spyOn(window.navigator, 'language', 'get')
languageGetter.mockReturnValue('pt-BR')
})

it('should compute browser language', () => {
const language = Info.browserLanguage()
expect(language).toBe('pt-BR')
})

it('should compute browser language prefix', () => {
const languagePrefix = Info.browserLanguagePrefix()
expect(languagePrefix).toBe('pt')
})
})
})
16 changes: 9 additions & 7 deletions src/extensions/replay/sessionrecording.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,10 @@ export class SessionRecording {
return 'disabled'
}

if (this._urlBlocked) {
return 'paused'
}

if (!isNullish(this._linkedFlag) && !this._linkedFlagSeen) {
return 'buffering'
}
Expand All @@ -401,10 +405,6 @@ export class SessionRecording {
return 'buffering'
}

if (this._urlBlocked) {
return 'paused'
}

if (isBoolean(this.isSampled)) {
return this.isSampled ? 'sampled' : 'disabled'
} else {
Expand Down Expand Up @@ -502,12 +502,14 @@ export class SessionRecording {
if (isNullish(this._removePageViewCaptureHook)) {
// :TRICKY: rrweb does not capture navigation within SPA-s, so hook into our $pageview events to get access to all events.
// Dropping the initial event is fine (it's always captured by rrweb).
this._removePageViewCaptureHook = this.instance._addCaptureHook((eventName) => {
this._removePageViewCaptureHook = this.instance.on('eventCaptured', (event) => {
// If anything could go wrong here it has the potential to block the main loop,
// so we catch all errors.
try {
if (eventName === '$pageview') {
const href = window ? this._maskUrl(window.location.href) : ''
if (event.event === '$pageview') {
const href = event?.properties.$current_url
? this._maskUrl(event?.properties.$current_url)
: ''
if (!href) {
return
}
Expand Down
17 changes: 16 additions & 1 deletion src/utils/event-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,18 @@ export const Info = {
*/
browserVersion: detectBrowserVersion,

browserLanguage: function (): string {
browserLanguage: function (): string | undefined {
return (
navigator.language || // Any modern browser
(navigator as Record<string, any>).userLanguage // IE11
)
},

browserLanguagePrefix: function (): string | undefined {
const browserLanguage = this.browserLanguage()
return typeof browserLanguage === 'string' ? browserLanguage.split('-')[0] : undefined
},

os: detectOS,

device: detectDevice,
Expand Down Expand Up @@ -209,6 +214,14 @@ export const Info = {
}
},

timezoneOffset: function (): number | undefined {
try {
return new Date().getTimezoneOffset()
} catch {
return undefined
}
},

properties: function (): Properties {
if (!userAgent) {
return {}
Expand All @@ -222,6 +235,7 @@ export const Info = {
$device: Info.device(userAgent),
$device_type: Info.deviceType(userAgent),
$timezone: Info.timezone(),
$timezone_offset: Info.timezoneOffset(),
}),
{
$current_url: location?.href,
Expand All @@ -230,6 +244,7 @@ export const Info = {
$raw_user_agent: userAgent.length > 1000 ? userAgent.substring(0, 997) + '...' : userAgent,
$browser_version: Info.browserVersion(userAgent, navigator.vendor),
$browser_language: Info.browserLanguage(),
$browser_language_prefix: Info.browserLanguagePrefix(),
$screen_height: window?.screen.height,
$screen_width: window?.screen.width,
$viewport_height: window?.innerHeight,
Expand Down

0 comments on commit 45fdae5

Please sign in to comment.