@phantomstudios/ft-lib 0.4.1-rc1 → 0.5.0-rc10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/CODEOWNERS +1 -1
- package/.github/dependabot.yml +11 -0
- package/.github/workflows/pr.yml +4 -1
- package/.github/workflows/release.yml +9 -2
- package/.nvmrc +1 -1
- package/CODE_OF_CONDUCT.md +1 -1
- package/CONTRIBUTING.md +3 -5
- package/SUPPORT.md +3 -3
- package/eslint.config.mjs +91 -0
- package/jest.config.js +1 -6
- package/lib/FTTracking/index.d.ts +3 -0
- package/lib/FTTracking/index.d.ts.map +1 -1
- package/lib/FTTracking/index.js +14 -1
- package/lib/FTTracking/index.js.map +1 -1
- package/lib/cmp/loadFtCmp.d.ts +3 -0
- package/lib/cmp/loadFtCmp.d.ts.map +1 -0
- package/lib/cmp/loadFtCmp.js +31 -0
- package/lib/cmp/loadFtCmp.js.map +1 -0
- package/lib/consentMonitor/index.d.ts +12 -12
- package/lib/consentMonitor/index.d.ts.map +1 -1
- package/lib/consentMonitor/index.js +111 -68
- package/lib/consentMonitor/index.js.map +1 -1
- package/lib/gaTracker/index.js +1 -2
- package/lib/gaTracker/index.js.map +1 -1
- package/lib/index.d.ts +7 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +7 -4
- package/lib/index.js.map +1 -1
- package/lib/oTracker/index.js.map +1 -1
- package/lib/permutiveVideoUtils/index.d.ts.map +1 -1
- package/lib/permutiveVideoUtils/index.js +1 -1
- package/lib/permutiveVideoUtils/index.js.map +1 -1
- package/lib/reactPlayerTracking/index.d.ts.map +1 -1
- package/lib/reactPlayerTracking/index.js +1 -1
- package/lib/reactPlayerTracking/index.js.map +1 -1
- package/lib/utils/attention.js.map +1 -1
- package/lib/utils/cookies.js +1 -2
- package/lib/utils/cookies.js.map +1 -1
- package/lib/utils/getTrace.js.map +1 -1
- package/lib/utils/intersectionObserverPolyfill.d.ts +1 -0
- package/lib/utils/intersectionObserverPolyfill.js +1 -0
- package/lib/utils/intersectionObserverPolyfill.js.map +1 -1
- package/lib/utils/scroll.js.map +1 -1
- package/lib/utils/yupValidator.d.ts.map +1 -1
- package/lib/utils/yupValidator.js +2 -2
- package/lib/utils/yupValidator.js.map +1 -1
- package/lib/ytIframeTracking/index.d.ts +0 -1
- package/lib/ytIframeTracking/index.d.ts.map +1 -1
- package/lib/ytIframeTracking/index.js.map +1 -1
- package/package.json +27 -17
- package/src/FTTracking/index.ts +21 -4
- package/src/cmp/loadFtCmp.ts +28 -0
- package/src/consentMonitor/index.ts +108 -85
- package/src/gaTracker/index.ts +4 -4
- package/src/index.ts +10 -3
- package/src/oTracker/index.ts +5 -5
- package/src/permutiveVideoUtils/index.ts +2 -2
- package/src/reactPlayerTracking/index.ts +4 -4
- package/src/types.d.ts +24 -5
- package/src/utils/attention.js +9 -9
- package/src/utils/getTrace.js +2 -2
- package/src/utils/scroll.ts +2 -2
- package/src/utils/yupValidator.ts +6 -6
- package/src/ytIframeTracking/index.ts +9 -9
- package/test/index.test.tsx +13 -12
- package/tsconfig.json +3 -1
- package/.eslintrc +0 -58
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function loadFtCmpScript(): Promise<void> {
|
|
2
|
+
if (document.getElementById("ft-cmp-loader")) {
|
|
3
|
+
return Promise.resolve();
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
return new Promise<void>((resolve, reject) => {
|
|
7
|
+
const script: HTMLScriptElement = document.createElement("script");
|
|
8
|
+
script.id = "ft-cmp-loader";
|
|
9
|
+
script.async = true;
|
|
10
|
+
script.src = "https://consent-notice.ft.com/cmp.js";
|
|
11
|
+
script.referrerPolicy = window.location.hostname.endsWith(".ft.com")
|
|
12
|
+
? "" // production hosts
|
|
13
|
+
: "no-referrer-when-downgrade"; // localhost / preview
|
|
14
|
+
|
|
15
|
+
script.onload = () => resolve();
|
|
16
|
+
script.onerror = () =>
|
|
17
|
+
reject(new Error("Sourcepoint CMP script failed to load"));
|
|
18
|
+
|
|
19
|
+
document.head.appendChild(script);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function enqueueCmpCallback(cb: () => void): void {
|
|
24
|
+
if (!window._sp_) window._sp_ = {};
|
|
25
|
+
if (!window._sp_queue) window._sp_queue = [];
|
|
26
|
+
|
|
27
|
+
window._sp_queue!.push(cb);
|
|
28
|
+
}
|
|
@@ -1,120 +1,143 @@
|
|
|
1
|
+
import { interceptManageCookiesLinks } from "@financial-times/cmp-client";
|
|
1
2
|
import Debug from "debug";
|
|
2
|
-
|
|
3
|
+
|
|
4
|
+
import { enqueueCmpCallback, loadFtCmpScript } from "../cmp/loadFtCmp";
|
|
5
|
+
|
|
6
|
+
const debug = Debug("@phantomstudios/ft-lib/consentMonitor");
|
|
3
7
|
|
|
4
8
|
const DEFAULT_DEV_HOSTS = ["localhost", "phq", ".app", "preview"];
|
|
5
9
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
interface ConsentReadyInfo {
|
|
11
|
+
consentedToAll: boolean;
|
|
12
|
+
}
|
|
13
|
+
type ConsentReadyHandler = (
|
|
14
|
+
legislation: string,
|
|
15
|
+
uuid: string,
|
|
16
|
+
tcData: unknown,
|
|
17
|
+
info: ConsentReadyInfo,
|
|
18
|
+
) => void;
|
|
19
|
+
type MessageChoiceHandler = (
|
|
20
|
+
legislation: string,
|
|
21
|
+
choiceId: number,
|
|
22
|
+
choiceTypeId: number,
|
|
23
|
+
) => void;
|
|
12
24
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
this._devHosts = devHosts.concat(DEFAULT_DEV_HOSTS);
|
|
16
|
-
} else if (devHosts === undefined) {
|
|
17
|
-
this._devHosts = DEFAULT_DEV_HOSTS;
|
|
18
|
-
} else {
|
|
19
|
-
this._devHosts = DEFAULT_DEV_HOSTS;
|
|
20
|
-
this._devHosts.push(devHosts);
|
|
21
|
-
}
|
|
25
|
+
const CMP_CHOICE_ACCEPT_ALL = 11;
|
|
26
|
+
const CMP_CHOICE_REJECT_ALL = 13;
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
export class ConsentMonitor {
|
|
29
|
+
private _consent = false;
|
|
30
|
+
private _devHosts: string[];
|
|
31
|
+
private _isDevEnvironment = false;
|
|
32
|
+
private _isInitialized = false;
|
|
26
33
|
|
|
27
|
-
get consent(): boolean {
|
|
34
|
+
public get consent(): boolean {
|
|
28
35
|
return this._consent;
|
|
29
36
|
}
|
|
30
|
-
|
|
31
|
-
get devHosts(): string[] | string {
|
|
37
|
+
public get devHosts(): string[] {
|
|
32
38
|
return this._devHosts;
|
|
33
39
|
}
|
|
34
|
-
|
|
35
|
-
get isDevEnvironment(): boolean {
|
|
40
|
+
public get isDevEnvironment(): boolean {
|
|
36
41
|
return this._isDevEnvironment;
|
|
37
42
|
}
|
|
38
|
-
|
|
39
|
-
get isInitialized(): boolean {
|
|
43
|
+
public get isInitialized(): boolean {
|
|
40
44
|
return this._isInitialized;
|
|
41
45
|
}
|
|
42
46
|
|
|
47
|
+
public get userHasConsented(): boolean {
|
|
48
|
+
return this._consent;
|
|
49
|
+
}
|
|
50
|
+
|
|
43
51
|
getCookieValue = (name: string) =>
|
|
44
52
|
document.cookie.match("(^|;)\\s*" + name + "\\s*=\\s*([^;]+)")?.pop() || "";
|
|
45
53
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
constructor(hostname?: string, devHosts?: string[] | string) {
|
|
55
|
+
if (Array.isArray(devHosts)) {
|
|
56
|
+
this._devHosts = [...devHosts, ...DEFAULT_DEV_HOSTS];
|
|
57
|
+
} else if (devHosts === undefined) {
|
|
58
|
+
this._devHosts = [...DEFAULT_DEV_HOSTS];
|
|
59
|
+
} else {
|
|
60
|
+
this._devHosts = [...DEFAULT_DEV_HOSTS, devHosts];
|
|
61
|
+
}
|
|
49
62
|
|
|
50
|
-
|
|
51
|
-
this._devHosts.
|
|
52
|
-
(
|
|
53
|
-
this._hostname.includes(devHost) && this.setDevCookieHandler()
|
|
63
|
+
const hostToCheck = hostname ?? window.location.hostname;
|
|
64
|
+
this._isDevEnvironment = this._devHosts.some((h) =>
|
|
65
|
+
hostToCheck.includes(h),
|
|
54
66
|
);
|
|
55
|
-
};
|
|
56
67
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
loadFtCmpScript()
|
|
69
|
+
.then(() => {
|
|
70
|
+
this.attachCmpListeners();
|
|
71
|
+
// const propertyConfig = window.location.hostname.endsWith("ft.com")
|
|
72
|
+
// ? properties["FT_DOTCOM_PROD"]
|
|
73
|
+
// : properties["FT_DOTCOM_TEST"];
|
|
74
|
+
|
|
75
|
+
// initialize CMP - duplicate?
|
|
76
|
+
//initSourcepointCmp({ propertyConfig });
|
|
77
|
+
// use cmp client lib to intercept footer 'Manage Cookies' links (opens privacy modal)
|
|
78
|
+
// Note, function requires very specific link: text = 'Manage Cookies' and href = 'https://ft.com/preferences/manage-cookies'
|
|
79
|
+
})
|
|
80
|
+
.catch((err) => console.error(err));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private attachCmpListeners(): void {
|
|
84
|
+
enqueueCmpCallback(() => {
|
|
85
|
+
const onReady: ConsentReadyHandler = (_l, _u, _t, info) => {
|
|
86
|
+
debug("onConsentReady:", info);
|
|
87
|
+
if (info.consentedToAll) {
|
|
88
|
+
this.enablePermutive();
|
|
64
89
|
} else {
|
|
65
|
-
this.
|
|
90
|
+
this.disablePermutive();
|
|
66
91
|
}
|
|
67
|
-
|
|
68
|
-
} else if (
|
|
69
|
-
this.getCookieValue("FTConsent").includes(
|
|
70
|
-
"behaviouraladsOnsite%3Aon"
|
|
71
|
-
) &&
|
|
72
|
-
!this.consent
|
|
73
|
-
) {
|
|
74
|
-
debug("setting permutive tracking consent: on");
|
|
75
|
-
this.permutiveConsentOn();
|
|
76
|
-
} else if (
|
|
77
|
-
!this.getCookieValue("FTConsent").includes(
|
|
78
|
-
"behaviouraladsOnsite%3Aon"
|
|
79
|
-
) &&
|
|
80
|
-
this.consent
|
|
81
|
-
) {
|
|
82
|
-
debug("setting permutive tracking consent: off");
|
|
83
|
-
this.permutiveConsentOff();
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
};
|
|
92
|
+
};
|
|
87
93
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const onCookieMessageAct = () => {
|
|
95
|
-
debug("setting development FT consent cookies");
|
|
96
|
-
document.cookie = "FTConsent=behaviouraladsOnsite%3Aon";
|
|
97
|
-
document.cookie = "FTCookieConsentGDPR=true";
|
|
98
|
-
oCookieMessage.removeEventListener(
|
|
99
|
-
"oCookieMessage.act",
|
|
100
|
-
onCookieMessageAct,
|
|
101
|
-
false
|
|
102
|
-
);
|
|
94
|
+
const onChoice: MessageChoiceHandler = (_l, _c, typeId) => {
|
|
95
|
+
debug("onMessageChoiceSelect:", typeId);
|
|
96
|
+
if (typeId === CMP_CHOICE_ACCEPT_ALL) this.enablePermutive();
|
|
97
|
+
else if (typeId === CMP_CHOICE_REJECT_ALL) this.disablePermutive();
|
|
98
|
+
// banner updated - check new cookie value to fire consent_update event
|
|
99
|
+
setTimeout(this.cookieConsentTest, 3000);
|
|
103
100
|
};
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
101
|
+
window._sp_.addEventListener?.("onConsentReady", onReady);
|
|
102
|
+
window._sp_.addEventListener?.("onMessageChoiceSelect", onChoice);
|
|
103
|
+
this._isInitialized = true;
|
|
104
|
+
interceptManageCookiesLinks();
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
private enablePermutive(): void {
|
|
109
|
+
if (this._consent) return;
|
|
110
|
+
debug("Permutive consent: ON");
|
|
111
|
+
window.permutive?.consent({
|
|
110
112
|
opt_in: true,
|
|
111
113
|
token: "behaviouraladsOnsite:on",
|
|
112
114
|
});
|
|
113
115
|
this._consent = true;
|
|
114
|
-
}
|
|
116
|
+
}
|
|
115
117
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
+
private disablePermutive(): void {
|
|
119
|
+
if (!this._consent) return;
|
|
120
|
+
debug("Permutive consent: OFF");
|
|
121
|
+
window.permutive?.consent({ opt_in: false });
|
|
118
122
|
this._consent = false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
//check for FTConsent - cookiesOnSite to trigger custom consent_update event for GTM tags (banner updated)
|
|
126
|
+
cookieConsentTest = () => {
|
|
127
|
+
if (!this._isInitialized || !window || !window.dataLayer) return;
|
|
128
|
+
if (this.getCookieValue("FTConsent").includes("cookiesOnsite%3Aon")) {
|
|
129
|
+
//send consent_update event
|
|
130
|
+
window.dataLayer.push({
|
|
131
|
+
event: "consent_update",
|
|
132
|
+
consent: true,
|
|
133
|
+
});
|
|
134
|
+
} else {
|
|
135
|
+
window.dataLayer.push({
|
|
136
|
+
event: "consent_update",
|
|
137
|
+
consent: false,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
119
140
|
};
|
|
120
141
|
}
|
|
142
|
+
|
|
143
|
+
export { ConsentMonitor as consentMonitor };
|
package/src/gaTracker/index.ts
CHANGED
|
@@ -55,13 +55,13 @@ export class gaTracker {
|
|
|
55
55
|
this.GTMEventDispatcher(
|
|
56
56
|
category,
|
|
57
57
|
action,
|
|
58
|
-
label || window.location.pathname //use pathname if label section has not been defined
|
|
58
|
+
label || window.location.pathname, //use pathname if label section has not been defined
|
|
59
59
|
);
|
|
60
60
|
} else {
|
|
61
61
|
this.GtagEventDispatcher(
|
|
62
62
|
category,
|
|
63
63
|
action,
|
|
64
|
-
label || window.location.pathname
|
|
64
|
+
label || window.location.pathname,
|
|
65
65
|
);
|
|
66
66
|
}
|
|
67
67
|
}
|
|
@@ -70,7 +70,7 @@ export class gaTracker {
|
|
|
70
70
|
|
|
71
71
|
setUpFTNavigationLinksTracking() {
|
|
72
72
|
const FTNavigationLinks = Array.from(
|
|
73
|
-
document.querySelectorAll(".o-header__drawer-menu-link")
|
|
73
|
+
document.querySelectorAll(".o-header__drawer-menu-link"),
|
|
74
74
|
);
|
|
75
75
|
if (FTNavigationLinks) {
|
|
76
76
|
FTNavigationLinks.map((navLink) => {
|
|
@@ -119,7 +119,7 @@ export class gaTracker {
|
|
|
119
119
|
|
|
120
120
|
setupFooterLinksTracking() {
|
|
121
121
|
const FTFooterLinks = Array.from(
|
|
122
|
-
document.querySelectorAll(".o-footer__legal-links li a")
|
|
122
|
+
document.querySelectorAll(".o-footer__legal-links li a"),
|
|
123
123
|
);
|
|
124
124
|
if (FTFooterLinks) {
|
|
125
125
|
FTFooterLinks.forEach((footerLink) => {
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
import { FTTracking, TrackingOptions } from "./FTTracking";
|
|
2
|
+
import { ConfigType, OrigamiEventType } from "./utils/yupValidator";
|
|
3
|
+
|
|
4
|
+
export { FTTracking };
|
|
5
|
+
export type { TrackingOptions };
|
|
6
|
+
|
|
7
|
+
export { loadFtCmpScript, enqueueCmpCallback } from "./cmp/loadFtCmp";
|
|
8
|
+
|
|
9
|
+
export { ConsentMonitor as consentMonitor } from "./consentMonitor";
|
|
2
10
|
export { permutiveVideoUtils } from "./permutiveVideoUtils";
|
|
3
11
|
export { reactPlayerTracking } from "./reactPlayerTracking";
|
|
4
12
|
export { gaTracker } from "./gaTracker";
|
|
5
13
|
export { oTracker } from "./oTracker";
|
|
6
14
|
export { ytIframeTracking } from "./ytIframeTracking";
|
|
7
|
-
export {
|
|
8
|
-
export { ConfigType, OrigamiEventType } from "./utils/yupValidator";
|
|
15
|
+
export type { ConfigType, OrigamiEventType };
|
package/src/oTracker/index.ts
CHANGED
|
@@ -21,12 +21,12 @@ export class oTracker {
|
|
|
21
21
|
constructor(config: ConfigType, options: TrackingOptions) {
|
|
22
22
|
this._config = parseConfig(
|
|
23
23
|
config,
|
|
24
|
-
options.disableAppFormatTransform || false
|
|
24
|
+
options.disableAppFormatTransform || false,
|
|
25
25
|
);
|
|
26
26
|
validateConfig(
|
|
27
27
|
this._config,
|
|
28
28
|
options.disableAppFormatTransform || false,
|
|
29
|
-
options.logValidationErrors || false
|
|
29
|
+
options.logValidationErrors || false,
|
|
30
30
|
);
|
|
31
31
|
this._config.source_id = oTracking.getRootID();
|
|
32
32
|
this.options = options;
|
|
@@ -68,7 +68,7 @@ export class oTracker {
|
|
|
68
68
|
detail.app = this.config.app;
|
|
69
69
|
detail.product = this.config.product;
|
|
70
70
|
document.body.dispatchEvent(
|
|
71
|
-
new CustomEvent("oTracking.event", { detail, bubbles: true })
|
|
71
|
+
new CustomEvent("oTracking.event", { detail, bubbles: true }),
|
|
72
72
|
);
|
|
73
73
|
} else {
|
|
74
74
|
throw "Invalid event type";
|
|
@@ -92,7 +92,7 @@ export class oTracker {
|
|
|
92
92
|
category: "brandedContent",
|
|
93
93
|
},
|
|
94
94
|
bubbles: true,
|
|
95
|
-
})
|
|
95
|
+
}),
|
|
96
96
|
);
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -104,7 +104,7 @@ export class oTracker {
|
|
|
104
104
|
(elm) =>
|
|
105
105
|
elm instanceof HTMLElement &&
|
|
106
106
|
elm.hasAttribute &&
|
|
107
|
-
elm.hasAttribute("data-o-event")
|
|
107
|
+
elm.hasAttribute("data-o-event"),
|
|
108
108
|
);
|
|
109
109
|
if (trackableElement && trackableElement instanceof HTMLElement) {
|
|
110
110
|
const data = trackableElement.getAttribute("data-o-event");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Debug from "debug";
|
|
2
|
-
const debug = Debug("@
|
|
2
|
+
const debug = Debug("@phantomstudios/ft-lib");
|
|
3
3
|
export class permutiveVideoUtils {
|
|
4
4
|
protected progressPermutiveMilestones: number[] = [0, 0.25, 0.5, 0.75, 1];
|
|
5
5
|
protected _campaign: string;
|
|
@@ -26,7 +26,7 @@ export class permutiveVideoUtils {
|
|
|
26
26
|
emitPermutiveProgressEvent = (
|
|
27
27
|
duration: number,
|
|
28
28
|
currentTime: number,
|
|
29
|
-
interval: any = undefined
|
|
29
|
+
interval: any = undefined,
|
|
30
30
|
) => {
|
|
31
31
|
const progress = currentTime / (Math.floor(duration) - 1);
|
|
32
32
|
|
|
@@ -3,7 +3,7 @@ import Debug from "debug";
|
|
|
3
3
|
import { FTTracking } from "../FTTracking";
|
|
4
4
|
import { permutiveVideoUtils } from "../permutiveVideoUtils";
|
|
5
5
|
import { OrigamiEventType } from "../utils/yupValidator";
|
|
6
|
-
const debug = Debug("@
|
|
6
|
+
const debug = Debug("@phantomstudios/ft-lib");
|
|
7
7
|
interface PlayerProgressEvent {
|
|
8
8
|
played: number;
|
|
9
9
|
loaded: number;
|
|
@@ -31,7 +31,7 @@ export class reactPlayerTracking {
|
|
|
31
31
|
this.permutiveTracker = new permutiveVideoUtils(
|
|
32
32
|
this.FTTracker.config.campaign,
|
|
33
33
|
this.videoTitle,
|
|
34
|
-
this.videoUrl
|
|
34
|
+
this.videoUrl,
|
|
35
35
|
);
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -79,7 +79,7 @@ export class reactPlayerTracking {
|
|
|
79
79
|
this.playedSeconds = reactPlayerProgress.playedSeconds;
|
|
80
80
|
if (this.duration > 0) {
|
|
81
81
|
this.playedPercent = Math.floor(
|
|
82
|
-
(Math.ceil(reactPlayerProgress.playedSeconds) / this.duration) * 100
|
|
82
|
+
(Math.ceil(reactPlayerProgress.playedSeconds) / this.duration) * 100,
|
|
83
83
|
);
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -99,7 +99,7 @@ export class reactPlayerTracking {
|
|
|
99
99
|
debug("edit permutive progress event- " + this.playedSeconds);
|
|
100
100
|
this.permutiveTracker?.emitPermutiveProgressEvent(
|
|
101
101
|
this.duration,
|
|
102
|
-
this.playedSeconds
|
|
102
|
+
this.playedSeconds,
|
|
103
103
|
);
|
|
104
104
|
};
|
|
105
105
|
}
|
package/src/types.d.ts
CHANGED
|
@@ -1,7 +1,31 @@
|
|
|
1
1
|
declare module "@financial-times/o-tracking";
|
|
2
2
|
declare module "@financial-times/o-viewport";
|
|
3
3
|
|
|
4
|
+
interface ConsentOptions {
|
|
5
|
+
opt_in?: boolean;
|
|
6
|
+
token?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
declare module "@financial-times/cmp-client" {
|
|
10
|
+
export function interceptManageCookiesLinks(): void;
|
|
11
|
+
export const properties: {
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
};
|
|
14
|
+
export function initSourcepointCmp(options?: {
|
|
15
|
+
propertyConfig?: any;
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
interface SourcepointCmpAPI {
|
|
19
|
+
queue?: Array<() => void>;
|
|
20
|
+
addEventListener?: (
|
|
21
|
+
eventName: string,
|
|
22
|
+
|
|
23
|
+
callback: (...args: any[]) => void,
|
|
24
|
+
) => void;
|
|
25
|
+
}
|
|
4
26
|
interface Window {
|
|
27
|
+
_sp_: SourcepointCmpAPI;
|
|
28
|
+
_sp_queue?: Array<() => void>;
|
|
5
29
|
dataLayer: any;
|
|
6
30
|
gtag: any;
|
|
7
31
|
permutive: {
|
|
@@ -13,11 +37,6 @@ interface Window {
|
|
|
13
37
|
isOvideoPlayer?: boolean; //channels only
|
|
14
38
|
}
|
|
15
39
|
|
|
16
|
-
interface ConsentOptions {
|
|
17
|
-
opt_in?: boolean;
|
|
18
|
-
token?: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
40
|
interface CallableConsent {
|
|
22
41
|
({}: ConsentOptions): void;
|
|
23
42
|
}
|
package/src/utils/attention.js
CHANGED
|
@@ -31,13 +31,13 @@ export class Attention {
|
|
|
31
31
|
//Add events for all the other Attention events
|
|
32
32
|
for (let i = 0; i < ATTENTION_EVENTS.length; i++) {
|
|
33
33
|
window.addEventListener(ATTENTION_EVENTS[i], (ev) =>
|
|
34
|
-
this.startAttention(ev)
|
|
34
|
+
this.startAttention(ev),
|
|
35
35
|
);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
for (let i = 0; i < UNATTENTION_EVENTS.length; i++) {
|
|
39
39
|
window.addEventListener(UNATTENTION_EVENTS[i], (ev) =>
|
|
40
|
-
this.endAttention(ev)
|
|
40
|
+
this.endAttention(ev),
|
|
41
41
|
);
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -46,7 +46,7 @@ export class Attention {
|
|
|
46
46
|
document.body.addEventListener(
|
|
47
47
|
"oViewport.visibility",
|
|
48
48
|
(ev) => this.handleVisibilityChange(ev),
|
|
49
|
-
false
|
|
49
|
+
false,
|
|
50
50
|
);
|
|
51
51
|
|
|
52
52
|
this.addVideoEvents();
|
|
@@ -79,14 +79,14 @@ export class Attention {
|
|
|
79
79
|
}
|
|
80
80
|
this.attentionTimeout = setTimeout(
|
|
81
81
|
() => this.endAttention(),
|
|
82
|
-
ATTENTION_INTERVAL
|
|
82
|
+
ATTENTION_INTERVAL,
|
|
83
83
|
);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
startConstantAttention() {
|
|
87
87
|
this.constantAttentionInterval = setInterval(
|
|
88
88
|
() => this.startAttention(),
|
|
89
|
-
ATTENTION_INTERVAL
|
|
89
|
+
ATTENTION_INTERVAL,
|
|
90
90
|
);
|
|
91
91
|
}
|
|
92
92
|
|
|
@@ -99,7 +99,7 @@ export class Attention {
|
|
|
99
99
|
if (this.startAttentionTime) {
|
|
100
100
|
this.endAttentionTime = new Date().getTime();
|
|
101
101
|
this.totalAttentionTime += Math.round(
|
|
102
|
-
(this.endAttentionTime - this.startAttentionTime) / 1000
|
|
102
|
+
(this.endAttentionTime - this.startAttentionTime) / 1000,
|
|
103
103
|
);
|
|
104
104
|
clearTimeout(this.attentionTimeout);
|
|
105
105
|
this.startAttentionTime = null;
|
|
@@ -117,13 +117,13 @@ export class Attention {
|
|
|
117
117
|
this.videoPlayers = document.getElementsByTagName("video");
|
|
118
118
|
for (let i = 0; i < this.videoPlayers.length; i++) {
|
|
119
119
|
this.videoPlayers[i].addEventListener("playing", (ev) =>
|
|
120
|
-
this.startConstantAttention(ev)
|
|
120
|
+
this.startConstantAttention(ev),
|
|
121
121
|
);
|
|
122
122
|
this.videoPlayers[i].addEventListener("pause", (ev) =>
|
|
123
|
-
this.endConstantAttention(ev)
|
|
123
|
+
this.endConstantAttention(ev),
|
|
124
124
|
);
|
|
125
125
|
this.videoPlayers[i].addEventListener("ended", (ev) =>
|
|
126
|
-
this.endConstantAttention(ev)
|
|
126
|
+
this.endConstantAttention(ev),
|
|
127
127
|
);
|
|
128
128
|
}
|
|
129
129
|
}
|
package/src/utils/getTrace.js
CHANGED
|
@@ -53,7 +53,7 @@ const getDomPathProps = (attrs, props) => {
|
|
|
53
53
|
// Collect any attribute that matches given strings.
|
|
54
54
|
attrs
|
|
55
55
|
.filter((attribute) =>
|
|
56
|
-
attribute.name.match(/^data-trackable|^data-o-|^aria-/i)
|
|
56
|
+
attribute.name.match(/^data-trackable|^data-o-|^aria-/i),
|
|
57
57
|
)
|
|
58
58
|
.forEach((attribute) => {
|
|
59
59
|
props[attribute.name] = attribute.value;
|
|
@@ -104,7 +104,7 @@ function getTrace(el) {
|
|
|
104
104
|
if (domPathProps["data-trackable"]) {
|
|
105
105
|
domPathProps = Object.assign(
|
|
106
106
|
domPathProps,
|
|
107
|
-
getSiblingsAndPosition(el, originalEl, selector)
|
|
107
|
+
getSiblingsAndPosition(el, originalEl, selector),
|
|
108
108
|
);
|
|
109
109
|
}
|
|
110
110
|
|
package/src/utils/scroll.ts
CHANGED
|
@@ -56,7 +56,7 @@ export class ScrollTracker {
|
|
|
56
56
|
|
|
57
57
|
intersectionCallback(
|
|
58
58
|
observer: IntersectionObserver,
|
|
59
|
-
changes: IntersectionObserverEntry[]
|
|
59
|
+
changes: IntersectionObserverEntry[],
|
|
60
60
|
) {
|
|
61
61
|
changes.forEach((change) => {
|
|
62
62
|
if (change.isIntersecting || change.intersectionRatio > 0) {
|
|
@@ -81,7 +81,7 @@ export class ScrollTracker {
|
|
|
81
81
|
this.gaTracker.GtagEventDispatcher(
|
|
82
82
|
"Scroll",
|
|
83
83
|
`scroll - ${percentagesViewed}%`,
|
|
84
|
-
window.location.pathname
|
|
84
|
+
window.location.pathname,
|
|
85
85
|
);
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -54,7 +54,7 @@ const configSchema = object({
|
|
|
54
54
|
.required()
|
|
55
55
|
.defined()
|
|
56
56
|
.transform((value) =>
|
|
57
|
-
unifyValuesTransform(value, disableAppFormatTransformValue)
|
|
57
|
+
unifyValuesTransform(value, disableAppFormatTransformValue),
|
|
58
58
|
)
|
|
59
59
|
.oneOf([
|
|
60
60
|
"Stream",
|
|
@@ -112,14 +112,14 @@ const origamiEventSchema = object({
|
|
|
112
112
|
.nullable()
|
|
113
113
|
.notRequired()
|
|
114
114
|
.when("category", {
|
|
115
|
-
is: "video" || "audio",
|
|
115
|
+
is: (value: string) => value === "video" || "audio",
|
|
116
116
|
then: () => number().required(),
|
|
117
117
|
}),
|
|
118
118
|
progress: number()
|
|
119
119
|
.nullable()
|
|
120
120
|
.notRequired()
|
|
121
121
|
.when("category", {
|
|
122
|
-
is: "video" || "audio",
|
|
122
|
+
is: (value: string) => value === "video" || "audio",
|
|
123
123
|
then: () => number().required(),
|
|
124
124
|
}),
|
|
125
125
|
});
|
|
@@ -129,7 +129,7 @@ export type OrigamiEventType = InferType<typeof origamiEventSchema>;
|
|
|
129
129
|
|
|
130
130
|
export const parseConfig = (
|
|
131
131
|
config: ConfigType,
|
|
132
|
-
disableAppFormatTransform: boolean
|
|
132
|
+
disableAppFormatTransform: boolean,
|
|
133
133
|
): ConfigType => {
|
|
134
134
|
disableAppFormatTransformValue = disableAppFormatTransform;
|
|
135
135
|
try {
|
|
@@ -155,7 +155,7 @@ export const parseConfig = (
|
|
|
155
155
|
export const validateConfig = (
|
|
156
156
|
config: ConfigType,
|
|
157
157
|
disableAppFormatTransform: boolean,
|
|
158
|
-
logValidationErrors: boolean
|
|
158
|
+
logValidationErrors: boolean,
|
|
159
159
|
): ValidationError[] | undefined => {
|
|
160
160
|
disableAppFormatTransformValue = disableAppFormatTransform;
|
|
161
161
|
try {
|
|
@@ -174,7 +174,7 @@ export const validateConfig = (
|
|
|
174
174
|
};
|
|
175
175
|
|
|
176
176
|
export const validateOrigamiEvent = (
|
|
177
|
-
config: OrigamiEventType
|
|
177
|
+
config: OrigamiEventType,
|
|
178
178
|
): ValidationError[] | undefined => {
|
|
179
179
|
try {
|
|
180
180
|
const parsedConfig = origamiEventSchema.cast(config);
|
|
@@ -17,7 +17,7 @@ export class ytIframeTracking {
|
|
|
17
17
|
this.videoUrl = videoUrl;
|
|
18
18
|
this.permutiveUtils = new permutiveVideoUtils(
|
|
19
19
|
this.FTTracker.config.campaign,
|
|
20
|
-
this.videoTitle
|
|
20
|
+
this.videoTitle,
|
|
21
21
|
);
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -27,13 +27,13 @@ export class ytIframeTracking {
|
|
|
27
27
|
emitProgressEvents = (
|
|
28
28
|
progress: number,
|
|
29
29
|
duration: number,
|
|
30
|
-
isYoutube: boolean
|
|
30
|
+
isYoutube: boolean,
|
|
31
31
|
) => {
|
|
32
32
|
while (progress >= this.progressMilestones[0]) {
|
|
33
33
|
this.FTTracker.gaEvent(
|
|
34
34
|
`Video${window.isOvideoPlayer ? ":fallback" : ""}`,
|
|
35
35
|
`${this.progressMilestones[0]}% watched`,
|
|
36
|
-
this.videoTitle
|
|
36
|
+
this.videoTitle,
|
|
37
37
|
);
|
|
38
38
|
if (isYoutube) {
|
|
39
39
|
this.FTTracker.oEvent({
|
|
@@ -103,7 +103,7 @@ export class ytIframeTracking {
|
|
|
103
103
|
this.permutiveUtils.emitPermutiveProgressEvent(
|
|
104
104
|
duration - 1,
|
|
105
105
|
currentTime,
|
|
106
|
-
this.videoProgressInterval
|
|
106
|
+
this.videoProgressInterval,
|
|
107
107
|
);
|
|
108
108
|
const progress = this.progressPercentage(duration, currentTime);
|
|
109
109
|
this.emitProgressEvents(progress, duration, true);
|
|
@@ -115,7 +115,7 @@ export class ytIframeTracking {
|
|
|
115
115
|
this.FTTracker.gaEvent(
|
|
116
116
|
`Video${window.isOvideoPlayer ? ":fallback" : ""}`,
|
|
117
117
|
`playing`,
|
|
118
|
-
this.videoTitle
|
|
118
|
+
this.videoTitle,
|
|
119
119
|
);
|
|
120
120
|
this.FTTracker.oEvent({
|
|
121
121
|
category: "video",
|
|
@@ -131,7 +131,7 @@ export class ytIframeTracking {
|
|
|
131
131
|
this.FTTracker.gaEvent(
|
|
132
132
|
`Video${window.isOvideoPlayer ? ":fallback" : ""}`,
|
|
133
133
|
`pause`,
|
|
134
|
-
`${this.videoTitle}
|
|
134
|
+
`${this.videoTitle}`,
|
|
135
135
|
);
|
|
136
136
|
this.FTTracker.oEvent({
|
|
137
137
|
category: "video",
|
|
@@ -146,7 +146,7 @@ export class ytIframeTracking {
|
|
|
146
146
|
this.FTTracker.gaEvent(
|
|
147
147
|
`Video${window.isOvideoPlayer ? ":fallback" : ""}`,
|
|
148
148
|
`100% watched`,
|
|
149
|
-
`${this.videoTitle}
|
|
149
|
+
`${this.videoTitle}`,
|
|
150
150
|
);
|
|
151
151
|
this.FTTracker.oEvent({
|
|
152
152
|
category: "video",
|
|
@@ -159,7 +159,7 @@ export class ytIframeTracking {
|
|
|
159
159
|
this.permutiveUtils.emitPermutiveProgressEvent(
|
|
160
160
|
currentTime,
|
|
161
161
|
currentTime,
|
|
162
|
-
this.videoProgressInterval
|
|
162
|
+
this.videoProgressInterval,
|
|
163
163
|
);
|
|
164
164
|
}
|
|
165
165
|
}
|
|
@@ -172,7 +172,7 @@ export class ytIframeTracking {
|
|
|
172
172
|
this.permutiveUtils.emitPermutiveProgressEvent(
|
|
173
173
|
duration - 1,
|
|
174
174
|
currentTime,
|
|
175
|
-
this.videoProgressInterval
|
|
175
|
+
this.videoProgressInterval,
|
|
176
176
|
);
|
|
177
177
|
}, 1000);
|
|
178
178
|
const progress = this.progressPercentage(duration, currentTime);
|