@leanbase-giangnd/js 0.0.4 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@leanbase-giangnd/js",
3
- "version": "0.0.4",
4
- "description": "Leanbase browser SDK - event tracking, autocapture, and session replay",
3
+ "version": "0.0.7",
4
+ "description": "Leanbase bgiangndrowser SDK - event tracking, autocapture, and session replay",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "directory": "packages/leanbase"
@@ -34,15 +34,14 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@posthog/core": "workspace:*",
37
- "@rrweb/record": "2.0.0-alpha.17",
38
37
  "fflate": "^0.4.8"
39
38
  },
40
39
  "devDependencies": {
41
- "@posthog-tooling/tsconfig-base": "workspace:*",
42
40
  "@posthog-tooling/rollup-utils": "workspace:*",
41
+ "@posthog-tooling/tsconfig-base": "workspace:*",
43
42
  "jest": "catalog:",
44
43
  "jest-environment-jsdom": "catalog:",
45
- "rollup": "catalog:",
46
- "rimraf": "^6.0.1"
44
+ "rimraf": "^6.0.1",
45
+ "rollup": "catalog:"
47
46
  }
48
47
  }
@@ -0,0 +1,5 @@
1
+ files in here are intended for the lazy loaded portion of replay
2
+
3
+ you aren't supposed to import them from outside the entrypoint file
4
+
5
+ they could cause an increase in bundle size
@@ -1,5 +1,5 @@
1
- import { CapturedNetworkRequest, LeanbaseConfig, NetworkRecordOptions } from '../../../types'
2
- import { isArray, isBoolean, isFunction, isNullish, isString, isUndefined } from '@posthog/core'
1
+ import { CapturedNetworkRequest, NetworkRecordOptions, PostHogConfig } from '../../../types'
2
+ import { isFunction, isNullish, isString, isUndefined } from '@posthog/core'
3
3
  import { convertToURL } from '../../../utils/request-utils'
4
4
  import { logger } from '../../../utils/logger'
5
5
  import { shouldCaptureValue } from '../../../autocapture-utils'
@@ -15,11 +15,12 @@ export const defaultNetworkOptions: Required<NetworkRecordOptions> = {
15
15
  'beacon',
16
16
  'body',
17
17
  'css',
18
- 'early-hints',
18
+ 'early-hint',
19
19
  'embed',
20
20
  'fetch',
21
21
  'frame',
22
22
  'iframe',
23
+ 'icon',
23
24
  'image',
24
25
  'img',
25
26
  'input',
@@ -91,17 +92,11 @@ const PAYLOAD_CONTENT_DENY_LIST = [
91
92
  const removeAuthorizationHeader = (data: CapturedNetworkRequest): CapturedNetworkRequest => {
92
93
  const headers = data.requestHeaders
93
94
  if (!isNullish(headers)) {
94
- const mutableHeaders: Record<string, any> = isArray(headers)
95
- ? Object.fromEntries(headers as any)
96
- : (headers as any)
97
-
98
- each(Object.keys(mutableHeaders ?? {}), (header) => {
95
+ each(Object.keys(headers ?? {}), (header) => {
99
96
  if (HEADER_DENY_LIST.includes(header.toLowerCase())) {
100
- mutableHeaders[header] = REDACTED
97
+ headers[header] = REDACTED
101
98
  }
102
99
  })
103
-
104
- data.requestHeaders = mutableHeaders as any
105
100
  }
106
101
  return data
107
102
  }
@@ -111,12 +106,12 @@ const POSTHOG_PATHS_TO_IGNORE = ['/s/', '/e/', '/i/']
111
106
  // because calls to PostHog would be reported using a call to PostHog which would be reported....
112
107
  const ignorePostHogPaths = (
113
108
  data: CapturedNetworkRequest,
114
- apiHostConfig: LeanbaseConfig['host']
109
+ apiHostConfig: PostHogConfig['api_host']
115
110
  ): CapturedNetworkRequest | undefined => {
116
111
  const url = convertToURL(data.name)
117
112
 
118
- const host = apiHostConfig || ''
119
- let replaceValue = host.indexOf('http') === 0 ? convertToURL(host)?.pathname : host
113
+ // we need to account for api host config as e.g. pathname could be /ingest/s/ and we want to ignore that
114
+ let replaceValue = apiHostConfig.indexOf('http') === 0 ? convertToURL(apiHostConfig)?.pathname : apiHostConfig
120
115
  if (replaceValue === '/') {
121
116
  replaceValue = ''
122
117
  }
@@ -210,47 +205,44 @@ function scrubPayloads(capturedRequest: CapturedNetworkRequest | undefined): Cap
210
205
  * if someone complains then we'll add an opt-in to let them override it
211
206
  */
212
207
  export const buildNetworkRequestOptions = (
213
- instanceConfig: LeanbaseConfig,
208
+ instanceConfig: PostHogConfig,
214
209
  remoteNetworkOptions: Pick<
215
210
  NetworkRecordOptions,
216
211
  'recordHeaders' | 'recordBody' | 'recordPerformance' | 'payloadHostDenyList'
217
- > = {}
212
+ >
218
213
  ): NetworkRecordOptions => {
219
- const remoteOptions = remoteNetworkOptions || {}
220
214
  const config: NetworkRecordOptions = {
221
215
  payloadSizeLimitBytes: defaultNetworkOptions.payloadSizeLimitBytes,
222
216
  performanceEntryTypeToObserve: [...defaultNetworkOptions.performanceEntryTypeToObserve],
223
217
  payloadHostDenyList: [
224
- ...(remoteOptions.payloadHostDenyList || []),
218
+ ...(remoteNetworkOptions.payloadHostDenyList || []),
225
219
  ...defaultNetworkOptions.payloadHostDenyList,
226
220
  ],
227
221
  }
228
222
  // client can always disable despite remote options
229
- const sessionRecordingConfig = instanceConfig.session_recording || {}
230
- const capturePerformanceConfig = instanceConfig.capture_performance
231
- const userPerformanceOptIn = isBoolean(capturePerformanceConfig)
232
- ? capturePerformanceConfig
233
- : !!capturePerformanceConfig?.network_timing
234
- const canRecordHeaders = sessionRecordingConfig.recordHeaders === true && !!remoteOptions.recordHeaders
235
- const canRecordBody = sessionRecordingConfig.recordBody === true && !!remoteOptions.recordBody
236
- const canRecordPerformance = userPerformanceOptIn && !!remoteOptions.recordPerformance
223
+ const canRecordHeaders =
224
+ instanceConfig.session_recording.recordHeaders === false ? false : remoteNetworkOptions.recordHeaders
225
+ const canRecordBody =
226
+ instanceConfig.session_recording.recordBody === false ? false : remoteNetworkOptions.recordBody
227
+ const canRecordPerformance =
228
+ instanceConfig.capture_performance === false ? false : remoteNetworkOptions.recordPerformance
237
229
 
238
230
  const payloadLimiter = limitPayloadSize(config)
239
231
 
240
232
  const enforcedCleaningFn: NetworkRecordOptions['maskRequestFn'] = (d: CapturedNetworkRequest) =>
241
- payloadLimiter(ignorePostHogPaths(removeAuthorizationHeader(d), instanceConfig.host || ''))
233
+ payloadLimiter(ignorePostHogPaths(removeAuthorizationHeader(d), instanceConfig.api_host))
242
234
 
243
- const hasDeprecatedMaskFunction = isFunction(sessionRecordingConfig.maskNetworkRequestFn)
235
+ const hasDeprecatedMaskFunction = isFunction(instanceConfig.session_recording.maskNetworkRequestFn)
244
236
 
245
- if (hasDeprecatedMaskFunction && isFunction(sessionRecordingConfig.maskCapturedNetworkRequestFn)) {
237
+ if (hasDeprecatedMaskFunction && isFunction(instanceConfig.session_recording.maskCapturedNetworkRequestFn)) {
246
238
  logger.warn(
247
239
  'Both `maskNetworkRequestFn` and `maskCapturedNetworkRequestFn` are defined. `maskNetworkRequestFn` will be ignored.'
248
240
  )
249
241
  }
250
242
 
251
243
  if (hasDeprecatedMaskFunction) {
252
- sessionRecordingConfig.maskCapturedNetworkRequestFn = (data: CapturedNetworkRequest) => {
253
- const cleanedURL = sessionRecordingConfig.maskNetworkRequestFn!({ url: data.name })
244
+ instanceConfig.session_recording.maskCapturedNetworkRequestFn = (data: CapturedNetworkRequest) => {
245
+ const cleanedURL = instanceConfig.session_recording.maskNetworkRequestFn!({ url: data.name })
254
246
  return {
255
247
  ...data,
256
248
  name: cleanedURL?.url,
@@ -258,11 +250,11 @@ export const buildNetworkRequestOptions = (
258
250
  }
259
251
  }
260
252
 
261
- config.maskRequestFn = isFunction(sessionRecordingConfig.maskCapturedNetworkRequestFn)
253
+ config.maskRequestFn = isFunction(instanceConfig.session_recording.maskCapturedNetworkRequestFn)
262
254
  ? (data) => {
263
255
  const cleanedRequest = enforcedCleaningFn(data)
264
256
  return cleanedRequest
265
- ? (sessionRecordingConfig.maskCapturedNetworkRequestFn?.(cleanedRequest) ?? undefined)
257
+ ? (instanceConfig.session_recording.maskCapturedNetworkRequestFn?.(cleanedRequest) ?? undefined)
266
258
  : undefined
267
259
  }
268
260
  : (data) => scrubPayloads(enforcedCleaningFn(data))
@@ -1,5 +1,3 @@
1
- import { record as rrwebRecord } from '@rrweb/record'
2
- import { clampToRange, includes, isBoolean, isNullish, isNumber, isObject, isString, isUndefined } from '@posthog/core'
3
1
  import type { recordOptions, rrwebRecord as rrwebRecordType } from '../types/rrweb'
4
2
  import {
5
3
  type customEvent,
@@ -10,7 +8,6 @@ import {
10
8
  RecordPlugin,
11
9
  } from '../types/rrweb-types'
12
10
  import { buildNetworkRequestOptions } from './config'
13
- import { getRecordNetworkPlugin } from './network-plugin'
14
11
  import {
15
12
  ACTIVE,
16
13
  allMatchSessionRecordingStatus,
@@ -34,16 +31,28 @@ import {
34
31
  } from './triggerMatching'
35
32
  import { estimateSize, INCREMENTAL_SNAPSHOT_EVENT_TYPE, truncateLargeConsoleLogs } from './sessionrecording-utils'
36
33
  import { gzipSync, strFromU8, strToU8 } from 'fflate'
37
- import { window, document, addEventListener } from '../../../utils'
34
+ import { assignableWindow, LazyLoadedSessionRecordingInterface, window, document } from '../../../utils/globals'
35
+ import { addEventListener } from '../../../utils'
38
36
  import { MutationThrottler } from './mutation-throttler'
39
37
  import { createLogger } from '../../../utils/logger'
38
+ import {
39
+ clampToRange,
40
+ includes,
41
+ isBoolean,
42
+ isFunction,
43
+ isNullish,
44
+ isNumber,
45
+ isObject,
46
+ isString,
47
+ isUndefined,
48
+ } from '@posthog/core'
40
49
  import {
41
50
  SESSION_RECORDING_EVENT_TRIGGER_ACTIVATED_SESSION,
42
51
  SESSION_RECORDING_IS_SAMPLED,
43
52
  SESSION_RECORDING_REMOTE_CONFIG,
44
53
  SESSION_RECORDING_URL_TRIGGER_ACTIVATED_SESSION,
45
54
  } from '../../../constants'
46
- import { Leanbase } from '../../../leanbase'
55
+ import { PostHog } from '../../../posthog-core'
47
56
  import {
48
57
  CaptureResult,
49
58
  NetworkRecordOptions,
@@ -118,7 +127,7 @@ const newQueuedEvent = (rrwebMethod: () => void): QueuedRRWebEvent => ({
118
127
  })
119
128
 
120
129
  function getRRWebRecord(): rrwebRecordType | undefined {
121
- return rrwebRecord as unknown as rrwebRecordType
130
+ return assignableWindow?.__PosthogExtensions__?.rrweb?.record
122
131
  }
123
132
 
124
133
  export type compressedFullSnapshotEvent = {
@@ -249,7 +258,7 @@ export function splitBuffer(buffer: SnapshotBuffer, sizeLimit: number = SEVEN_ME
249
258
  }
250
259
  }
251
260
 
252
- export class LazyLoadedSessionRecording {
261
+ export class LazyLoadedSessionRecording implements LazyLoadedSessionRecordingInterface {
253
262
  private _endpoint: string = BASE_ENDPOINT
254
263
  private _mutationThrottler?: MutationThrottler
255
264
  /**
@@ -300,7 +309,7 @@ export class LazyLoadedSessionRecording {
300
309
  }
301
310
 
302
311
  private get _sessionIdleThresholdMilliseconds(): number {
303
- return this._instance.config.session_recording?.session_idle_threshold_ms || RECORDING_IDLE_THRESHOLD_MS
312
+ return this._instance.config.session_recording.session_idle_threshold_ms || RECORDING_IDLE_THRESHOLD_MS
304
313
  }
305
314
 
306
315
  private get _isSampled(): boolean | null {
@@ -328,7 +337,7 @@ export class LazyLoadedSessionRecording {
328
337
  private _samplingSessionListener: (() => void) | undefined = undefined
329
338
  private _forceIdleSessionIdListener: (() => void) | undefined = undefined
330
339
 
331
- constructor(private readonly _instance: Leanbase) {
340
+ constructor(private readonly _instance: PostHog) {
332
341
  // we know there's a sessionManager, so don't need to start without a session id
333
342
  const { sessionId, windowId } = this._sessionManager.checkAndGetSessionAndWindowId()
334
343
  this._sessionId = sessionId
@@ -371,7 +380,7 @@ export class LazyLoadedSessionRecording {
371
380
  }
372
381
 
373
382
  private get _canvasRecording(): { enabled: boolean; fps: number; quality: number } {
374
- const canvasRecording_client_side = this._instance.config.session_recording?.captureCanvas
383
+ const canvasRecording_client_side = this._instance.config.session_recording.captureCanvas
375
384
  const canvasRecording_server_side = this._remoteConfig?.canvasRecording
376
385
 
377
386
  const enabled: boolean =
@@ -407,60 +416,44 @@ export class LazyLoadedSessionRecording {
407
416
  // network payload capture config has three parts
408
417
  // each can be configured server side or client side
409
418
  private get _networkPayloadCapture():
410
- | Pick<NetworkRecordOptions, 'recordHeaders' | 'recordBody' | 'recordPerformance' | 'payloadHostDenyList'>
419
+ | Pick<NetworkRecordOptions, 'recordHeaders' | 'recordBody' | 'recordPerformance'>
411
420
  | undefined {
412
421
  const networkPayloadCapture_server_side = this._remoteConfig?.networkPayloadCapture
413
422
  const networkPayloadCapture_client_side = {
414
423
  recordHeaders: this._instance.config.session_recording?.recordHeaders,
415
424
  recordBody: this._instance.config.session_recording?.recordBody,
416
425
  }
417
- const headersOptIn = networkPayloadCapture_client_side?.recordHeaders === true
418
- const bodyOptIn = networkPayloadCapture_client_side?.recordBody === true
419
- const clientPerformanceConfig = this._instance.config.capture_performance
420
- const clientPerformanceOptIn = isObject(clientPerformanceConfig)
421
- ? !!clientPerformanceConfig.network_timing
422
- : !!clientPerformanceConfig
423
- const serverAllowsHeaders = networkPayloadCapture_server_side?.recordHeaders ?? true
424
- const serverAllowsBody = networkPayloadCapture_server_side?.recordBody ?? true
425
- const capturePerfResponse = networkPayloadCapture_server_side?.capturePerformance
426
- const serverAllowsPerformance = (() => {
427
- if (isObject(capturePerfResponse)) {
428
- return !!capturePerfResponse.network_timing
429
- }
430
- return capturePerfResponse ?? true
431
- })()
432
-
433
- const headersEnabled = headersOptIn && serverAllowsHeaders
434
- const bodyEnabled = bodyOptIn && serverAllowsBody
435
- const networkTimingEnabled = clientPerformanceOptIn && serverAllowsPerformance
436
-
437
- if (!headersEnabled && !bodyEnabled && !networkTimingEnabled) {
438
- return undefined
439
- }
440
-
441
- return {
442
- recordHeaders: headersEnabled,
443
- recordBody: bodyEnabled,
444
- recordPerformance: networkTimingEnabled,
445
- payloadHostDenyList: networkPayloadCapture_server_side?.payloadHostDenyList,
446
- }
426
+ const headersEnabled =
427
+ networkPayloadCapture_client_side?.recordHeaders || networkPayloadCapture_server_side?.recordHeaders
428
+ const bodyEnabled =
429
+ networkPayloadCapture_client_side?.recordBody || networkPayloadCapture_server_side?.recordBody
430
+ const clientConfigForPerformanceCapture = isObject(this._instance.config.capture_performance)
431
+ ? this._instance.config.capture_performance.network_timing
432
+ : this._instance.config.capture_performance
433
+ const networkTimingEnabled = !!(isBoolean(clientConfigForPerformanceCapture)
434
+ ? clientConfigForPerformanceCapture
435
+ : networkPayloadCapture_server_side?.capturePerformance)
436
+
437
+ return headersEnabled || bodyEnabled || networkTimingEnabled
438
+ ? { recordHeaders: headersEnabled, recordBody: bodyEnabled, recordPerformance: networkTimingEnabled }
439
+ : undefined
447
440
  }
448
441
 
449
442
  private _gatherRRWebPlugins() {
450
443
  const plugins: RecordPlugin[] = []
451
444
 
452
- if (this._isConsoleLogCaptureEnabled) {
453
- logger.info('Console log capture requested but console plugin is not bundled in this build yet.')
445
+ const recordConsolePlugin = assignableWindow.__PosthogExtensions__?.rrwebPlugins?.getRecordConsolePlugin
446
+ if (recordConsolePlugin && this._isConsoleLogCaptureEnabled) {
447
+ plugins.push(recordConsolePlugin())
454
448
  }
455
449
 
456
- if (this._networkPayloadCapture) {
450
+ const networkPlugin = assignableWindow.__PosthogExtensions__?.rrwebPlugins?.getRecordNetworkPlugin
451
+ if (!!this._networkPayloadCapture && isFunction(networkPlugin)) {
457
452
  const canRecordNetwork = !isLocalhost() || this._forceAllowLocalhostNetworkCapture
458
453
 
459
454
  if (canRecordNetwork) {
460
455
  plugins.push(
461
- getRecordNetworkPlugin(
462
- buildNetworkRequestOptions(this._instance.config, this._networkPayloadCapture)
463
- )
456
+ networkPlugin(buildNetworkRequestOptions(this._instance.config, this._networkPayloadCapture))
464
457
  )
465
458
  } else {
466
459
  logger.info('NetworkCapture not started because we are on localhost.')
@@ -471,7 +464,7 @@ export class LazyLoadedSessionRecording {
471
464
  }
472
465
 
473
466
  private _maskUrl(url: string): string | undefined {
474
- const userSessionRecordingOptions = this._instance.config.session_recording || {}
467
+ const userSessionRecordingOptions = this._instance.config.session_recording
475
468
 
476
469
  if (userSessionRecordingOptions.maskNetworkRequestFn) {
477
470
  let networkRequest: NetworkRequest | null | undefined = {
@@ -673,8 +666,8 @@ export class LazyLoadedSessionRecording {
673
666
  this._statusMatcher = allMatchSessionRecordingStatus
674
667
  this._triggerMatching = new AndTriggerMatching([this._eventTriggerMatching, this._urlTriggerMatching])
675
668
  }
676
- this._instance.registerForSession({
677
- $sdk_debug_replay_remote_trigger_matching_config: config?.triggerMatchType ?? null,
669
+ this._instance.register_for_session({
670
+ $sdk_debug_replay_remote_trigger_matching_config: config?.triggerMatchType,
678
671
  })
679
672
 
680
673
  this._urlTriggerMatching.onConfig(config)
@@ -877,7 +870,7 @@ export class LazyLoadedSessionRecording {
877
870
  }
878
871
 
879
872
  const eventToSend =
880
- (this._instance.config.session_recording?.compress_events ?? true) ? compressEvent(event) : event
873
+ (this._instance.config.session_recording.compress_events ?? true) ? compressEvent(event) : event
881
874
  const size = estimateSize(eventToSend)
882
875
 
883
876
  const properties = {
@@ -1020,25 +1013,36 @@ export class LazyLoadedSessionRecording {
1020
1013
  }
1021
1014
 
1022
1015
  private _captureSnapshot(properties: Properties) {
1023
- // :TRICKY: Make sure we batch these requests, use a custom endpoint and don't truncate the strings.
1024
- this._instance.capture('$snapshot', properties, {
1025
- _url: this._snapshotUrl(),
1016
+ // Send snapshots immediately via the stateless immediate path so they are
1017
+ // not mixed into general event batches. This ensures the client will
1018
+ // choose the snapshot-only endpoint (`/s/`). Fall back to normal
1019
+ // capture if the immediate API isn't available at runtime.
1020
+ const opts = {
1026
1021
  _noTruncate: true,
1027
1022
  _batchKey: SESSION_RECORDING_BATCH_KEY,
1028
1023
  skip_client_rate_limiting: true,
1029
- })
1030
- }
1024
+ }
1031
1025
 
1032
- private _snapshotUrl(): string {
1033
- const host = this._instance.config.host || ''
1034
1026
  try {
1035
- // eslint-disable-next-line compat/compat
1036
- return new URL(this._endpoint, host).href
1037
- } catch {
1038
- const normalizedHost = host.endsWith('/') ? host.slice(0, -1) : host
1039
- const normalizedEndpoint = this._endpoint.startsWith('/') ? this._endpoint.slice(1) : this._endpoint
1040
- return `${normalizedHost}/${normalizedEndpoint}`
1027
+ const maybeCaptureStatelessImmediate = (this._instance as any).captureStatelessImmediate
1028
+ if (isFunction(maybeCaptureStatelessImmediate)) {
1029
+ const distinctId = (this._instance as any).getDistinctId?.() || undefined
1030
+ // captureStatelessImmediate expects (distinctId, event, properties, options)
1031
+ ;(this._instance as any).captureStatelessImmediate(distinctId, '$snapshot', properties, opts)
1032
+ return
1033
+ }
1034
+ } catch (e) {
1035
+ // if anything goes wrong, fall through to the safe capture path below
1036
+ logger.error('Failed to send snapshot via stateless immediate path, falling back to capture', e)
1041
1037
  }
1038
+
1039
+ // :TRICKY: Fallback - use the standard capture path. Keep the explicit
1040
+ // _url for compatibility with environments that expect a pre-computed
1041
+ // endpoint, even though the immediate path above is preferred.
1042
+ this._instance.capture('$snapshot', properties, {
1043
+ _url: this._instance.requestRouter.endpointFor('api', this._endpoint),
1044
+ ...opts,
1045
+ })
1042
1046
  }
1043
1047
 
1044
1048
  private get _sessionDuration(): number | null {
@@ -1099,7 +1103,7 @@ export class LazyLoadedSessionRecording {
1099
1103
  }
1100
1104
 
1101
1105
  private _reportStarted(startReason: SessionStartReason, tagPayload?: Record<string, any>) {
1102
- this._instance.registerForSession({
1106
+ this._instance.register_for_session({
1103
1107
  $session_recording_start_reason: startReason,
1104
1108
  })
1105
1109
  logger.info(startReason.replace('_', ' '), tagPayload)
@@ -1333,8 +1337,8 @@ export class LazyLoadedSessionRecording {
1333
1337
  this._mutationThrottler =
1334
1338
  this._mutationThrottler ??
1335
1339
  new MutationThrottler(rrwebRecord, {
1336
- refillRate: this._instance.config.session_recording?.__mutationThrottlerRefillRate,
1337
- bucketSize: this._instance.config.session_recording?.__mutationThrottlerBucketSize,
1340
+ refillRate: this._instance.config.session_recording.__mutationThrottlerRefillRate,
1341
+ bucketSize: this._instance.config.session_recording.__mutationThrottlerBucketSize,
1338
1342
  onBlockedNode: (id, node) => {
1339
1343
  const message = `Too many mutations on node '${id}'. Rate limiting. This could be due to SVG animations or something similar`
1340
1344
  logger.info(message, {
@@ -16,11 +16,8 @@ export class MutationThrottler {
16
16
  onBlockedNode?: (id: number, node: Node | null) => void
17
17
  } = {}
18
18
  ) {
19
- const configuredBucketSize = this._options.bucketSize ?? 100
20
- const effectiveBucketSize = Math.max(configuredBucketSize - 1, 1)
21
-
22
19
  this._rateLimiter = new BucketedRateLimiter({
23
- bucketSize: effectiveBucketSize,
20
+ bucketSize: this._options.bucketSize ?? 100,
24
21
  refillRate: this._options.refillRate ?? 10,
25
22
  refillInterval: 1000, // one second
26
23
  _onBucketRateLimited: this._onNodeRateLimited,
@@ -12,6 +12,7 @@
12
12
  import type { IWindow, listenerHandler, RecordPlugin } from '../types/rrweb-types'
13
13
  import { CapturedNetworkRequest, Headers, InitiatorType, NetworkRecordOptions } from '../../../types'
14
14
  import { isArray, isBoolean, isFormData, isNull, isNullish, isString, isUndefined, isObject } from '@posthog/core'
15
+ import { isDocument } from '../../../utils/type-utils'
15
16
  import { createLogger } from '../../../utils/logger'
16
17
  import { formDataToQuery } from '../../../utils/request-utils'
17
18
  import { patch } from '../rrweb-plugins/patch'
@@ -45,10 +46,6 @@ export function findLast<T>(array: Array<T>, predicate: (value: T) => boolean):
45
46
  return undefined
46
47
  }
47
48
 
48
- function isDocument(value: any): value is Document {
49
- return !!value && typeof value === 'object' && 'nodeType' in value && (value as any).nodeType === 9
50
- }
51
-
52
49
  function initPerformanceObserver(cb: networkCallback, win: IWindow, options: Required<NetworkRecordOptions>) {
53
50
  // if we are only observing timings then we could have a single observer for all types, with buffer true,
54
51
  // but we are going to filter by initiatorType _if we are wrapping fetch and xhr as the wrapped functions
@@ -671,16 +668,12 @@ function initNetworkObserver(
671
668
  fetchObserver = initFetchObserver(cb, win, networkOptions)
672
669
  }
673
670
 
674
- const teardown: listenerHandler = () => {
671
+ initialisedHandler = () => {
675
672
  performanceObserver()
676
673
  xhrObserver()
677
674
  fetchObserver()
678
- // allow future observers to initialize after cleanup
679
- initialisedHandler = null
680
675
  }
681
-
682
- initialisedHandler = teardown
683
- return teardown
676
+ return initialisedHandler
684
677
  }
685
678
 
686
679
  // use the plugin name so that when this functionality is adopted into rrweb
@@ -2,10 +2,10 @@ import {
2
2
  SESSION_RECORDING_EVENT_TRIGGER_ACTIVATED_SESSION,
3
3
  SESSION_RECORDING_URL_TRIGGER_ACTIVATED_SESSION,
4
4
  } from '../../../constants'
5
- import { Leanbase } from '../../../leanbase'
6
- import { RemoteConfig, SessionRecordingPersistedConfig, SessionRecordingUrlTrigger } from '../../../types'
5
+ import { PostHog } from '../../../posthog-core'
6
+ import { FlagVariant, RemoteConfig, SessionRecordingPersistedConfig, SessionRecordingUrlTrigger } from '../../../types'
7
7
  import { isNullish, isBoolean, isString, isObject } from '@posthog/core'
8
- import { window } from '../../../utils'
8
+ import { window } from '../../../utils/globals'
9
9
 
10
10
  export const DISABLED = 'disabled'
11
11
  export const SAMPLED = 'sampled'
@@ -133,7 +133,7 @@ export class URLTriggerMatching implements TriggerStatusMatching {
133
133
 
134
134
  urlBlocked: boolean = false
135
135
 
136
- constructor(private readonly _instance: Leanbase) {}
136
+ constructor(private readonly _instance: PostHog) {}
137
137
 
138
138
  onConfig(config: ReplayConfigType) {
139
139
  this._urlTriggers =
@@ -172,7 +172,7 @@ export class URLTriggerMatching implements TriggerStatusMatching {
172
172
  const eitherIsPending = urlTriggerStatus === TRIGGER_PENDING
173
173
 
174
174
  const result = eitherIsActivated ? TRIGGER_ACTIVATED : eitherIsPending ? TRIGGER_PENDING : TRIGGER_DISABLED
175
- this._instance.registerForSession({
175
+ this._instance.register_for_session({
176
176
  $sdk_debug_replay_url_trigger_status: result,
177
177
  })
178
178
  return result
@@ -212,10 +212,10 @@ export class URLTriggerMatching implements TriggerStatusMatching {
212
212
  }
213
213
 
214
214
  export class LinkedFlagMatching implements TriggerStatusMatching {
215
- linkedFlag: string | { flag: string; variant: string } | null = null
215
+ linkedFlag: string | FlagVariant | null = null
216
216
  linkedFlagSeen: boolean = false
217
217
  private _flagListenerCleanup: () => void = () => {}
218
- constructor(private readonly _instance: Leanbase) {}
218
+ constructor(private readonly _instance: PostHog) {}
219
219
 
220
220
  triggerStatus(): TriggerStatus {
221
221
  let result = TRIGGER_PENDING
@@ -225,7 +225,7 @@ export class LinkedFlagMatching implements TriggerStatusMatching {
225
225
  if (this.linkedFlagSeen) {
226
226
  result = TRIGGER_ACTIVATED
227
227
  }
228
- this._instance.registerForSession({
228
+ this._instance.register_for_session({
229
229
  $sdk_debug_replay_linked_flag_trigger_status: result,
230
230
  })
231
231
  return result
@@ -242,11 +242,11 @@ export class LinkedFlagMatching implements TriggerStatusMatching {
242
242
  if (!isNullish(this.linkedFlag) && !this.linkedFlagSeen) {
243
243
  const linkedFlag = isString(this.linkedFlag) ? this.linkedFlag : this.linkedFlag.flag
244
244
  const linkedVariant = isString(this.linkedFlag) ? null : this.linkedFlag.variant
245
- this._flagListenerCleanup = this._instance.onFeatureFlags((flags) => {
246
- const flagIsPresent = isObject(flags) && linkedFlag in (flags as any)
245
+ this._flagListenerCleanup = this._instance.onFeatureFlags((_flags: any, variants: any) => {
246
+ const flagIsPresent = isObject(variants) && linkedFlag in variants
247
247
  let linkedFlagMatches = false
248
248
  if (flagIsPresent) {
249
- const variantForFlagKey = (flags as any)[linkedFlag]
249
+ const variantForFlagKey = variants[linkedFlag]
250
250
  if (isBoolean(variantForFlagKey)) {
251
251
  linkedFlagMatches = variantForFlagKey === true
252
252
  } else if (linkedVariant) {
@@ -279,7 +279,7 @@ export class LinkedFlagMatching implements TriggerStatusMatching {
279
279
  export class EventTriggerMatching implements TriggerStatusMatching {
280
280
  _eventTriggers: string[] = []
281
281
 
282
- constructor(private readonly _instance: Leanbase) {}
282
+ constructor(private readonly _instance: PostHog) {}
283
283
 
284
284
  onConfig(config: ReplayConfigType) {
285
285
  this._eventTriggers =
@@ -314,7 +314,7 @@ export class EventTriggerMatching implements TriggerStatusMatching {
314
314
  : eventTriggerStatus === TRIGGER_PENDING
315
315
  ? TRIGGER_PENDING
316
316
  : TRIGGER_DISABLED
317
- this._instance.registerForSession({
317
+ this._instance.register_for_session({
318
318
  $sdk_debug_replay_event_trigger_status: result,
319
319
  })
320
320
  return result
@@ -1,3 +1,6 @@
1
+ // import { patch } from 'rrweb/typings/utils'
2
+ // copied from https://github.com/rrweb-io/rrweb/blob/8aea5b00a4dfe5a6f59bd2ae72bb624f45e51e81/packages/rrweb/src/utils.ts#L129
3
+ // which was copied from https://github.com/getsentry/sentry-javascript/blob/b2109071975af8bf0316d3b5b38f519bdaf5dc15/packages/utils/src/object.ts
1
4
  import { isFunction } from '@posthog/core'
2
5
 
3
6
  export function patch(
@@ -15,6 +18,8 @@ export function patch(
15
18
  const original = source[name] as () => unknown
16
19
  const wrapped = replacement(original)
17
20
 
21
+ // Make sure it's a function first, as we need to attach an empty prototype for `defineProperties` to work
22
+ // otherwise it'll throw "TypeError: Object.defineProperties called on non-object"
18
23
  if (isFunction(wrapped)) {
19
24
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
20
25
  wrapped.prototype = wrapped.prototype || {}
@@ -35,5 +40,7 @@ export function patch(
35
40
  return () => {
36
41
  //
37
42
  }
43
+ // This can throw if multiple fill happens on a global object like XMLHttpRequest
44
+ // Fixes https://github.com/getsentry/sentry-javascript/issues/2043
38
45
  }
39
46
  }