Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ const originalLocation = window!.location

function fakeNavigateTo(href: string) {
delete (window as any).location
// @ts-expect-error this is a test, it's safe to write to location like this
window!.location = { href } as Location
}

Expand Down Expand Up @@ -290,6 +291,7 @@ describe('Lazy SessionRecording', () => {
})

afterEach(() => {
// @ts-expect-error this is a test, it's safe to write to location like this
window!.location = originalLocation
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ describe('SessionRecording', () => {
})

afterEach(() => {
// @ts-expect-error this is a test, it's safe to write to location like this
window!.location = originalLocation
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -685,7 +685,6 @@ export class LazyLoadedSessionRecording implements LazyLoadedSessionRecordingInt
})

this._makeSamplingDecision(this.sessionId)
this._receivedFlags = true
this._startRecorder()

// calling addEventListener multiple times is safe and will not add duplicates
Expand Down Expand Up @@ -826,7 +825,6 @@ export class LazyLoadedSessionRecording implements LazyLoadedSessionRecordingInt
// we always start trigger pending so need to wait for flags before we know if we're really pending
if (
rawEvent.type === EventType.FullSnapshot &&
this._receivedFlags &&
this._triggerMatching.triggerStatus(this.sessionId) === TRIGGER_PENDING
) {
this._clearBufferBeforeMostRecentMeta()
Expand Down Expand Up @@ -881,14 +879,9 @@ export class LazyLoadedSessionRecording implements LazyLoadedSessionRecordingInt
}

get status(): SessionRecordingStatus {
// todo: this check should move into the status matcher
if (!this._receivedFlags) {
return BUFFERING
}

return this._statusMatcher({
// can't get here without recording being enabled...
receivedFlags: this._receivedFlags,
receivedFlags: true,
isRecordingEnabled: true,
// things that do still vary
isSampled: this._isSampled,
Expand Down
17 changes: 15 additions & 2 deletions packages/browser/src/extensions/replay/sessionrecording-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
PostHogExtensionKind,
window,
} from '../../utils/globals'
import { LAZY_LOADING, SessionRecordingStatus, TriggerType } from './triggerMatching'
import { DISABLED, LAZY_LOADING, SessionRecordingStatus, TriggerType } from './triggerMatching'

const LOGGER_PREFIX = '[SessionRecording]'
const logger = createLogger(LOGGER_PREFIX)
Expand All @@ -23,6 +23,8 @@ const logger = createLogger(LOGGER_PREFIX)
export class SessionRecordingWrapper {
_forceAllowLocalhostNetworkCapture: boolean = false

private _receivedFlags: boolean = false

private _persistFlagsOnSessionListener: (() => void) | undefined = undefined
private _lazyLoadedSessionRecording: LazyLoadedSessionRecordingInterface | undefined

Expand All @@ -35,6 +37,10 @@ export class SessionRecordingWrapper {
* once a flags response is received status can be disabled, active or sampled
*/
get status(): SessionRecordingStatus {
if (this._receivedFlags && !this._isRecordingEnabled) {
return DISABLED
}

return this._lazyLoadedSessionRecording?.status || LAZY_LOADING
}

Expand Down Expand Up @@ -125,7 +131,9 @@ export class SessionRecordingWrapper {
const persistence = this._instance.persistence

const persistResponse = () => {
const sessionRecordingConfigResponse = response.sessionRecording
const sessionRecordingConfigResponse =
response.sessionRecording === false ? undefined : response.sessionRecording
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wild that we've never represented that sessionRecording can be false
it's "safe" to just treat it as undefined when false


const receivedSampleRate = sessionRecordingConfigResponse?.sampleRate

const parsedSampleRate = isNullish(receivedSampleRate) ? null : parseFloat(receivedSampleRate)
Expand Down Expand Up @@ -175,8 +183,13 @@ export class SessionRecordingWrapper {
logger.info('skipping remote config with no sessionRecording', response)
return
}
if (response.sessionRecording === false) {
// remotely disabled
return
}

this._persistRemoteConfig(response)
this._receivedFlags = true
// TODO how do we send a custom message with the received remote config like we used to for debug
this.startIfEnabledOrStop()
}
Expand Down
70 changes: 37 additions & 33 deletions packages/browser/src/extensions/replay/sessionrecording.ts
Original file line number Diff line number Diff line change
Expand Up @@ -630,37 +630,40 @@ export class SessionRecording {

onRemoteConfig(response: RemoteConfig) {
this.tryAddCustomEvent('$remote_config_received', response)
this._receivedFlags = true

this._persistRemoteConfig(response)

if (response.sessionRecording?.endpoint) {
this._endpoint = response.sessionRecording?.endpoint
}
if (response.sessionRecording) {
if (response.sessionRecording?.endpoint) {
this._endpoint = response.sessionRecording?.endpoint
}

this._setupSampling()
this._setupSampling()

if (response.sessionRecording?.triggerMatchType === 'any') {
this._statusMatcher = anyMatchSessionRecordingStatus
this._triggerMatching = new OrTriggerMatching([this._eventTriggerMatching, this._urlTriggerMatching])
} else {
// either the setting is "ALL"
// or we default to the most restrictive
this._statusMatcher = allMatchSessionRecordingStatus
this._triggerMatching = new AndTriggerMatching([this._eventTriggerMatching, this._urlTriggerMatching])
}
this._instance.register_for_session({
$sdk_debug_replay_remote_trigger_matching_config: response.sessionRecording?.triggerMatchType,
})
if (response.sessionRecording?.triggerMatchType === 'any') {
this._statusMatcher = anyMatchSessionRecordingStatus
this._triggerMatching = new OrTriggerMatching([this._eventTriggerMatching, this._urlTriggerMatching])
} else {
// either the setting is "ALL"
// or we default to the most restrictive
this._statusMatcher = allMatchSessionRecordingStatus
this._triggerMatching = new AndTriggerMatching([this._eventTriggerMatching, this._urlTriggerMatching])
}
this._instance.register_for_session({
$sdk_debug_replay_remote_trigger_matching_config: response.sessionRecording?.triggerMatchType,
})

this._urlTriggerMatching.onConfig(response)
this._eventTriggerMatching.onConfig(response)
this._linkedFlagMatching.onConfig(response, (flag, variant) => {
this._reportStarted('linked_flag_matched', {
flag,
variant,
this._urlTriggerMatching.onConfig(response)
this._eventTriggerMatching.onConfig(response)
this._linkedFlagMatching.onConfig(response, (flag, variant) => {
this._reportStarted('linked_flag_matched', {
flag,
variant,
})
})
})
}

this._receivedFlags = true
this.startIfEnabledOrStop()
}

Expand All @@ -680,33 +683,34 @@ export class SessionRecording {
const persistence = this._instance.persistence

const persistResponse = () => {
const receivedSampleRate = response.sessionRecording?.sampleRate
const receivedConfig = response.sessionRecording === false ? undefined : response.sessionRecording
const receivedSampleRate = receivedConfig?.sampleRate

const parsedSampleRate = isNullish(receivedSampleRate) ? null : parseFloat(receivedSampleRate)
if (isNullish(parsedSampleRate)) {
this._resetSampling()
}

const receivedMinimumDuration = response.sessionRecording?.minimumDurationMilliseconds
const receivedMinimumDuration = receivedConfig?.minimumDurationMilliseconds

persistence.register({
[SESSION_RECORDING_ENABLED_SERVER_SIDE]: !!response['sessionRecording'],
[CONSOLE_LOG_RECORDING_ENABLED_SERVER_SIDE]: response.sessionRecording?.consoleLogRecordingEnabled,
[CONSOLE_LOG_RECORDING_ENABLED_SERVER_SIDE]: receivedConfig?.consoleLogRecordingEnabled,
[SESSION_RECORDING_NETWORK_PAYLOAD_CAPTURE]: {
capturePerformance: response.capturePerformance,
...response.sessionRecording?.networkPayloadCapture,
...receivedConfig?.networkPayloadCapture,
},
[SESSION_RECORDING_MASKING]: response.sessionRecording?.masking,
[SESSION_RECORDING_MASKING]: receivedConfig?.masking,
[SESSION_RECORDING_CANVAS_RECORDING]: {
enabled: response.sessionRecording?.recordCanvas,
fps: response.sessionRecording?.canvasFps,
quality: response.sessionRecording?.canvasQuality,
enabled: receivedConfig?.recordCanvas,
fps: receivedConfig?.canvasFps,
quality: receivedConfig?.canvasQuality,
},
[SESSION_RECORDING_SAMPLE_RATE]: parsedSampleRate,
[SESSION_RECORDING_MINIMUM_DURATION]: isUndefined(receivedMinimumDuration)
? null
: receivedMinimumDuration,
[SESSION_RECORDING_SCRIPT_CONFIG]: response.sessionRecording?.scriptConfig,
[SESSION_RECORDING_SCRIPT_CONFIG]: receivedConfig?.scriptConfig,
})
}

Expand Down
24 changes: 20 additions & 4 deletions packages/browser/src/extensions/replay/triggerMatching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,17 @@ export class URLTriggerMatching implements TriggerStatusMatching {

onConfig(config: ReplayConfigType) {
this._urlTriggers =
(isEagerLoadedConfig(config) ? config.sessionRecording?.urlTriggers : config?.urlTriggers) || []
(isEagerLoadedConfig(config)
? isObject(config.sessionRecording)
? config.sessionRecording?.urlTriggers
: []
: config?.urlTriggers) || []
this._urlBlocklist =
(isEagerLoadedConfig(config) ? config.sessionRecording?.urlBlocklist : config?.urlBlocklist) || []
(isEagerLoadedConfig(config)
? isObject(config.sessionRecording)
? config.sessionRecording?.urlBlocklist
: []
: config?.urlBlocklist) || []
}

/**
Expand Down Expand Up @@ -225,7 +233,11 @@ export class LinkedFlagMatching implements TriggerStatusMatching {

onConfig(config: ReplayConfigType, onStarted: (flag: string, variant: string | null) => void) {
this.linkedFlag =
(isEagerLoadedConfig(config) ? config.sessionRecording?.linkedFlag : config?.linkedFlag) || null
(isEagerLoadedConfig(config)
? isObject(config.sessionRecording)
? config.sessionRecording?.linkedFlag
: null
: config?.linkedFlag) || null

if (!isNullish(this.linkedFlag) && !this.linkedFlagSeen) {
const linkedFlag = isString(this.linkedFlag) ? this.linkedFlag : this.linkedFlag.flag
Expand Down Expand Up @@ -271,7 +283,11 @@ export class EventTriggerMatching implements TriggerStatusMatching {

onConfig(config: ReplayConfigType) {
this._eventTriggers =
(isEagerLoadedConfig(config) ? config.sessionRecording?.eventTriggers : config?.eventTriggers) || []
(isEagerLoadedConfig(config)
? isObject(config.sessionRecording)
? config.sessionRecording?.eventTriggers
: []
: config?.eventTriggers) || []
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1502,7 +1502,7 @@ export interface RemoteConfig {
/**
* Session recording configuration options
*/
sessionRecording?: SessionRecordingRemoteConfig
sessionRecording?: SessionRecordingRemoteConfig | false

/**
* Whether surveys are enabled
Expand Down
Loading