Skip to content

Commit

Permalink
Handle init values better when unintialised
Browse files Browse the repository at this point in the history
  • Loading branch information
benjackwhite committed Aug 3, 2023
1 parent ba3b8a4 commit ab469dc
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 49 deletions.
33 changes: 24 additions & 9 deletions src/extensions/sessionrecording.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,15 @@ export class SessionRecording {
})
}

private get sessionManager() {
if (!this.instance.sessionManager) {
logger.error('Session recording started without valid sessionManager')
return
}

return this.instance.sessionManager
}

startRecordingIfEnabled() {
if (this.isRecordingEnabled()) {
this.startCaptureAndTrySendingQueuedSnapshots()
Expand Down Expand Up @@ -185,14 +194,17 @@ export class SessionRecording {
}

private _startCapture() {
// According to the rrweb docs, rrweb is not supported on IE11 and below:
// "rrweb does not support IE11 and below because it uses the MutationObserver API which was supported by these browsers."
// https://github.com/rrweb-io/rrweb/blob/master/guide.md#compatibility-note
//
// However, MutationObserver does exist on IE11, it just doesn't work well and does not detect all changes.
// Instead, when we load "recorder.js", the first JS error is about "Object.assign" being undefined.
// Thus instead of MutationObserver, we look for this function and block recording if it's undefined.
if (!this.sessionManager) {
return
}
if (typeof Object.assign === 'undefined') {
// According to the rrweb docs, rrweb is not supported on IE11 and below:
// "rrweb does not support IE11 and below because it uses the MutationObserver API which was supported by these browsers."
// https://github.com/rrweb-io/rrweb/blob/master/guide.md#compatibility-note
//
// However, MutationObserver does exist on IE11, it just doesn't work well and does not detect all changes.
// Instead, when we load "recorder.js", the first JS error is about "Object.assign" being undefined.
// Thus instead of MutationObserver, we look for this function and block recording if it's undefined.
return
}

Expand All @@ -203,7 +215,7 @@ export class SessionRecording {

this.captureStarted = true
// We want to ensure the sessionManager is reset if necessary on load of the recorder
this.instance.sessionManager.checkAndGetSessionAndWindowId()
this.sessionManager.checkAndGetSessionAndWindowId()

const recorderJS = this.getRecordingVersion() === 'v2' ? 'recorder-v2.js' : 'recorder.js'

Expand Down Expand Up @@ -231,6 +243,9 @@ export class SessionRecording {
}

private _updateWindowAndSessionIds(event: eventWithTime) {
if (!this.sessionManager) {
return
}
// Some recording events are triggered by non-user events (e.g. "X minutes ago" text updating on the screen).
// We don't want to extend the session or trigger a new session in these cases. These events are designated by event
// type -> incremental update, and source -> mutation.
Expand Down Expand Up @@ -258,7 +273,7 @@ export class SessionRecording {
}

// We only want to extend the session if it is an interactive event.
const { windowId, sessionId } = this.instance.sessionManager.checkAndGetSessionAndWindowId(
const { windowId, sessionId } = this.sessionManager.checkAndGetSessionAndWindowId(
!isUserInteraction,
event.timestamp
)
Expand Down
76 changes: 43 additions & 33 deletions src/posthog-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,10 +260,7 @@ export class PostHog {
__loaded_recorder_version: 'v1' | 'v2' | undefined // flag that keeps track of which version of recorder is loaded
config: PostHogConfig

persistence: PostHogPersistence
rateLimiter: RateLimiter
sessionPersistence: PostHogPersistence
sessionManager: SessionIdManager
pageViewIdManager: PageViewIdManager
featureFlags: PostHogFeatureFlags
surveys: PostHogSurveys
Expand All @@ -272,8 +269,12 @@ export class PostHog {
webPerformance: WebPerformanceObserver | undefined
exceptionAutocapture: ExceptionObserver | undefined

_requestQueue: RequestQueue
_retryQueue: RetryQueue
// These are instance-specific state created after initialisation
persistence?: PostHogPersistence
sessionPersistence?: PostHogPersistence
sessionManager?: SessionIdManager
_requestQueue?: RequestQueue
_retryQueue?: RetryQueue

_triggered_notifs: any
compression: Partial<Record<Compression, boolean>>
Expand Down Expand Up @@ -314,13 +315,6 @@ export class PostHog {
this.surveys = new PostHogSurveys(this)
this.rateLimiter = new RateLimiter()

// these are created in _init() after we have the config
this._requestQueue = undefined as any
this._retryQueue = undefined as any
this.persistence = undefined as any
this.sessionPersistence = undefined as any
this.sessionManager = undefined as any

// NOTE: See the property definition for deprecation notice
this.people = {
set: (prop: string | Properties, to?: string, callback?: RequestCallback) => {
Expand Down Expand Up @@ -562,7 +556,7 @@ export class PostHog {
_start_queue_if_opted_in(): void {
if (!this.has_opted_out_capturing()) {
if (this.get_config('request_batching')) {
this._requestQueue.poll()
this._requestQueue?.poll()
}
}
}
Expand Down Expand Up @@ -625,8 +619,8 @@ export class PostHog {
this.capture('$pageleave')
}

this._requestQueue.unload()
this._retryQueue.unload()
this._requestQueue?.unload()
this._retryQueue?.unload()
}

_handle_queued_event(url: string, data: Record<string, any>, options?: XHROptions): void {
Expand All @@ -645,6 +639,9 @@ export class PostHog {
}

_send_request(url: string, data: Record<string, any>, options: CaptureOptions, callback?: RequestCallback): void {
if (!this.__loaded || !this._retryQueue) {
return
}
if (this.rateLimiter.isRateLimited(options._batchKey)) {
if (this.get_config('debug')) {
console.warn('[PostHog SendRequest] is quota limited. Dropping request.')
Expand Down Expand Up @@ -837,7 +834,7 @@ export class PostHog {
): CaptureResult | void {
// While developing, a developer might purposefully _not_ call init(),
// in this case, we would like capture to be a noop.
if (!this.__loaded) {
if (!this.__loaded || !this.sessionPersistence || !this._requestQueue) {
return
}

Expand Down Expand Up @@ -922,6 +919,10 @@ export class PostHog {
}

_calculate_event_properties(event_name: string, event_properties: Properties): Properties {
if (!this.persistence || !this.sessionPersistence) {
return event_properties
}

// set defaults
const start_timestamp = this.persistence.remove_event_timer(event_name)
let properties = { ...event_properties }
Expand Down Expand Up @@ -1019,7 +1020,7 @@ export class PostHog {
* @param {Number} [days] How many days since the user's last visit to store the super properties
*/
register(properties: Properties, days?: number): void {
this.persistence.register(properties, days)
this.persistence?.register(properties, days)
}

/**
Expand All @@ -1046,7 +1047,7 @@ export class PostHog {
* @param {Number} [days] How many days since the users last visit to store the super properties
*/
register_once(properties: Properties, default_value?: Property, days?: number): void {
this.persistence.register_once(properties, default_value, days)
this.persistence?.register_once(properties, default_value, days)
}

/**
Expand All @@ -1073,7 +1074,7 @@ export class PostHog {
* @param {Object} properties An associative array of properties to store about the user
*/
register_for_session(properties: Properties): void {
this.sessionPersistence.register(properties)
this.sessionPersistence?.register(properties)
}

/**
Expand All @@ -1082,7 +1083,7 @@ export class PostHog {
* @param {String} property The name of the super property to remove
*/
unregister(property: string): void {
this.persistence.unregister(property)
this.persistence?.unregister(property)
}

/**
Expand All @@ -1091,7 +1092,7 @@ export class PostHog {
* @param {String} property The name of the session super property to remove
*/
unregister_for_session(property: string): void {
this.sessionPersistence.unregister(property)
this.sessionPersistence?.unregister(property)
}

_register_single(prop: string, value: Property) {
Expand Down Expand Up @@ -1191,7 +1192,7 @@ export class PostHog {
* @returns {Function} A function that can be called to unsubscribe the listener. E.g. Used by useEffect when the component unmounts.
*/
onSessionId(callback: SessionIdChangedCallback): () => void {
return this.sessionManager.onSessionId(callback)
return this.sessionManager?.onSessionId(callback) ?? (() => {})
}

/** Get list of all surveys. */
Expand Down Expand Up @@ -1251,6 +1252,9 @@ export class PostHog {
* @param {Object} [userPropertiesToSetOnce] Optional: An associative array of properties to store about the user. If property is previously set, this does not override that value.
*/
identify(new_distinct_id?: string, userPropertiesToSet?: Properties, userPropertiesToSetOnce?: Properties): void {
if (!this.__loaded || !this.persistence) {
return
}
//if the new_distinct_id has not been set ignore the identify event
if (!new_distinct_id) {
console.error('Unique user id has not been set in posthog.identify')
Expand Down Expand Up @@ -1417,11 +1421,14 @@ export class PostHog {
* Useful for clearing data when a user logs out.
*/
reset(reset_device_id?: boolean): void {
if (!this.__loaded) {
return
}
const device_id = this.get_property('$device_id')
this.persistence.clear()
this.sessionPersistence.clear()
this.persistence.set_user_state('anonymous')
this.sessionManager.resetSessionId()
this.persistence?.clear()
this.sessionPersistence?.clear()
this.persistence?.set_user_state('anonymous')
this.sessionManager?.resetSessionId()
const uuid = this.get_config('get_device_id')(this.uuidFn())
this.register_once(
{
Expand Down Expand Up @@ -1464,7 +1471,7 @@ export class PostHog {
*/

get_session_id(): string {
return this.sessionManager.checkAndGetSessionAndWindowId(true).sessionId
return this.sessionManager?.checkAndGetSessionAndWindowId(true).sessionId ?? ''
}

/**
Expand All @@ -1475,6 +1482,9 @@ export class PostHog {
* @param options.timestampLookBack How many seconds to look back for the timestamp (defaults to 10)
*/
get_session_replay_url(options?: { withTimestamp?: boolean; timestampLookBack?: number }): string {
if (!this.sessionManager) {
return ''
}
const host = this.config.ui_host || this.config.api_host
const { sessionId, sessionStartTimestamp } = this.sessionManager.checkAndGetSessionAndWindowId(true)
let url = host + '/replay/' + sessionId
Expand Down Expand Up @@ -1765,7 +1775,7 @@ export class PostHog {
* @param {String} property_name The name of the super property you want to retrieve
*/
get_property(property_name: string): Property | undefined {
return this.persistence['props'][property_name]
return this.persistence?.['props'][property_name]
}

/**
Expand All @@ -1788,7 +1798,7 @@ export class PostHog {
* @param {String} property_name The name of the session super property you want to retrieve
*/
getSessionProperty(property_name: string): Property | undefined {
return this.sessionPersistence['props'][property_name]
return this.sessionPersistence?.['props'][property_name]
}

toString(): string {
Expand Down Expand Up @@ -1851,11 +1861,11 @@ export class PostHog {
return
}

if (!this.get_config('disable_persistence') && this.persistence.disabled !== disabled) {
this.persistence.set_disabled(disabled)
if (!this.get_config('disable_persistence') && this.persistence?.disabled !== disabled) {
this.persistence?.set_disabled(disabled)
}
if (!this.get_config('disable_persistence') && this.sessionPersistence.disabled !== disabled) {
this.sessionPersistence.set_disabled(disabled)
if (!this.get_config('disable_persistence') && this.sessionPersistence?.disabled !== disabled) {
this.sessionPersistence?.set_disabled(disabled)
}
}

Expand Down
15 changes: 9 additions & 6 deletions src/posthog-featureflags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ export class PostHogFeatureFlags {
} else {
flagCallReported[key] = [flagReportValue]
}
this.instance.persistence.register({ [FLAG_CALL_REPORTED]: flagCallReported })
this.instance.persistence?.register({ [FLAG_CALL_REPORTED]: flagCallReported })

this.instance.capture('$feature_flag_called', { $feature_flag: key, $feature_flag_response: flagValue })
}
Expand Down Expand Up @@ -264,6 +264,9 @@ export class PostHogFeatureFlags {
}

receivedFeatureFlags(response: Partial<DecideResponse>): void {
if (!this.instance.persistence) {
return
}
this.instance.decideEndpointWasHit = true
const currentFlags = this.getFlagVariants()
const currentFlagPayloads = this.getFlagPayloads()
Expand All @@ -286,15 +289,15 @@ export class PostHogFeatureFlags {
this._override_warning = false

if (flags === false) {
this.instance.persistence.unregister(PERSISTENCE_OVERRIDE_FEATURE_FLAGS)
this.instance.persistence?.unregister(PERSISTENCE_OVERRIDE_FEATURE_FLAGS)
} else if (Array.isArray(flags)) {
const flagsObj: Record<string, string | boolean> = {}
for (let i = 0; i < flags.length; i++) {
flagsObj[flags[i]] = true
}
this.instance.persistence.register({ [PERSISTENCE_OVERRIDE_FEATURE_FLAGS]: flagsObj })
this.instance.persistence?.register({ [PERSISTENCE_OVERRIDE_FEATURE_FLAGS]: flagsObj })
} else {
this.instance.persistence.register({ [PERSISTENCE_OVERRIDE_FEATURE_FLAGS]: flags })
this.instance.persistence?.register({ [PERSISTENCE_OVERRIDE_FEATURE_FLAGS]: flags })
}
}
/*
Expand Down Expand Up @@ -330,7 +333,7 @@ export class PostHogFeatureFlags {
this.setPersonPropertiesForFlags(enrollmentPersonProp, false)

const newFlags = { ...this.getFlagVariants(), [key]: isEnrolled }
this.instance.persistence.register({
this.instance.persistence?.register({
[PERSISTENCE_ACTIVE_FEATURE_FLAGS]: Object.keys(filterActiveFeatureFlags(newFlags)),
[ENABLED_FEATURE_FLAGS]: newFlags,
})
Expand All @@ -349,7 +352,7 @@ export class PostHogFeatureFlags {
{ method: 'GET' },
(response) => {
const earlyAccessFeatures = (response as EarlyAccessFeatureResponse).earlyAccessFeatures
this.instance.persistence.register({ [PERSISTENCE_EARLY_ACCESS_FEATURES]: earlyAccessFeatures })
this.instance.persistence?.register({ [PERSISTENCE_EARLY_ACCESS_FEATURES]: earlyAccessFeatures })
return callback(earlyAccessFeatures)
}
)
Expand Down
2 changes: 1 addition & 1 deletion src/posthog-surveys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export class PostHogSurveys {
{ method: 'GET' },
(response) => {
const surveys = response.surveys
this.instance.persistence.register({ [SURVEYS]: surveys })
this.instance.persistence?.register({ [SURVEYS]: surveys })
return callback(surveys)
}
)
Expand Down

0 comments on commit ab469dc

Please sign in to comment.