Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/analytics-core/src/types/element-interactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@ export interface ElementInteractionsOptions {
messenger?: Messenger;
};

/**
* Options for integrating background capture features that rely on a Messenger
* to communicate with an external parent window.
*/
backgroundCaptureOptions?: {
enabled?: boolean;
messenger?: Messenger;
};

/**
* This has been deprecated in favor of rage clicks tracking
* via frustrationInteractions.
Expand Down
9 changes: 9 additions & 0 deletions packages/analytics-types/src/element-interactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ export interface ElementInteractionsOptions {
messenger?: Messenger;
};

/**
* Options for integrating background capture features that rely on a Messenger
* to communicate with an external parent window.
*/
backgroundCaptureOptions?: {
enabled?: boolean;
messenger?: Messenger;
};

/**
* Debounce time in milliseconds for tracking events.
* This is used to detect rage clicks.
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-autocapture-browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
},
"dependencies": {
"@amplitude/analytics-core": "2.35.1-feat-zoning-010526.0",
"@amplitude/rrweb-snapshot": "2.0.0-alpha.35",
"rxjs": "^7.8.1",
"tslib": "^2.4.1"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-autocapture-browser/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ iife.output.name = 'amplitudeAutocapturePlugin';
if (process.env.NODE_ENV === 'development') {
iife.output.sourcemap = 'inline';
}
export default [umd, iife];
export default [umd, iife];
14 changes: 13 additions & 1 deletion packages/plugin-autocapture-browser/src/autocapture-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ export const autocapturePlugin = (
enabled: true,
messenger: new WindowMessenger(),
},
backgroundCaptureOptions = {
enabled: true,
messenger: new WindowMessenger(),
},
} = options;

options.cssSelectorAllowlist = options.cssSelectorAllowlist ?? DEFAULT_CSS_SELECTOR_ALLOWLIST;
Expand Down Expand Up @@ -423,7 +427,6 @@ export const autocapturePlugin = (

/* istanbul ignore next */
config?.loggerProvider?.log(`${name} has been successfully added.`);

// Setup visual tagging selector
if (window.opener && visualTaggingOptions.enabled) {
const allowlist = (options as AutoCaptureOptionsWithDefaults).cssSelectorAllowlist;
Expand All @@ -439,6 +442,15 @@ export const autocapturePlugin = (
actionClickAllowlist: actionClickAllowlist,
});
}

// Setup background capture messenger if it is not already setup for visual tagging selector
if (window.opener && backgroundCaptureOptions.enabled && !visualTaggingOptions.messenger) {
/* istanbul ignore next */
backgroundCaptureOptions.messenger?.setup({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repeated inits leak/duplicate listeners/observers. Consider an explicit lifecycle: store handler refs; teardown removes listeners and closes any existing instance before re‑init (e.g., WindowMessenger.setup, initialize-background-capture).

🚀 Want me to fix this? Reply ex: "fix it for me".

dataExtractor: dataExtractor,
logger: config?.loggerProvider,
});
}
Comment on lines +450 to +453
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Background capture setup omits the endpoint derived from serverZone, so WindowMessenger defaults to US origin and rejects messages from EU (origin mismatch). Consider passing the same endpoint here as in the visual tagging path.

Suggested change
dataExtractor: dataExtractor,
logger: config?.loggerProvider,
});
}
backgroundCaptureOptions.messenger?.setup({
dataExtractor: dataExtractor,
logger: config?.loggerProvider,
...(config?.serverZone && { endpoint: constants.AMPLITUDE_ORIGINS_MAP[config.serverZone] }),
});

🚀 Want me to fix this? Reply ex: "fix it for me".

};

const execute: BrowserEnrichmentPlugin['execute'] = async (event) => {
Expand Down
2 changes: 2 additions & 0 deletions packages/plugin-autocapture-browser/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ export const AMPLITUDE_ORIGINS_MAP = {

export const AMPLITUDE_VISUAL_TAGGING_SELECTOR_SCRIPT_URL =
'https://cdn.amplitude.com/libs/visual-tagging-selector-1.0.0-alpha.js.gz';
export const AMPLITUDE_BACKGROUND_CAPTURE_SCRIPT_URL =
'https://cdn.amplitude.com/libs/background-capture-1.0.0-alpha.0.js.gz';
// This is the class name used by the visual tagging selector to highlight the selected element.
// Should not use this class in the selector.
export const AMPLITUDE_VISUAL_TAGGING_HIGHLIGHT_CLASS = 'amp-visual-tagging-selector-highlight';
Expand Down
42 changes: 40 additions & 2 deletions packages/plugin-autocapture-browser/src/libs/messenger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ import {
AMPLITUDE_ORIGIN,
AMPLITUDE_VISUAL_TAGGING_SELECTOR_SCRIPT_URL,
AMPLITUDE_VISUAL_TAGGING_HIGHLIGHT_CLASS,
AMPLITUDE_BACKGROUND_CAPTURE_SCRIPT_URL,
} from '../constants';
import { asyncLoadScript, generateUniqueId } from '../helpers';
import { ILogger, Messenger, ActionType } from '@amplitude/analytics-core';
// Inline background-capture implementation (disabled) uses rrweb snapshot.
// eslint-disable-next-line import/no-extraneous-dependencies
// import { snapshot } from '@amplitude/rrweb-snapshot';
import { VERSION } from '../version';
import { DataExtractor } from '../data-extractor';

Expand All @@ -19,7 +23,11 @@ export type Action =
| 'close-visual-tagging-selector'
| 'element-selected'
| 'track-selector-mode-changed'
| 'track-selector-moved';
| 'track-selector-moved'
| 'initialize-background-capture'
| 'close-background-capture'
| 'background-capture-loaded'
| 'background-capture-complete';

interface InitializeVisualTaggingSelectorData {
actionType: ActionType;
Expand Down Expand Up @@ -53,6 +61,10 @@ export type ActionData = {
'element-selected': ElementSelectedData;
'track-selector-mode-changed': TrackSelectorModeChangedData;
'track-selector-moved': TrackSelectorMovedData;
'initialize-background-capture': null | undefined;
'close-background-capture': null | undefined;
'background-capture-loaded': null | undefined;
'background-capture-complete': { [key: string]: string | null };
};

export interface Message<A extends Action> {
Expand Down Expand Up @@ -155,7 +167,8 @@ export class WindowMessenger implements Messenger {
this.endpoint = endpoint;
}
let amplitudeVisualTaggingSelectorInstance: any = null;

let amplitudeBackgroundCaptureInstance: any = null;
this.logger?.debug?.('Setting up messenger');
// Attach Event Listener to listen for messages from the parent window
window.addEventListener('message', (event) => {
this.logger?.debug?.('Message received: ', JSON.stringify(event));
Expand Down Expand Up @@ -214,9 +227,27 @@ export class WindowMessenger implements Messenger {
.catch(() => {
this.logger?.warn('Failed to initialize visual tagging selector');
});
} else if (action === 'initialize-background-capture') {
this.logger?.debug?.('Initializing background capture (external script)');
asyncLoadScript(new URL(AMPLITUDE_BACKGROUND_CAPTURE_SCRIPT_URL, this.endpoint).toString())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-initializing background-capture overwrites amplitudeBackgroundCaptureInstance without closing the previous instance. Consider closing any existing instance before creating a new one to avoid leaked listeners and duplicate capture.

Suggested change
asyncLoadScript(new URL(AMPLITUDE_BACKGROUND_CAPTURE_SCRIPT_URL, this.endpoint).toString())
// eslint-disable-next-line
amplitudeBackgroundCaptureInstance?.close?.();
amplitudeBackgroundCaptureInstance = null;
asyncLoadScript(new URL(AMPLITUDE_BACKGROUND_CAPTURE_SCRIPT_URL, this.endpoint).toString())

🚀 Want me to fix this? Reply ex: "fix it for me".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new URL(AMPLITUDE_BACKGROUND_CAPTURE_SCRIPT_URL, this.endpoint) will throw if this.endpoint is '*'. Consider passing the absolute AMPLITUDE_BACKGROUND_CAPTURE_SCRIPT_URL directly to avoid crashing the listener.

Suggested change
asyncLoadScript(new URL(AMPLITUDE_BACKGROUND_CAPTURE_SCRIPT_URL, this.endpoint).toString())
asyncLoadScript(AMPLITUDE_BACKGROUND_CAPTURE_SCRIPT_URL)

🚀 Want me to fix this? Reply ex: "fix it for me".

.then(() => {
this.logger?.debug?.('Background capture script loaded (external)');
// eslint-disable-next-line
amplitudeBackgroundCaptureInstance = (window as any)?.amplitudeBackgroundCapture?.({
messenger: this,
onBackgroundCapture: this.onBackgroundCapture,
});
this.notify({ action: 'background-capture-loaded' });
})
.catch(() => {
this.logger?.warn('Failed to initialize background capture');
});
} else if (action === 'close-visual-tagging-selector') {
// eslint-disable-next-line
amplitudeVisualTaggingSelectorInstance?.close?.();
} else if (action === 'close-background-capture') {
// eslint-disable-next-line
amplitudeBackgroundCaptureInstance?.close?.();
}
}
});
Expand All @@ -236,4 +267,11 @@ export class WindowMessenger implements Messenger {
this.notify({ action: 'track-selector-moved', data: properties });
}
};

private onBackgroundCapture = (type: string, backgroundCaptureData: { [key: string]: string | number | null }) => {
if (type === 'background-capture-complete') {
this.logger?.debug?.('Background capture complete');
this.notify({ action: 'background-capture-complete', data: backgroundCaptureData });
}
};
}
Loading
Loading