diff --git a/src/__tests__/posthog-core.identify.test.ts b/src/__tests__/posthog-core.identify.test.ts index 1586c8269..075c55475 100644 --- a/src/__tests__/posthog-core.identify.test.ts +++ b/src/__tests__/posthog-core.identify.test.ts @@ -208,6 +208,22 @@ describe('identify()', () => { ) expect(instance.featureFlags.setAnonymousDistinctId).not.toHaveBeenCalled() }) + + it('does not call $set when duplicate properties are passed', () => { + instance.identify('a-new-id', { email: 'john@example.com' }, { howOftenAmISet: 'once!' }) + instance.identify('a-new-id', { email: 'john@example.com' }, { howOftenAmISet: 'once!' }) + + expect(beforeSendMock).toHaveBeenCalledTimes(1) + expect(instance.featureFlags.setAnonymousDistinctId).not.toHaveBeenCalled() + }) + + it('calls $set when different properties are passed with the same distinct_id', () => { + instance.identify('a-new-id', { email: 'john@example.com' }, { howOftenAmISet: 'once!' }) + instance.identify('a-new-id', { email: 'john@example.com' }, { howOftenAmISet: 'twice!' }) + + expect(beforeSendMock).toHaveBeenCalledTimes(2) + expect(instance.featureFlags.setAnonymousDistinctId).not.toHaveBeenCalled() + }) }) describe('invalid id passed', () => { diff --git a/src/posthog-core.ts b/src/posthog-core.ts index ab30d98a6..94c607be2 100644 --- a/src/posthog-core.ts +++ b/src/posthog-core.ts @@ -83,6 +83,7 @@ import { PostHogExceptions } from './posthog-exceptions' import { SiteApps } from './site-apps' import { DeadClicksAutocapture, isDeadClicksEnabledForAutocapture } from './extensions/dead-clicks-autocapture' import { includes, isDistinctIdStringLike } from './utils/string-utils' +import { getIdentifyHash } from './utils/identify-utils' /* SIMPLE STYLE GUIDE: @@ -279,6 +280,7 @@ export class PostHog { analyticsDefaultEndpoint: string version = Config.LIB_VERSION _initialPersonProfilesConfig: 'always' | 'never' | 'identified_only' | null + _cachedIdentify: string | null SentryIntegration: typeof SentryIntegration sentryIntegration: (options?: SentryIntegrationOptions) => ReturnType @@ -306,7 +308,7 @@ export class PostHog { this.analyticsDefaultEndpoint = '/e/' this._initialPageviewCaptured = false this._initialPersonProfilesConfig = null - + this._cachedIdentify = null this.featureFlags = new PostHogFeatureFlags(this) this.toolbar = new Toolbar(this) this.scrollManager = new ScrollManager(this) @@ -1430,9 +1432,21 @@ export class PostHog { // let the reload feature flag request know to send this previous distinct id // for flag consistency this.featureFlags.setAnonymousDistinctId(previous_distinct_id) + + this._cachedIdentify = getIdentifyHash(new_distinct_id, userPropertiesToSet, userPropertiesToSetOnce) } else if (userPropertiesToSet || userPropertiesToSetOnce) { - // If the distinct_id is not changing, but we have user properties to set, we can go for a $set event - this.setPersonProperties(userPropertiesToSet, userPropertiesToSetOnce) + // If the distinct_id is not changing, but we have user properties to set, we can check if they have changed + // and if so, send a $set event + + if ( + this._cachedIdentify !== getIdentifyHash(new_distinct_id, userPropertiesToSet, userPropertiesToSetOnce) + ) { + this.setPersonProperties(userPropertiesToSet, userPropertiesToSetOnce) + + this._cachedIdentify = getIdentifyHash(new_distinct_id, userPropertiesToSet, userPropertiesToSetOnce) + } else { + logger.info('A duplicate posthog.identify call was made with the same properties. It has been ignored.') + } } // Reload active feature flags if the user identity changes. @@ -1464,6 +1478,8 @@ export class PostHog { // Update current user properties this.setPersonPropertiesForFlags(userPropertiesToSet || {}) + // if exactly this $set call has been sent before, don't send it again - determine based on hash of properties + this.capture('$set', { $set: userPropertiesToSet || {}, $set_once: userPropertiesToSetOnce || {} }) } @@ -1568,6 +1584,7 @@ export class PostHog { this.surveys?.reset() this.persistence?.set_property(USER_STATE, 'anonymous') this.sessionManager?.resetSessionId() + this._cachedIdentify = null if (this.config.__preview_experimental_cookieless_mode) { this.register_once( { diff --git a/src/utils/identify-utils.ts b/src/utils/identify-utils.ts new file mode 100644 index 000000000..049ba68c4 --- /dev/null +++ b/src/utils/identify-utils.ts @@ -0,0 +1,10 @@ +import { jsonStringify } from '../request' +import type { Properties } from '../types' + +export function getIdentifyHash( + distinct_id: string, + userPropertiesToSet?: Properties, + userPropertiesToSetOnce?: Properties +): string { + return jsonStringify({ distinct_id, userPropertiesToSet, userPropertiesToSetOnce }) +}