diff --git a/packages/analytics-core/src/index.ts b/packages/analytics-core/src/index.ts index 1d09d61ca..78bf84454 100644 --- a/packages/analytics-core/src/index.ts +++ b/packages/analytics-core/src/index.ts @@ -136,4 +136,4 @@ export { } from './types/config/react-native-config'; export { ReactNativeClient } from './types/client/react-native-client'; -export { Observable, asyncMap, merge, multicast } from './utils/observable'; +export { Observable, asyncMap, merge, multicast, Unsubscribable } from './utils/observable'; diff --git a/packages/analytics-core/src/utils/observable.ts b/packages/analytics-core/src/utils/observable.ts index f4debfbf8..5789f7605 100644 --- a/packages/analytics-core/src/utils/observable.ts +++ b/packages/analytics-core/src/utils/observable.ts @@ -145,4 +145,4 @@ function multicast(source: ZenObservable): ZenObservable { } } -export { asyncMap, multicast, merge }; +export { asyncMap, multicast, merge, Unsubscribable }; diff --git a/packages/plugin-autocapture-browser/src/autocapture-plugin.ts b/packages/plugin-autocapture-browser/src/autocapture-plugin.ts index c8a99e800..6aff6836f 100644 --- a/packages/plugin-autocapture-browser/src/autocapture-plugin.ts +++ b/packages/plugin-autocapture-browser/src/autocapture-plugin.ts @@ -8,10 +8,12 @@ import { DEFAULT_ACTION_CLICK_ALLOWLIST, DEFAULT_DATA_ATTRIBUTE_PREFIX, IDiagnosticsClient, + getGlobalScope, + multicast, } from '@amplitude/analytics-core'; import { VERSION } from './version'; import * as constants from './constants'; -import { fromEvent, map, type Observable, type Subscription, share } from 'rxjs'; +import { fromEvent, map, type Observable, share } from 'rxjs'; import { createShouldTrackEvent, type ElementBasedTimestampedEvent, @@ -31,7 +33,7 @@ import { groupLabeledEventIdsByEventType, } from './pageActions/triggers'; import { DataExtractor } from './data-extractor'; -import { Observable as ZenObservable } from '@amplitude/analytics-core'; +import { Observable as ZenObservable, Unsubscribable } from '@amplitude/analytics-core'; declare global { interface Window { @@ -59,7 +61,7 @@ export enum ObservablesEnum { export interface AllWindowObservables { [ObservablesEnum.ClickObservable]: Observable>; - [ObservablesEnum.ChangeObservable]: Observable>; + [ObservablesEnum.ChangeObservable]: ZenObservable>; // [ObservablesEnum.ErrorObservable]: Observable>; [ObservablesEnum.NavigateObservable]: Observable> | undefined; [ObservablesEnum.MutationObservable]: Observable>; @@ -112,7 +114,7 @@ export const autocapturePlugin = ( const name = constants.PLUGIN_NAME; const type = 'enrichment'; - const subscriptions: Subscription[] = []; + const subscriptions: Unsubscribable[] = []; // Create data extractor based on options const dataExtractor = new DataExtractor(options, context); @@ -131,16 +133,23 @@ export const autocapturePlugin = ( ), share(), ); - const changeObservable = fromEvent(document, 'change', { capture: true }).pipe( - map((change) => - dataExtractor.addAdditionalEventProperties( - change, - 'change', - (options as AutoCaptureOptionsWithDefaults).cssSelectorAllowlist, - dataAttributePrefix, - ), - ), - share(), + + const changeObservable = multicast( + new ZenObservable>((observer) => { + const handler = (changeEvent: Event) => { + const enrichedChangeEvent = dataExtractor.addAdditionalEventProperties( + changeEvent, + 'change', + (options as AutoCaptureOptionsWithDefaults).cssSelectorAllowlist, + dataAttributePrefix, + ) as ElementBasedTimestampedEvent; + observer.next(enrichedChangeEvent); + }; + /* istanbul ignore next */ + getGlobalScope()?.document.addEventListener('change', handler, { capture: true }); + /* istanbul ignore next */ + return () => getGlobalScope()?.document.removeEventListener('change', handler); + }), ); // Create Observable from unhandled errors @@ -180,7 +189,7 @@ export const autocapturePlugin = ( return { [ObservablesEnum.ClickObservable]: clickObservable as Observable>, - [ObservablesEnum.ChangeObservable]: changeObservable as Observable>, + [ObservablesEnum.ChangeObservable]: changeObservable, // [ObservablesEnum.ErrorObservable]: errorObservable, [ObservablesEnum.NavigateObservable]: navigateObservable, [ObservablesEnum.MutationObservable]: mutationObservable, diff --git a/packages/plugin-autocapture-browser/src/autocapture/track-change.ts b/packages/plugin-autocapture-browser/src/autocapture/track-change.ts index 8cde4dd7d..bf462ee98 100644 --- a/packages/plugin-autocapture-browser/src/autocapture/track-change.ts +++ b/packages/plugin-autocapture-browser/src/autocapture/track-change.ts @@ -1,6 +1,5 @@ import { AllWindowObservables } from 'src/autocapture-plugin'; -import { type evaluateTriggersFn } from 'src/helpers'; -import { filter, map } from 'rxjs'; +import { ElementBasedTimestampedEvent, type evaluateTriggersFn } from 'src/helpers'; import { BrowserClient, ActionType } from '@amplitude/analytics-core'; import { filterOutNonTrackableEvents, shouldTrackEvent } from '../helpers'; import { AMPLITUDE_ELEMENT_CHANGED_EVENT } from '../constants'; @@ -20,14 +19,13 @@ export function trackChange({ }) { const { changeObservable } = allObservables; - const filteredChangeObservable = changeObservable.pipe( - filter(filterOutNonTrackableEvents), - filter((changeEvent) => { + const filteredChangeObservable = changeObservable + .filter(filterOutNonTrackableEvents) + .filter((changeEvent: ElementBasedTimestampedEvent) => { // Only track change on elements that should be tracked, return shouldTrackEvent('change', changeEvent.closestTrackedAncestor); - }), - map((changeEvent) => evaluateTriggers(changeEvent)), - ); + }) + .map((changeEvent) => evaluateTriggers(changeEvent)); return filteredChangeObservable.subscribe((changeEvent) => { /* istanbul ignore next */ diff --git a/test-server/autocapture/element-interactions.html b/test-server/autocapture/element-interactions.html index b2594c619..da94789ac 100644 --- a/test-server/autocapture/element-interactions.html +++ b/test-server/autocapture/element-interactions.html @@ -268,9 +268,9 @@

Content Changing Button

import.meta.env.VITE_AMPLITUDE_USER_ID || 'amplitude-typescript test user', { identify, - fetchRemoteConfig: true, + fetchRemoteConfig: false, autocapture: { - elementInteractions: false, + elementInteractions: true, frustrationInteractions, sessions: true, },