@leanbase-giangnd/js 0.0.7 → 0.1.1

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.
@@ -1,28 +1,29 @@
1
1
  import { SESSION_RECORDING_IS_SAMPLED, SESSION_RECORDING_REMOTE_CONFIG } from '../../constants'
2
- import { PostHog } from '../../posthog-core'
2
+ import { Leanbase } from '../../leanbase'
3
3
  import { Properties, RemoteConfig, SessionRecordingPersistedConfig, SessionStartReason } from '../../types'
4
4
  import { type eventWithTime } from './types/rrweb-types'
5
5
 
6
6
  import { isNullish, isUndefined } from '@posthog/core'
7
- import { createLogger } from '../../utils/logger'
8
- import {
9
- assignableWindow,
10
- LazyLoadedSessionRecordingInterface,
11
- PostHogExtensionKind,
12
- window,
13
- } from '../../utils/globals'
7
+ import { logger } from '../../leanbase-logger'
8
+ import { window } from '../../utils'
9
+ import { LazyLoadedSessionRecording } from './external/lazy-loaded-session-recorder'
14
10
  import { DISABLED, LAZY_LOADING, SessionRecordingStatus, TriggerType } from './external/triggerMatching'
15
11
 
16
12
  const LOGGER_PREFIX = '[SessionRecording]'
17
- const logger = createLogger(LOGGER_PREFIX)
13
+ const log = {
14
+ info: (...args: any[]) => logger.info(LOGGER_PREFIX, ...args),
15
+ warn: (...args: any[]) => logger.warn(LOGGER_PREFIX, ...args),
16
+ error: (...args: any[]) => logger.error(LOGGER_PREFIX, ...args),
17
+ }
18
18
 
19
19
  export class SessionRecording {
20
20
  _forceAllowLocalhostNetworkCapture: boolean = false
21
21
 
22
22
  private _receivedFlags: boolean = false
23
+ private _serverRecordingEnabled: boolean = false
23
24
 
24
25
  private _persistFlagsOnSessionListener: (() => void) | undefined = undefined
25
- private _lazyLoadedSessionRecording: LazyLoadedSessionRecordingInterface | undefined
26
+ private _lazyLoadedSessionRecording: LazyLoadedSessionRecording | undefined
26
27
 
27
28
  public get started(): boolean {
28
29
  return !!this._lazyLoadedSessionRecording?.isStarted
@@ -44,9 +45,9 @@ export class SessionRecording {
44
45
  return LAZY_LOADING
45
46
  }
46
47
 
47
- constructor(private readonly _instance: PostHog) {
48
+ constructor(private readonly _instance: Leanbase) {
48
49
  if (!this._instance.sessionManager) {
49
- logger.error('started without valid sessionManager')
50
+ log.error('started without valid sessionManager')
50
51
  throw new Error(LOGGER_PREFIX + ' started without valid sessionManager. This is a bug.')
51
52
  }
52
53
 
@@ -58,7 +59,7 @@ export class SessionRecording {
58
59
  private get _isRecordingEnabled() {
59
60
  const enabled_server_side = !!this._instance.get_property(SESSION_RECORDING_REMOTE_CONFIG)?.enabled
60
61
  const enabled_client_side = !this._instance.config.disable_session_recording
61
- const isDisabled = this._instance.config.disable_session_recording || !!this._instance.consent?.isOptedOut?.()
62
+ const isDisabled = this._instance.config.disable_session_recording || this._instance.consent?.isOptedOut?.()
62
63
  return window && enabled_server_side && enabled_client_side && !isDisabled
63
64
  }
64
65
 
@@ -67,17 +68,10 @@ export class SessionRecording {
67
68
  return
68
69
  }
69
70
 
70
- // According to the rrweb docs, rrweb is not supported on IE11 and below:
71
- // "rrweb does not support IE11 and below because it uses the MutationObserver API, which was supported by these browsers."
72
- // https://github.com/rrweb-io/rrweb/blob/master/guide.md#compatibility-note
73
- //
74
- // However, MutationObserver does exist on IE11, it just doesn't work well and does not detect all changes.
75
- // Instead, when we load "recorder.js", the first JS error is about "Object.assign" and "Array.from" being undefined.
76
- // Thus instead of MutationObserver, we look for this function and block recording if it's undefined.
77
71
  const canRunReplay = !isUndefined(Object.assign) && !isUndefined(Array.from)
78
72
  if (this._isRecordingEnabled && canRunReplay) {
79
73
  this._lazyLoadAndStart(startReason)
80
- logger.info('starting')
74
+ log.info('starting')
81
75
  } else {
82
76
  this.stopRecording()
83
77
  }
@@ -97,25 +91,7 @@ export class SessionRecording {
97
91
  return
98
92
  }
99
93
 
100
- // If recorder.js is already loaded (if array.full.js snippet is used or posthog-js/dist/recorder is
101
- // imported), don't load the script. Otherwise, remotely import recorder.js from cdn since it hasn't been loaded.
102
- if (
103
- !assignableWindow?.__PosthogExtensions__?.rrweb?.record ||
104
- !assignableWindow.__PosthogExtensions__?.initSessionRecording
105
- ) {
106
- assignableWindow.__PosthogExtensions__?.loadExternalDependency?.(
107
- this._instance,
108
- this._scriptName,
109
- (err) => {
110
- if (err) {
111
- return logger.error('could not load recorder', err)
112
- }
113
- this._onScriptLoaded(startReason)
114
- }
115
- )
116
- } else {
117
- this._onScriptLoaded(startReason)
118
- }
94
+ this._onScriptLoaded(startReason)
119
95
  }
120
96
 
121
97
  stopRecording() {
@@ -179,20 +155,28 @@ export class SessionRecording {
179
155
  }
180
156
  }
181
157
 
158
+ private _clearRemoteConfig() {
159
+ this._instance.persistence?.unregister(SESSION_RECORDING_REMOTE_CONFIG)
160
+ this._resetSampling()
161
+ }
162
+
182
163
  onRemoteConfig(response: RemoteConfig) {
183
164
  if (!('sessionRecording' in response)) {
184
165
  // if sessionRecording is not in the response, we do nothing
185
- logger.info('skipping remote config with no sessionRecording', response)
166
+ log.info('skipping remote config with no sessionRecording', response)
186
167
  return
187
168
  }
169
+ this._receivedFlags = true
170
+
188
171
  if (response.sessionRecording === false) {
189
- // remotely disabled
190
- this._receivedFlags = true
172
+ this._serverRecordingEnabled = false
173
+ this._clearRemoteConfig()
174
+ this.stopRecording()
191
175
  return
192
176
  }
193
177
 
178
+ this._serverRecordingEnabled = true
194
179
  this._persistRemoteConfig(response)
195
- this._receivedFlags = true
196
180
  this.startIfEnabledOrStop()
197
181
  }
198
182
 
@@ -204,22 +188,9 @@ export class SessionRecording {
204
188
  }
205
189
  }
206
190
 
207
- private get _scriptName(): PostHogExtensionKind {
208
- const remoteConfig: SessionRecordingPersistedConfig | undefined = this._instance?.persistence?.get_property(
209
- SESSION_RECORDING_REMOTE_CONFIG
210
- )
211
- return (remoteConfig?.scriptConfig?.script as PostHogExtensionKind) || 'lazy-recorder'
212
- }
213
-
214
191
  private _onScriptLoaded(startReason?: SessionStartReason) {
215
- if (!assignableWindow.__PosthogExtensions__?.initSessionRecording) {
216
- throw Error('Called on script loaded before session recording is available')
217
- }
218
-
219
192
  if (!this._lazyLoadedSessionRecording) {
220
- this._lazyLoadedSessionRecording = assignableWindow.__PosthogExtensions__?.initSessionRecording(
221
- this._instance
222
- )
193
+ this._lazyLoadedSessionRecording = new LazyLoadedSessionRecording(this._instance)
223
194
  ;(this._lazyLoadedSessionRecording as any)._forceAllowLocalhostNetworkCapture =
224
195
  this._forceAllowLocalhostNetworkCapture
225
196
  }
package/src/leanbase.ts CHANGED
@@ -7,7 +7,6 @@ import {
7
7
  isObject,
8
8
  isString,
9
9
  isUndefined,
10
- isFunction,
11
10
  } from '@posthog/core'
12
11
  import type {
13
12
  PostHogEventProperties,
@@ -39,18 +38,25 @@ import { COOKIELESS_MODE_FLAG_PROPERTY, USER_STATE } from './constants'
39
38
  import { getEventProperties } from './utils/event-utils'
40
39
  import { SessionIdManager } from './sessionid'
41
40
  import { SessionPropsManager } from './session-props'
42
- import { RequestRouter } from './utils/request-router'
43
41
  import { uuidv7 } from './uuidv7'
44
42
  import { PageViewManager } from './page-view'
45
43
  import { ScrollManager } from './scroll-manager'
46
44
  import { isLikelyBot } from './utils/blocked-uas'
45
+ import { SessionRecording } from './extensions/replay/session-recording'
46
+ import type { RequestRouter } from '../../browser/lib/src/utils/request-router'
47
+ import type { ConsentManager } from '../../browser/lib/src/consent'
47
48
 
48
49
  const defaultConfig = (): LeanbaseConfig => ({
49
50
  host: 'https://i.leanbase.co',
50
- api_host: 'https://i.leanbase.co',
51
51
  token: '',
52
52
  autocapture: true,
53
53
  rageclick: true,
54
+ disable_session_recording: false,
55
+ session_recording: {
56
+ // Force-enable session recording locally unless explicitly disabled via config
57
+ forceClientRecording: true,
58
+ },
59
+ enable_recording_console_log: undefined,
54
60
  persistence: 'localStorage+cookie',
55
61
  capture_pageview: 'history_change',
56
62
  capture_pageleave: 'if_capture_pageview',
@@ -71,7 +77,6 @@ const defaultConfig = (): LeanbaseConfig => ({
71
77
  opt_out_useragent_filter: false,
72
78
  properties_string_max_length: 65535,
73
79
  loaded: () => {},
74
- session_recording: {} as any,
75
80
  })
76
81
 
77
82
  export class Leanbase extends PostHogCore {
@@ -84,8 +89,9 @@ export class Leanbase extends PostHogCore {
84
89
  sessionPersistence?: LeanbasePersistence
85
90
  sessionManager?: SessionIdManager
86
91
  sessionPropsManager?: SessionPropsManager
87
- sessionRecording?: any
88
- requestRouter: RequestRouter
92
+ requestRouter!: RequestRouter
93
+ consent!: ConsentManager
94
+ sessionRecording?: SessionRecording
89
95
  isRemoteConfigLoaded?: boolean
90
96
  personProcessingSetOncePropertiesSent = false
91
97
  isLoaded: boolean = false
@@ -102,7 +108,6 @@ export class Leanbase extends PostHogCore {
102
108
  this.initialPageviewCaptured = false
103
109
  this.scrollManager = new ScrollManager(this)
104
110
  this.pageViewManager = new PageViewManager(this)
105
- this.requestRouter = new RequestRouter(this)
106
111
  this.init(token, mergedConfig)
107
112
  }
108
113
 
@@ -114,19 +119,16 @@ export class Leanbase extends PostHogCore {
114
119
  )
115
120
  this.isLoaded = true
116
121
  this.persistence = new LeanbasePersistence(this.config)
117
- this.replayAutocapture = new Autocapture(this)
118
- this.replayAutocapture.startIfEnabled()
119
122
 
120
- // Initialize session manager and props before session recording (matches browser behavior)
121
123
  if (this.config.cookieless_mode !== 'always') {
122
- if (!this.sessionManager) {
123
- this.sessionManager = new SessionIdManager(this)
124
- this.sessionPropsManager = new SessionPropsManager(this, this.sessionManager, this.persistence as any)
125
- }
124
+ this.sessionManager = new SessionIdManager(this)
125
+ this.sessionPropsManager = new SessionPropsManager(this, this.sessionManager, this.persistence)
126
+ }
127
+
128
+ this.replayAutocapture = new Autocapture(this)
129
+ this.replayAutocapture.startIfEnabled()
126
130
 
127
- // runtime require to lazy-load replay code; allowed for browser parity
128
- // @ts-expect-error - runtime import only available in browser build
129
- const { SessionRecording } = require('./extensions/replay/session-recording') // eslint-disable-line @typescript-eslint/no-require-imports
131
+ if (this.sessionManager && this.config.cookieless_mode !== 'always') {
130
132
  this.sessionRecording = new SessionRecording(this)
131
133
  this.sessionRecording.startIfEnabledOrStop()
132
134
  }
@@ -207,6 +209,7 @@ export class Leanbase extends PostHogCore {
207
209
 
208
210
  this.isRemoteConfigLoaded = true
209
211
  this.replayAutocapture?.onRemoteConfig(config)
212
+ this.sessionRecording?.onRemoteConfig(config)
210
213
  }
211
214
 
212
215
  fetch(url: string, options: PostHogFetchOptions): Promise<PostHogFetchResponse> {
@@ -222,8 +225,13 @@ export class Leanbase extends PostHogCore {
222
225
  const oldConfig = { ...this.config }
223
226
  if (isObject(config)) {
224
227
  extend(this.config, config)
228
+ if (!this.config.api_host && this.config.host) {
229
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
230
+ ;(this.config as any).api_host = this.config.host
231
+ }
225
232
  this.persistence?.update_config(this.config, oldConfig)
226
233
  this.replayAutocapture?.startIfEnabled()
234
+ this.sessionRecording?.startIfEnabledOrStop()
227
235
  }
228
236
 
229
237
  const isTempStorage = this.config.persistence === 'sessionStorage' || this.config.persistence === 'memory'
@@ -248,43 +256,14 @@ export class Leanbase extends PostHogCore {
248
256
  return this.persistence?.get_property(key)
249
257
  }
250
258
 
251
- setPersistedProperty<T>(key: PostHogPersistedProperty, value: T | null): void {
252
- this.persistence?.set_property(key, value)
253
- }
254
-
255
- // Backwards-compatible aliases expected by replay/browser code
256
- get_property(key: string): any {
259
+ get_property<T = any>(key: string): T | undefined {
257
260
  return this.persistence?.get_property(key)
258
261
  }
259
262
 
260
- set_property(key: string, value: any): void {
263
+ setPersistedProperty<T>(key: PostHogPersistedProperty, value: T | null): void {
261
264
  this.persistence?.set_property(key, value)
262
265
  }
263
266
 
264
- register_for_session(properties: Record<string, any>): void {
265
- // PostHogCore may expose registerForSession; call it if available
266
- if (isFunction((this as any).registerForSession)) {
267
- ;(this as any).registerForSession(properties)
268
- return
269
- }
270
-
271
- // fallback: store properties in sessionPersistence
272
- if (this.sessionPersistence) {
273
- Object.keys(properties).forEach((k) => this.sessionPersistence?.set_property(k, properties[k]))
274
- }
275
- }
276
-
277
- unregister_for_session(property: string): void {
278
- if (isFunction((this as any).unregisterForSession)) {
279
- ;(this as any).unregisterForSession(property)
280
- return
281
- }
282
-
283
- if (this.sessionPersistence) {
284
- this.sessionPersistence.set_property(property, null)
285
- }
286
- }
287
-
288
267
  calculateEventProperties(
289
268
  eventName: string,
290
269
  eventProperties: PostHogEventProperties,
@@ -334,6 +313,14 @@ export class Leanbase extends PostHogCore {
334
313
  extend(properties, this.sessionPropsManager.getSessionProps())
335
314
  }
336
315
 
316
+ try {
317
+ if (this.sessionRecording) {
318
+ extend(properties, this.sessionRecording.sdkDebugProperties)
319
+ }
320
+ } catch (e: any) {
321
+ properties['$sdk_debug_error_capturing_properties'] = String(e)
322
+ }
323
+
337
324
  let pageviewProperties: Record<string, any> = this.pageViewManager.doEvent()
338
325
  if (eventName === '$pageview' && !readOnly) {
339
326
  pageviewProperties = this.pageViewManager.doPageView(timestamp, uuid)
@@ -0,0 +1,5 @@
1
+ declare module 'fflate' {
2
+ export const gzipSync: (...args: any[]) => any
3
+ export const strFromU8: (...args: any[]) => any
4
+ export const strToU8: (...args: any[]) => any
5
+ }
@@ -0,0 +1,8 @@
1
+ declare module '@rrweb/record' {
2
+ import type { recordOptions, rrwebRecord } from '../extensions/replay/types/rrweb'
3
+
4
+ const record: (options: recordOptions) => rrwebRecord
5
+
6
+ export { record }
7
+ export default record
8
+ }