Skip to content

Commit ccd3766

Browse files
committed
fix: register download and form listeners after window is loaded
1 parent 802da9c commit ccd3766

File tree

2 files changed

+133
-121
lines changed

2 files changed

+133
-121
lines changed

packages/analytics-browser/src/plugins/file-download-tracking.ts

Lines changed: 65 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -30,75 +30,81 @@ export const fileDownloadTracking = (): EnrichmentPlugin => {
3030
const name = '@amplitude/plugin-file-download-tracking-browser';
3131
const type = 'enrichment';
3232
const setup = async (config: BrowserConfig, amplitude: BrowserClient) => {
33-
/* istanbul ignore if */
34-
if (!amplitude) {
35-
// TODO: Add required minimum version of @amplitude/analytics-browser
36-
config.loggerProvider.warn(
37-
'File download tracking requires a later version of @amplitude/analytics-browser. File download events are not tracked.',
38-
);
39-
return;
40-
}
41-
42-
/* istanbul ignore if */
43-
if (typeof document === 'undefined') {
44-
return;
45-
}
46-
47-
const addFileDownloadListener = (a: HTMLAnchorElement) => {
48-
let url: URL;
49-
try {
50-
// eslint-disable-next-line no-restricted-globals
51-
url = new URL(a.href, window.location.href);
52-
} catch {
53-
/* istanbul ignore next */
33+
// The form interaction plugin observes changes in the dom. For this to work correctly, the observer can only be setup
34+
// after the body is built. When Amplitud gets initialized in a script tag, the body tag is still unavailable. So register this
35+
// only after the window is loaded
36+
// eslint-disable-next-line no-restricted-globals
37+
window.addEventListener('load', function () {
38+
/* istanbul ignore if */
39+
if (!amplitude) {
40+
// TODO: Add required minimum version of @amplitude/analytics-browser
41+
config.loggerProvider.warn(
42+
'File download tracking requires a later version of @amplitude/analytics-browser. File download events are not tracked.',
43+
);
5444
return;
5545
}
56-
const result = ext.exec(url.href);
57-
const fileExtension = result?.[1];
5846

59-
if (fileExtension) {
60-
addEventListener(a, 'click', () => {
61-
if (fileExtension) {
62-
amplitude.track(DEFAULT_FILE_DOWNLOAD_EVENT, {
63-
[FILE_EXTENSION]: fileExtension,
64-
[FILE_NAME]: url.pathname,
65-
[LINK_ID]: a.id,
66-
[LINK_TEXT]: a.text,
67-
[LINK_URL]: a.href,
68-
});
69-
}
70-
});
47+
/* istanbul ignore if */
48+
if (typeof document === 'undefined') {
49+
return;
7150
}
72-
};
73-
74-
const ext =
75-
/\.(pdf|xlsx?|docx?|txt|rtf|csv|exe|key|pp(s|t|tx)|7z|pkg|rar|gz|zip|avi|mov|mp4|mpe?g|wmv|midi?|mp3|wav|wma)$/;
7651

77-
// Adds listener to existing anchor tags
78-
const links = Array.from(document.getElementsByTagName('a'));
79-
links.forEach(addFileDownloadListener);
52+
const addFileDownloadListener = (a: HTMLAnchorElement) => {
53+
let url: URL;
54+
try {
55+
// eslint-disable-next-line no-restricted-globals
56+
url = new URL(a.href, window.location.href);
57+
} catch {
58+
/* istanbul ignore next */
59+
return;
60+
}
61+
const result = ext.exec(url.href);
62+
const fileExtension = result?.[1];
8063

81-
// Adds listener to anchor tags added after initial load
82-
/* istanbul ignore else */
83-
if (typeof MutationObserver !== 'undefined') {
84-
observer = new MutationObserver((mutations) => {
85-
mutations.forEach((mutation) => {
86-
mutation.addedNodes.forEach((node) => {
87-
if (node.nodeName === 'A') {
88-
addFileDownloadListener(node as HTMLAnchorElement);
89-
}
90-
if ('querySelectorAll' in node && typeof node.querySelectorAll === 'function') {
91-
Array.from(node.querySelectorAll('a') as HTMLAnchorElement[]).map(addFileDownloadListener);
64+
if (fileExtension) {
65+
addEventListener(a, 'click', () => {
66+
if (fileExtension) {
67+
amplitude.track(DEFAULT_FILE_DOWNLOAD_EVENT, {
68+
[FILE_EXTENSION]: fileExtension,
69+
[FILE_NAME]: url.pathname,
70+
[LINK_ID]: a.id,
71+
[LINK_TEXT]: a.text,
72+
[LINK_URL]: a.href,
73+
});
9274
}
9375
});
76+
}
77+
};
78+
79+
const ext =
80+
/\.(pdf|xlsx?|docx?|txt|rtf|csv|exe|key|pp(s|t|tx)|7z|pkg|rar|gz|zip|avi|mov|mp4|mpe?g|wmv|midi?|mp3|wav|wma)$/;
81+
82+
// Adds listener to existing anchor tags
83+
const links = Array.from(document.getElementsByTagName('a'));
84+
links.forEach(addFileDownloadListener);
85+
86+
// Adds listener to anchor tags added after initial load
87+
/* istanbul ignore else */
88+
if (typeof MutationObserver !== 'undefined') {
89+
observer = new MutationObserver((mutations) => {
90+
mutations.forEach((mutation) => {
91+
mutation.addedNodes.forEach((node) => {
92+
if (node.nodeName === 'A') {
93+
addFileDownloadListener(node as HTMLAnchorElement);
94+
}
95+
if ('querySelectorAll' in node && typeof node.querySelectorAll === 'function') {
96+
Array.from(node.querySelectorAll('a') as HTMLAnchorElement[]).map(addFileDownloadListener);
97+
}
98+
});
99+
});
94100
});
95-
});
96101

97-
observer.observe(document.body, {
98-
subtree: true,
99-
childList: true,
100-
});
101-
}
102+
observer.observe(document.body, {
103+
subtree: true,
104+
childList: true,
105+
});
106+
}
107+
});
102108
};
103109
const execute = async (event: Event) => event;
104110
const teardown = async () => {

packages/analytics-browser/src/plugins/form-interaction-tracking.ts

Lines changed: 68 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -38,77 +38,83 @@ export const formInteractionTracking = (): EnrichmentPlugin => {
3838
const name = '@amplitude/plugin-form-interaction-tracking-browser';
3939
const type = 'enrichment';
4040
const setup = async (config: BrowserConfig, amplitude: BrowserClient) => {
41-
/* istanbul ignore if */
42-
if (!amplitude) {
43-
// TODO: Add required minimum version of @amplitude/analytics-browser
44-
config.loggerProvider.warn(
45-
'Form interaction tracking requires a later version of @amplitude/analytics-browser. Form interaction events are not tracked.',
46-
);
47-
return;
48-
}
49-
50-
/* istanbul ignore if */
51-
if (typeof document === 'undefined') {
52-
return;
53-
}
54-
55-
const addFormInteractionListener = (form: HTMLFormElement) => {
56-
let hasFormChanged = false;
57-
58-
addEventListener(form, 'change', () => {
59-
if (!hasFormChanged) {
60-
amplitude.track(DEFAULT_FORM_START_EVENT, {
61-
[FORM_ID]: stringOrUndefined(form.id),
62-
[FORM_NAME]: stringOrUndefined(form.name),
63-
[FORM_DESTINATION]: form.action,
64-
});
65-
}
66-
hasFormChanged = true;
67-
});
41+
// The form interaction plugin observes changes in the dom. For this to work correctly, the observer can only be setup
42+
// after the body is built. When Amplitud gets initialized in a script tag, the body tag is still unavailable. So register this
43+
// only after the window is loaded
44+
// eslint-disable-next-line no-restricted-globals
45+
window.addEventListener('load', function () {
46+
/* istanbul ignore if */
47+
if (!amplitude) {
48+
// TODO: Add required minimum version of @amplitude/analytics-browser
49+
config.loggerProvider.warn(
50+
'Form interaction tracking requires a later version of @amplitude/analytics-browser. Form interaction events are not tracked.',
51+
);
52+
return;
53+
}
54+
55+
/* istanbul ignore if */
56+
if (typeof document === 'undefined') {
57+
return;
58+
}
59+
60+
const addFormInteractionListener = (form: HTMLFormElement) => {
61+
let hasFormChanged = false;
62+
63+
addEventListener(form, 'change', () => {
64+
if (!hasFormChanged) {
65+
amplitude.track(DEFAULT_FORM_START_EVENT, {
66+
[FORM_ID]: stringOrUndefined(form.id),
67+
[FORM_NAME]: stringOrUndefined(form.name),
68+
[FORM_DESTINATION]: form.action,
69+
});
70+
}
71+
hasFormChanged = true;
72+
});
6873

69-
addEventListener(form, 'submit', () => {
70-
if (!hasFormChanged) {
71-
amplitude.track(DEFAULT_FORM_START_EVENT, {
74+
addEventListener(form, 'submit', () => {
75+
if (!hasFormChanged) {
76+
amplitude.track(DEFAULT_FORM_START_EVENT, {
77+
[FORM_ID]: stringOrUndefined(form.id),
78+
[FORM_NAME]: stringOrUndefined(form.name),
79+
[FORM_DESTINATION]: form.action,
80+
});
81+
}
82+
83+
amplitude.track(DEFAULT_FORM_SUBMIT_EVENT, {
7284
[FORM_ID]: stringOrUndefined(form.id),
7385
[FORM_NAME]: stringOrUndefined(form.name),
7486
[FORM_DESTINATION]: form.action,
7587
});
76-
}
77-
78-
amplitude.track(DEFAULT_FORM_SUBMIT_EVENT, {
79-
[FORM_ID]: stringOrUndefined(form.id),
80-
[FORM_NAME]: stringOrUndefined(form.name),
81-
[FORM_DESTINATION]: form.action,
88+
hasFormChanged = false;
8289
});
83-
hasFormChanged = false;
84-
});
85-
};
86-
87-
// Adds listener to existing anchor tags
88-
const forms = Array.from(document.getElementsByTagName('form'));
89-
forms.forEach(addFormInteractionListener);
90-
91-
// Adds listener to anchor tags added after initial load
92-
/* istanbul ignore else */
93-
if (typeof MutationObserver !== 'undefined') {
94-
observer = new MutationObserver((mutations) => {
95-
mutations.forEach((mutation) => {
96-
mutation.addedNodes.forEach((node) => {
97-
if (node.nodeName === 'FORM') {
98-
addFormInteractionListener(node as HTMLFormElement);
99-
}
100-
if ('querySelectorAll' in node && typeof node.querySelectorAll === 'function') {
101-
Array.from(node.querySelectorAll('form') as HTMLFormElement[]).map(addFormInteractionListener);
102-
}
90+
};
91+
92+
// Adds listener to existing anchor tags
93+
const forms = Array.from(document.getElementsByTagName('form'));
94+
forms.forEach(addFormInteractionListener);
95+
96+
// Adds listener to anchor tags added after initial load
97+
/* istanbul ignore else */
98+
if (typeof MutationObserver !== 'undefined') {
99+
observer = new MutationObserver((mutations) => {
100+
mutations.forEach((mutation) => {
101+
mutation.addedNodes.forEach((node) => {
102+
if (node.nodeName === 'FORM') {
103+
addFormInteractionListener(node as HTMLFormElement);
104+
}
105+
if ('querySelectorAll' in node && typeof node.querySelectorAll === 'function') {
106+
Array.from(node.querySelectorAll('form') as HTMLFormElement[]).map(addFormInteractionListener);
107+
}
108+
});
103109
});
104110
});
105-
});
106111

107-
observer.observe(document.body, {
108-
subtree: true,
109-
childList: true,
110-
});
111-
}
112+
observer.observe(document.body, {
113+
subtree: true,
114+
childList: true,
115+
});
116+
}
117+
});
112118
};
113119
const execute = async (event: Event) => event;
114120
const teardown = async () => {

0 commit comments

Comments
 (0)