Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PostHog.init() always returns a value, TS types are misleading #1616

Open
thexeos opened this issue Dec 17, 2024 · 1 comment
Open

PostHog.init() always returns a value, TS types are misleading #1616

thexeos opened this issue Dec 17, 2024 · 1 comment

Comments

@thexeos
Copy link

thexeos commented Dec 17, 2024

The PostHog.init() method always returns a value (PostHog) and never returns undefined, contrary to what TS types suggest. The current behavior is as follows:

Returns the primary PostHog instance (this) when:

  • No name is provided
  • The name is 'posthog'
  • The token is undefined/empty (with a warning)
  • The instance is already loaded (with a warning)

Returns a new named PostHog instance when:

  • A custom name is provided

Relevant code:

init(
token: string,
config?: OnlyValidKeys<Partial<PostHogConfig>, Partial<PostHogConfig>>,
name?: string
): PostHog | undefined {
if (!name || name === PRIMARY_INSTANCE_NAME) {
// This means we are initializing the primary instance (i.e. this)
return this._init(token, config, name)
} else {
const namedPosthog = instances[name] ?? new PostHog()
namedPosthog._init(token, config, name)
instances[name] = namedPosthog
// Add as a property to the primary instance (this isn't type-safe but its how it was always done)
;(instances[PRIMARY_INSTANCE_NAME] as any)[name] = namedPosthog
return namedPosthog
}
}
// posthog._init(token:string, config:object, name:string)
//
// This function sets up the current instance of the posthog
// library. The difference between this method and the init(...)
// method is this one initializes the actual instance, whereas the
// init(...) method sets up a new library and calls _init on it.
//
// Note that there are operations that can be asynchronous, so we
// accept a callback that is called when all the asynchronous work
// is done. Note that we do not use promises because we want to be
// IE11 compatible. We could use polyfills, which would make the
// code a bit cleaner, but will add some overhead.
//
_init(token: string, config: Partial<PostHogConfig> = {}, name?: string): PostHog {
if (isUndefined(token) || isEmptyString(token)) {
logger.critical(
'PostHog was initialized without a token. This likely indicates a misconfiguration. Please check the first argument passed to posthog.init()'
)
return this
}
if (this.__loaded) {
logger.warn('You have already initialized PostHog! Re-initializing is a no-op')
return this
}
this.__loaded = true
this.config = {} as PostHogConfig // will be set right below
this._triggered_notifs = []
if (config.person_profiles) {
this._initialPersonProfilesConfig = config.person_profiles
}
this.set_config(
extend({}, defaultConfig(), configRenames(config), {
name: name,
token: token,
})
)
if (this.config.on_xhr_error) {
logger.error('on_xhr_error is deprecated. Use on_request_error instead')
}
this.compression = config.disable_compression ? undefined : Compression.GZipJS
this.persistence = new PostHogPersistence(this.config)
this.sessionPersistence =
this.config.persistence === 'sessionStorage' || this.config.persistence === 'memory'
? this.persistence
: new PostHogPersistence({ ...this.config, persistence: 'sessionStorage' })
// should I store the initial person profiles config in persistence?
const initialPersistenceProps = { ...this.persistence.props }
const initialSessionProps = { ...this.sessionPersistence.props }
this._requestQueue = new RequestQueue((req) => this._send_retriable_request(req))
this._retryQueue = new RetryQueue(this)
this.__request_queue = []
if (!this.config.__preview_experimental_cookieless_mode) {
this.sessionManager = new SessionIdManager(this)
this.sessionPropsManager = new SessionPropsManager(this.sessionManager, this.persistence)
}
new TracingHeaders(this).startIfEnabledOrStop()
this.siteApps = new SiteApps(this)
this.siteApps?.init()
if (!this.config.__preview_experimental_cookieless_mode) {
this.sessionRecording = new SessionRecording(this)
this.sessionRecording.startIfEnabledOrStop()
}
if (!this.config.disable_scroll_properties) {
this.scrollManager.startMeasuringScrollPosition()
}
this.autocapture = new Autocapture(this)
this.autocapture.startIfEnabled()
this.surveys.loadIfEnabled()
this.heatmaps = new Heatmaps(this)
this.heatmaps.startIfEnabled()
this.webVitalsAutocapture = new WebVitalsAutocapture(this)
this.exceptionObserver = new ExceptionObserver(this)
this.exceptionObserver.startIfEnabled()
this.deadClicksAutocapture = new DeadClicksAutocapture(this, isDeadClicksEnabledForAutocapture)
this.deadClicksAutocapture.startIfEnabled()
// if any instance on the page has debug = true, we set the
// global debug to be true
Config.DEBUG = Config.DEBUG || this.config.debug
if (Config.DEBUG) {
logger.info('Starting in debug mode', {
this: this,
config,
thisC: { ...this.config },
p: initialPersistenceProps,
s: initialSessionProps,
})
}
this._sync_opt_out_with_persistence()
// isUndefined doesn't provide typehint here so wouldn't reduce bundle as we'd need to assign
// eslint-disable-next-line posthog-js/no-direct-undefined-check
if (config.bootstrap?.distinctID !== undefined) {
const uuid = this.config.get_device_id(uuidv7())
const deviceID = config.bootstrap?.isIdentifiedID ? uuid : config.bootstrap.distinctID
this.persistence.set_property(USER_STATE, config.bootstrap?.isIdentifiedID ? 'identified' : 'anonymous')
this.register({
distinct_id: config.bootstrap.distinctID,
$device_id: deviceID,
})
}
if (this._hasBootstrappedFeatureFlags()) {
const activeFlags = Object.keys(config.bootstrap?.featureFlags || {})
.filter((flag) => !!config.bootstrap?.featureFlags?.[flag])
.reduce(
(res: Record<string, string | boolean>, key) => (
(res[key] = config.bootstrap?.featureFlags?.[key] || false), res
),
{}
)
const featureFlagPayloads = Object.keys(config.bootstrap?.featureFlagPayloads || {})
.filter((key) => activeFlags[key])
.reduce((res: Record<string, JsonType>, key) => {
if (config.bootstrap?.featureFlagPayloads?.[key]) {
res[key] = config.bootstrap?.featureFlagPayloads?.[key]
}
return res
}, {})
this.featureFlags.receivedFeatureFlags({ featureFlags: activeFlags, featureFlagPayloads })
}
if (this.config.__preview_experimental_cookieless_mode) {
this.register_once(
{
distinct_id: COOKIELESS_SENTINEL_VALUE,
$device_id: null,
},
''
)
} else if (!this.get_distinct_id()) {
// There is no need to set the distinct id
// or the device id if something was already stored
// in the persistence
const uuid = this.config.get_device_id(uuidv7())
this.register_once(
{
distinct_id: uuid,
$device_id: uuid,
},
''
)
// distinct id == $device_id is a proxy for anonymous user
this.persistence.set_property(USER_STATE, 'anonymous')
}
// Set up event handler for pageleave
// Use `onpagehide` if available, see https://calendar.perfplanet.com/2020/beaconing-in-practice/#beaconing-reliability-avoiding-abandons
window?.addEventListener?.('onpagehide' in self ? 'pagehide' : 'unload', this._handle_unload.bind(this))
this.toolbar.maybeLoadToolbar()
// We want to avoid promises for IE11 compatibility, so we use callbacks here
if (config.segment) {
setupSegmentIntegration(this, () => this._loaded())
} else {
this._loaded()
}
if (isFunction(this.config._onCapture) && this.config._onCapture !== __NOOP) {
logger.warn('onCapture is deprecated. Please use `before_send` instead')
this.on('eventCaptured', (data) => this.config._onCapture(data.event, data))
}
return this
}

@rafaeelaudibert
Copy link
Member

That does seem to be true :). We'd definitely appreciate a pull request changing that.

This does feel like a breaking change, but it's unlikely someone was heavily depending on this, so I'd be happy to merge this as a minor version.

thexeos added a commit to thexeos/posthog-js that referenced this issue Dec 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants