@phantomstudios/ft-lib 0.2.0
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/.babelrc +6 -0
- package/.eslintrc +58 -0
- package/.github/CODEOWNERS +1 -0
- package/.github/workflows/pr.yml +31 -0
- package/.github/workflows/release.yml +44 -0
- package/.nvmrc +1 -0
- package/.prettierignore +5 -0
- package/CODE_OF_CONDUCT.md +76 -0
- package/CONTRIBUTING.md +48 -0
- package/LICENSE +21 -0
- package/README.md +158 -0
- package/SUPPORT.md +4 -0
- package/jest.config.js +24 -0
- package/lib/FTTracking/index.d.ts +24 -0
- package/lib/FTTracking/index.d.ts.map +1 -0
- package/lib/FTTracking/index.js +69 -0
- package/lib/FTTracking/index.js.map +1 -0
- package/lib/consentMonitor/index.d.ts +19 -0
- package/lib/consentMonitor/index.d.ts.map +1 -0
- package/lib/consentMonitor/index.js +117 -0
- package/lib/consentMonitor/index.js.map +1 -0
- package/lib/gaTracker/index.d.ts +13 -0
- package/lib/gaTracker/index.d.ts.map +1 -0
- package/lib/gaTracker/index.js +120 -0
- package/lib/gaTracker/index.js.map +1 -0
- package/lib/index.d.ts +9 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +18 -0
- package/lib/index.js.map +1 -0
- package/lib/oTracker/index.d.ts +14 -0
- package/lib/oTracker/index.d.ts.map +1 -0
- package/lib/oTracker/index.js +114 -0
- package/lib/oTracker/index.js.map +1 -0
- package/lib/permutiveVideoUtils/index.d.ts +12 -0
- package/lib/permutiveVideoUtils/index.d.ts.map +1 -0
- package/lib/permutiveVideoUtils/index.js +59 -0
- package/lib/permutiveVideoUtils/index.js.map +1 -0
- package/lib/reactPlayerTracking/index.d.ts +30 -0
- package/lib/reactPlayerTracking/index.d.ts.map +1 -0
- package/lib/reactPlayerTracking/index.js +85 -0
- package/lib/reactPlayerTracking/index.js.map +1 -0
- package/lib/utils/attention.d.ts +20 -0
- package/lib/utils/attention.d.ts.map +1 -0
- package/lib/utils/attention.js +142 -0
- package/lib/utils/attention.js.map +1 -0
- package/lib/utils/cookies.d.ts +2 -0
- package/lib/utils/cookies.d.ts.map +1 -0
- package/lib/utils/cookies.js +14 -0
- package/lib/utils/cookies.js.map +1 -0
- package/lib/utils/getTrace.d.ts +6 -0
- package/lib/utils/getTrace.d.ts.map +1 -0
- package/lib/utils/getTrace.js +109 -0
- package/lib/utils/getTrace.js.map +1 -0
- package/lib/utils/intersectionObserverPolyfill.d.ts +1 -0
- package/lib/utils/intersectionObserverPolyfill.d.ts.map +1 -0
- package/lib/utils/intersectionObserverPolyfill.js +421 -0
- package/lib/utils/intersectionObserverPolyfill.js.map +1 -0
- package/lib/utils/scroll.d.ts +18 -0
- package/lib/utils/scroll.d.ts.map +1 -0
- package/lib/utils/scroll.js +75 -0
- package/lib/utils/scroll.js.map +1 -0
- package/lib/utils/yupValidator.d.ts +87 -0
- package/lib/utils/yupValidator.d.ts.map +1 -0
- package/lib/utils/yupValidator.js +189 -0
- package/lib/utils/yupValidator.js.map +1 -0
- package/lib/ytIframeTracking/index.d.ts +27 -0
- package/lib/ytIframeTracking/index.d.ts.map +1 -0
- package/lib/ytIframeTracking/index.js +165 -0
- package/lib/ytIframeTracking/index.js.map +1 -0
- package/package.json +75 -0
- package/src/FTTracking/index.ts +74 -0
- package/src/consentMonitor/index.ts +120 -0
- package/src/gaTracker/index.ts +136 -0
- package/src/index.ts +12 -0
- package/src/oTracker/index.ts +122 -0
- package/src/permutiveVideoUtils/index.ts +53 -0
- package/src/reactPlayerTracking/index.ts +105 -0
- package/src/types.d.ts +33 -0
- package/src/utils/attention.js +138 -0
- package/src/utils/cookies.js +9 -0
- package/src/utils/getTrace.js +122 -0
- package/src/utils/intersectionObserverPolyfill.js +470 -0
- package/src/utils/scroll.ts +94 -0
- package/src/utils/yupValidator.ts +205 -0
- package/src/ytIframeTracking/index.ts +210 -0
- package/test/index.test.tsx +63 -0
- package/tsconfig.json +26 -0
- package/tsconfig.test.json +6 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import {
|
|
2
|
+
object,
|
|
3
|
+
string,
|
|
4
|
+
number,
|
|
5
|
+
boolean,
|
|
6
|
+
mixed,
|
|
7
|
+
InferType,
|
|
8
|
+
ValidationError,
|
|
9
|
+
} from "yup";
|
|
10
|
+
|
|
11
|
+
//transform passed values to first character uppercase and replace spaces with underscores
|
|
12
|
+
const unifyValuesTransform = (value: string) => {
|
|
13
|
+
return (
|
|
14
|
+
value.trim().charAt(0).toUpperCase() +
|
|
15
|
+
value.trim().slice(1).toLowerCase().replace(/ /g, "_").replace(/-/g, "_")
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
//replace legacy 'AlphaGrid'..
|
|
20
|
+
const authorTransform = (value: string) => {
|
|
21
|
+
if (value.toLowerCase() === "alphagrid") {
|
|
22
|
+
return "Alpha_Grid";
|
|
23
|
+
}
|
|
24
|
+
return (
|
|
25
|
+
value.trim().charAt(0).toUpperCase() +
|
|
26
|
+
value.trim().slice(1).toLowerCase().replace(/ /g, "_").replace(/-/g, "_")
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const configSchema = object({
|
|
31
|
+
product: string().equals(["paid-post"]),
|
|
32
|
+
url: string().required(),
|
|
33
|
+
feature: string().required().oneOf(["channel", "microsite"]),
|
|
34
|
+
author: string().defined().default("").transform(authorTransform),
|
|
35
|
+
sponsor: string().defined().default(""),
|
|
36
|
+
articleName: string().defined().default(""),
|
|
37
|
+
videoName: string().defined().default(""),
|
|
38
|
+
videoType: string()
|
|
39
|
+
.optional()
|
|
40
|
+
.default("")
|
|
41
|
+
.transform(unifyValuesTransform)
|
|
42
|
+
.oneOf(["Feature", "Case_study", "Interview", "Animation", ""]),
|
|
43
|
+
hasVideo: boolean().optional().nullable(),
|
|
44
|
+
primaryTopic: string().defined().default("").transform(unifyValuesTransform),
|
|
45
|
+
secondaryTopic: string()
|
|
46
|
+
.defined()
|
|
47
|
+
.default("")
|
|
48
|
+
.transform(unifyValuesTransform),
|
|
49
|
+
advertiserIndustry: string().defined().default(""),
|
|
50
|
+
app: string()
|
|
51
|
+
.required()
|
|
52
|
+
.defined()
|
|
53
|
+
.transform(unifyValuesTransform)
|
|
54
|
+
.oneOf([
|
|
55
|
+
"Stream",
|
|
56
|
+
"Article",
|
|
57
|
+
"Animated_article",
|
|
58
|
+
"Immersive_article",
|
|
59
|
+
"Video",
|
|
60
|
+
"Article_with_video",
|
|
61
|
+
"Audio",
|
|
62
|
+
"Infographic",
|
|
63
|
+
"Interactive_infographic",
|
|
64
|
+
"Photo_essay",
|
|
65
|
+
]),
|
|
66
|
+
publishDate: string().nullable().default(""),
|
|
67
|
+
isBranded: boolean().defined(),
|
|
68
|
+
contentType: string().defined().default("").transform(unifyValuesTransform),
|
|
69
|
+
campaign: string().defined().default(""),
|
|
70
|
+
server: string().equals(["https://spoor-api.ft.com/px.gif"]),
|
|
71
|
+
title: string().defined().default(""),
|
|
72
|
+
adbook_campaign_id: string().optional().default(""),
|
|
73
|
+
source_id: string().optional(),
|
|
74
|
+
wordCount: number().optional(),
|
|
75
|
+
commercial_product: string().optional().default("ft"),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const gtmCustomEventSchema = object({
|
|
79
|
+
category: string()
|
|
80
|
+
.required()
|
|
81
|
+
.oneOf([
|
|
82
|
+
"Internal click",
|
|
83
|
+
"External click",
|
|
84
|
+
"Video",
|
|
85
|
+
"Video:fallback",
|
|
86
|
+
"Audio",
|
|
87
|
+
"Scroll",
|
|
88
|
+
"Share",
|
|
89
|
+
"Form",
|
|
90
|
+
"Header",
|
|
91
|
+
"Footer",
|
|
92
|
+
]),
|
|
93
|
+
action: string().required(),
|
|
94
|
+
label: string().required(),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const origamiEventSchema = object({
|
|
98
|
+
category: string()
|
|
99
|
+
.required()
|
|
100
|
+
.oneOf([
|
|
101
|
+
"page",
|
|
102
|
+
"video",
|
|
103
|
+
"audio",
|
|
104
|
+
"cta",
|
|
105
|
+
"scroll",
|
|
106
|
+
"brandedContent",
|
|
107
|
+
"internal click",
|
|
108
|
+
"external click",
|
|
109
|
+
"share",
|
|
110
|
+
"channel",
|
|
111
|
+
]),
|
|
112
|
+
action: string().required(),
|
|
113
|
+
app: string().nullable().notRequired(),
|
|
114
|
+
product: string().nullable().notRequired(),
|
|
115
|
+
source: string().nullable().notRequired(),
|
|
116
|
+
meta: mixed().nullable().notRequired(),
|
|
117
|
+
duration: number()
|
|
118
|
+
.nullable()
|
|
119
|
+
.notRequired()
|
|
120
|
+
.when("category", {
|
|
121
|
+
is: "video" || "audio",
|
|
122
|
+
then: () => number().required(),
|
|
123
|
+
}),
|
|
124
|
+
progress: number()
|
|
125
|
+
.nullable()
|
|
126
|
+
.notRequired()
|
|
127
|
+
.when("category", {
|
|
128
|
+
is: "video" || "audio",
|
|
129
|
+
then: () => number().required(),
|
|
130
|
+
}),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
export type ConfigType = InferType<typeof configSchema>;
|
|
134
|
+
export type GTMCustomEventType = InferType<typeof gtmCustomEventSchema>;
|
|
135
|
+
export type OrigamiEventType = InferType<typeof origamiEventSchema>;
|
|
136
|
+
|
|
137
|
+
export const parseConfig = (config: ConfigType): ConfigType => {
|
|
138
|
+
try {
|
|
139
|
+
//Replace app value based on deprecated hasVideo field and then remove hasVideo
|
|
140
|
+
if (config.hasVideo && config.app.toLowerCase() === "article") {
|
|
141
|
+
config.app === "Article_with_video";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
delete config.hasVideo;
|
|
145
|
+
//Parse wordCount as number if not already
|
|
146
|
+
if (config.wordCount) {
|
|
147
|
+
config.wordCount = Number(config.wordCount);
|
|
148
|
+
}
|
|
149
|
+
return configSchema.cast(config);
|
|
150
|
+
} catch (err: any) {
|
|
151
|
+
err.errors?.map((err: Error) => {
|
|
152
|
+
console.error("FTTracker - config cast error: " + err);
|
|
153
|
+
});
|
|
154
|
+
return config;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export const validateConfig = (
|
|
159
|
+
config: ConfigType
|
|
160
|
+
): ValidationError[] | undefined => {
|
|
161
|
+
try {
|
|
162
|
+
configSchema.validateSync(config, {
|
|
163
|
+
strict: false,
|
|
164
|
+
abortEarly: false,
|
|
165
|
+
});
|
|
166
|
+
} catch (err: any) {
|
|
167
|
+
err.errors?.map((err: ValidationError) => {
|
|
168
|
+
console.error("FTTracker - config validation error: " + err);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return undefined;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export const validateGTMCustomEvent = (
|
|
175
|
+
event: GTMCustomEventType
|
|
176
|
+
): ValidationError[] | undefined => {
|
|
177
|
+
try {
|
|
178
|
+
gtmCustomEventSchema.validateSync(event, {
|
|
179
|
+
strict: true,
|
|
180
|
+
abortEarly: false,
|
|
181
|
+
});
|
|
182
|
+
} catch (err: any) {
|
|
183
|
+
err.errors?.map((err: ValidationError) => {
|
|
184
|
+
console.error("FTTracker - GTM custom event validation error: " + err);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
return undefined;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export const validateOrigamiEvent = (
|
|
191
|
+
config: OrigamiEventType
|
|
192
|
+
): ValidationError[] | undefined => {
|
|
193
|
+
try {
|
|
194
|
+
const parsedConfig = origamiEventSchema.cast(config);
|
|
195
|
+
origamiEventSchema.validateSync(parsedConfig, {
|
|
196
|
+
strict: true,
|
|
197
|
+
abortEarly: false,
|
|
198
|
+
});
|
|
199
|
+
} catch (err: any) {
|
|
200
|
+
err.errors?.map((err: ValidationError) => {
|
|
201
|
+
console.error("FTTracker - Origami event validation error: " + err);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return undefined;
|
|
205
|
+
};
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { FTTracking } from "../FTTracking";
|
|
2
|
+
import { permutiveVideoUtils } from "../permutiveVideoUtils";
|
|
3
|
+
import { OrigamiEventType } from "../utils/yupValidator";
|
|
4
|
+
|
|
5
|
+
export class ytIframeTracking {
|
|
6
|
+
FTTracker: FTTracking;
|
|
7
|
+
progressMilestones = [1, 25, 50, 75];
|
|
8
|
+
permutiveUtils: permutiveVideoUtils;
|
|
9
|
+
videoProgressInterval = 0;
|
|
10
|
+
videoTitle: string;
|
|
11
|
+
videoUrl: string | undefined;
|
|
12
|
+
|
|
13
|
+
constructor(FTTracker: FTTracking, videoTitle?: string, videoUrl?: string) {
|
|
14
|
+
this.FTTracker = FTTracker;
|
|
15
|
+
this.videoTitle =
|
|
16
|
+
videoTitle || window.gtmCategory || window.location.pathname; //reverted fix for channels - use window.gtmCategory to maintain the original label style
|
|
17
|
+
this.videoUrl = videoUrl;
|
|
18
|
+
this.permutiveUtils = new permutiveVideoUtils(
|
|
19
|
+
this.FTTracker.config.campaign,
|
|
20
|
+
this.videoTitle
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
progressPercentage = (duration: number, currentTime: number) =>
|
|
25
|
+
parseInt(((currentTime / duration) * 100).toFixed(2));
|
|
26
|
+
|
|
27
|
+
emitProgressEvents = (
|
|
28
|
+
progress: number,
|
|
29
|
+
duration: number,
|
|
30
|
+
isYoutube: boolean
|
|
31
|
+
) => {
|
|
32
|
+
while (progress >= this.progressMilestones[0]) {
|
|
33
|
+
this.FTTracker.gaEvent(
|
|
34
|
+
`Video${window.isOvideoPlayer ? ":fallback" : ""}`,
|
|
35
|
+
`${this.progressMilestones[0]}% watched`,
|
|
36
|
+
this.videoTitle
|
|
37
|
+
);
|
|
38
|
+
if (isYoutube) {
|
|
39
|
+
this.FTTracker.oEvent({
|
|
40
|
+
category: "video",
|
|
41
|
+
action: "progress",
|
|
42
|
+
duration: duration,
|
|
43
|
+
progress,
|
|
44
|
+
} as OrigamiEventType);
|
|
45
|
+
}
|
|
46
|
+
this.progressMilestones.shift();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
//Fallback oVideo implemented only on channels - uses shared events so added here for now.
|
|
51
|
+
public oVideoEventHandler(videoEl: HTMLVideoElement) {
|
|
52
|
+
window.isOvideoPlayer = true;
|
|
53
|
+
this.permutiveUtils.videoId = videoEl.getAttribute("src") as string;
|
|
54
|
+
videoEl.addEventListener("play", () => {
|
|
55
|
+
this.playTracking(videoEl.currentTime, videoEl.duration);
|
|
56
|
+
});
|
|
57
|
+
videoEl.addEventListener("progress", () => {
|
|
58
|
+
this.progressTracking(videoEl.currentTime, videoEl.duration);
|
|
59
|
+
});
|
|
60
|
+
videoEl.addEventListener("seeked", () => {
|
|
61
|
+
this.seekedTracking(videoEl.currentTime, videoEl.duration);
|
|
62
|
+
});
|
|
63
|
+
videoEl.addEventListener("pause", () => {
|
|
64
|
+
this.pausedTracking(videoEl.currentTime, videoEl.duration);
|
|
65
|
+
});
|
|
66
|
+
videoEl.addEventListener("ended", () => {
|
|
67
|
+
this.endedTracking(videoEl.currentTime, videoEl.duration);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public ytIframeEventHandler(event: YT.PlayerEvent) {
|
|
72
|
+
const player = event.target as YT.Player;
|
|
73
|
+
|
|
74
|
+
if (!this.videoUrl) {
|
|
75
|
+
this.videoUrl = player.getVideoUrl();
|
|
76
|
+
}
|
|
77
|
+
this.permutiveUtils.videoId = this.videoUrl;
|
|
78
|
+
//player.playerInfo.videoUrl
|
|
79
|
+
|
|
80
|
+
switch (player.getPlayerState()) {
|
|
81
|
+
case YT.PlayerState.ENDED:
|
|
82
|
+
this.endedTracking(player.getCurrentTime(), player.getDuration());
|
|
83
|
+
break;
|
|
84
|
+
case YT.PlayerState.PLAYING:
|
|
85
|
+
this.ytPlayTracking(player);
|
|
86
|
+
break;
|
|
87
|
+
case YT.PlayerState.PAUSED:
|
|
88
|
+
//test if still pause after 1 second, otherwise its a seek
|
|
89
|
+
setTimeout(() => {
|
|
90
|
+
if (player.getPlayerState() == YT.PlayerState.PAUSED) {
|
|
91
|
+
this.pausedTracking(player.getCurrentTime(), player.getDuration());
|
|
92
|
+
}
|
|
93
|
+
}, 1000);
|
|
94
|
+
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
ytPlayTracking(player: YT.Player) {
|
|
100
|
+
this.videoProgressInterval = window.setInterval(() => {
|
|
101
|
+
const currentTime = player.getCurrentTime();
|
|
102
|
+
const duration = player.getDuration();
|
|
103
|
+
this.permutiveUtils.emitPermutiveProgressEvent(
|
|
104
|
+
duration - 1,
|
|
105
|
+
currentTime,
|
|
106
|
+
this.videoProgressInterval
|
|
107
|
+
);
|
|
108
|
+
const progress = this.progressPercentage(duration, currentTime);
|
|
109
|
+
this.emitProgressEvents(progress, duration, true);
|
|
110
|
+
}, 1000);
|
|
111
|
+
const currentTime = player.getCurrentTime();
|
|
112
|
+
const duration = player.getDuration();
|
|
113
|
+
const progress = this.progressPercentage(duration, currentTime);
|
|
114
|
+
if (progress < 100) {
|
|
115
|
+
this.FTTracker.gaEvent(
|
|
116
|
+
`Video${window.isOvideoPlayer ? ":fallback" : ""}`,
|
|
117
|
+
`playing`,
|
|
118
|
+
this.videoTitle
|
|
119
|
+
);
|
|
120
|
+
this.FTTracker.oEvent({
|
|
121
|
+
category: "video",
|
|
122
|
+
action: "playing",
|
|
123
|
+
duration: duration,
|
|
124
|
+
progress,
|
|
125
|
+
} as OrigamiEventType);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
pausedTracking(currentTime: number, duration: number) {
|
|
130
|
+
const progress = this.progressPercentage(duration, currentTime);
|
|
131
|
+
this.FTTracker.gaEvent(
|
|
132
|
+
`Video${window.isOvideoPlayer ? ":fallback" : ""}`,
|
|
133
|
+
`pause`,
|
|
134
|
+
`${this.videoTitle}`
|
|
135
|
+
);
|
|
136
|
+
this.FTTracker.oEvent({
|
|
137
|
+
category: "video",
|
|
138
|
+
action: "pause",
|
|
139
|
+
duration: duration,
|
|
140
|
+
progress,
|
|
141
|
+
} as OrigamiEventType);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
endedTracking(currentTime: number, duration: number) {
|
|
145
|
+
const progress = this.progressPercentage(duration, currentTime);
|
|
146
|
+
this.FTTracker.gaEvent(
|
|
147
|
+
`Video${window.isOvideoPlayer ? ":fallback" : ""}`,
|
|
148
|
+
`100% watched`,
|
|
149
|
+
`${this.videoTitle}`
|
|
150
|
+
);
|
|
151
|
+
this.FTTracker.oEvent({
|
|
152
|
+
category: "video",
|
|
153
|
+
action: "ended",
|
|
154
|
+
duration: duration,
|
|
155
|
+
progress,
|
|
156
|
+
} as OrigamiEventType);
|
|
157
|
+
//force a 100% event on end
|
|
158
|
+
if (this.permutiveUtils.remainingProgress.length > 0) {
|
|
159
|
+
this.permutiveUtils.emitPermutiveProgressEvent(
|
|
160
|
+
currentTime,
|
|
161
|
+
currentTime,
|
|
162
|
+
this.videoProgressInterval
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/*** Below events only used for FT-Channels videoJS and Origami players - remove if no longer needed ***/
|
|
168
|
+
|
|
169
|
+
/*** videojs and origami player event only ***/
|
|
170
|
+
playTracking(currentTime: number, duration: number) {
|
|
171
|
+
this.videoProgressInterval = window.setInterval(() => {
|
|
172
|
+
this.permutiveUtils.emitPermutiveProgressEvent(
|
|
173
|
+
duration - 1,
|
|
174
|
+
currentTime,
|
|
175
|
+
this.videoProgressInterval
|
|
176
|
+
);
|
|
177
|
+
}, 1000);
|
|
178
|
+
const progress = this.progressPercentage(duration, currentTime);
|
|
179
|
+
this.FTTracker.oEvent({
|
|
180
|
+
category: "video",
|
|
181
|
+
action: "playing",
|
|
182
|
+
duration: duration,
|
|
183
|
+
progress,
|
|
184
|
+
} as OrigamiEventType);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/*** videojs and origami player event only ***/
|
|
188
|
+
progressTracking(currentTime: number, duration: number) {
|
|
189
|
+
const progress = this.progressPercentage(duration, currentTime);
|
|
190
|
+
this.emitProgressEvents(progress, duration, false);
|
|
191
|
+
this.FTTracker.oEvent({
|
|
192
|
+
category: "video",
|
|
193
|
+
action: "progress",
|
|
194
|
+
duration: duration,
|
|
195
|
+
progress,
|
|
196
|
+
} as OrigamiEventType);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/*** videojs and origami player event only ***/
|
|
200
|
+
seekedTracking(currentTime: number, duration: number) {
|
|
201
|
+
const progress = this.progressPercentage(duration, currentTime);
|
|
202
|
+
this.emitProgressEvents(progress, duration, false);
|
|
203
|
+
this.FTTracker.oEvent({
|
|
204
|
+
category: "video",
|
|
205
|
+
action: "seek",
|
|
206
|
+
duration: duration,
|
|
207
|
+
progress,
|
|
208
|
+
} as OrigamiEventType);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { consentMonitor } from "../src/consentMonitor";
|
|
2
|
+
import { permutiveVideoUtils } from "../src/permutiveVideoUtils";
|
|
3
|
+
|
|
4
|
+
let windowSpy: jest.SpyInstance;
|
|
5
|
+
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
windowSpy = jest.spyOn(window, "window", "get");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
windowSpy.mockRestore();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe("consentMonitor", () => {
|
|
15
|
+
it("can be initiated", () => {
|
|
16
|
+
const obj = new consentMonitor();
|
|
17
|
+
expect(obj.consent).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
it("sets default dev hosts property", async () => {
|
|
20
|
+
const obj = new consentMonitor("https://FT-staging.com");
|
|
21
|
+
expect(obj.devHosts).toEqual(["localhost", "phq", ".app", "preview"]);
|
|
22
|
+
});
|
|
23
|
+
it("matches hostname to devHosts", async () => {
|
|
24
|
+
const obj = new consentMonitor("https://FT-staging.devhost.com", [
|
|
25
|
+
"localhost",
|
|
26
|
+
"vercel.app",
|
|
27
|
+
"phq",
|
|
28
|
+
"devhost",
|
|
29
|
+
]);
|
|
30
|
+
expect(obj.isDevEnvironment).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("Initializes correctly", async () => {
|
|
34
|
+
jest.useFakeTimers();
|
|
35
|
+
windowSpy.mockImplementation(() => ({
|
|
36
|
+
permutive: {
|
|
37
|
+
consent: ({}) => {
|
|
38
|
+
return {};
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
}));
|
|
42
|
+
const obj = new consentMonitor("https://FT-staging.devhost.com", [
|
|
43
|
+
"localhost",
|
|
44
|
+
"vercel.app",
|
|
45
|
+
"phq",
|
|
46
|
+
"devhost",
|
|
47
|
+
]);
|
|
48
|
+
expect(obj.isInitialized).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
//TODO - mock document.cookie get/set and test consent value
|
|
51
|
+
describe("Permutive video progress utils", () => {
|
|
52
|
+
it("can be initiated", () => {
|
|
53
|
+
const permutiveVideoTracker = new permutiveVideoUtils(
|
|
54
|
+
"FT-Campaign",
|
|
55
|
+
"video title",
|
|
56
|
+
"videoID"
|
|
57
|
+
);
|
|
58
|
+
expect(permutiveVideoTracker.remainingProgress).toEqual([
|
|
59
|
+
0, 0.25, 0.5, 0.75, 1,
|
|
60
|
+
]);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es5",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"types": ["youtube", "jest"],
|
|
6
|
+
"lib": ["dom", "es2020"],
|
|
7
|
+
"jsx": "react",
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"declarationMap": true,
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
"outDir": "./lib",
|
|
12
|
+
"rootDir": "./src",
|
|
13
|
+
"strict": true,
|
|
14
|
+
"noImplicitAny": true,
|
|
15
|
+
"strictNullChecks": true,
|
|
16
|
+
"strictPropertyInitialization": true,
|
|
17
|
+
"noImplicitThis": true,
|
|
18
|
+
"alwaysStrict": true,
|
|
19
|
+
"noImplicitReturns": true,
|
|
20
|
+
"allowSyntheticDefaultImports": true,
|
|
21
|
+
"esModuleInterop": true,
|
|
22
|
+
"allowJs": true,
|
|
23
|
+
},
|
|
24
|
+
"include": ["src/**/*.ts", "src/**/*.js"],
|
|
25
|
+
"exclude": ["test/**/*"]
|
|
26
|
+
}
|