-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
381 lines (322 loc) · 9.3 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
import posthog from 'posthog-js';
import Cookies from 'js-cookie';
const PH_API = 'ZdDZgkeOt4Z_m-UWmqFsE1d6-kcCK3BH0ypYTUIFty4';
const PH_HOST = 'https://t.webiny.com';
const PH_COOKIE = 'ph_webiny';
class Tracking {
constructor () {
this.user = {};
this.debugFlag = false;
}
/**
* Activates Posthog tracking utility.
*/
async activateTracking () {
// skip tracking for gatsby builds
if (typeof document === `undefined`) {
return false;
}
// introduce a state for debug option
window['w_ph_debug'] = [];
this.initializePosthogTracking (posthog => {
// populate the user details
this.gatherUserDetails (posthog);
});
}
/**
* Initializes posthog and identifies the current user.
*
* @param {*} callback
*/
initializePosthogTracking (callback) {
if (window.posthog) {
callback (window.posthog);
return window.posthog;
}
posthog.init (PH_API, {
api_host: PH_HOST,
loaded: async posthog => {
// get user ip
const userIp = await this.getUserIp ();
this.debug ('Posthog loaded, user id set to:' + userIp);
if (userIp !== false) {
posthog.identify (userIp);
}
window.posthog = posthog;
callback (posthog);
},
});
}
/**
* Returns the user's IP address
*/
async getUserIp () {
// retrieve user IP from the cookie
this.user = this.getUserFromCookie ();
if (this.user.hasOwnProperty ('ip')) {
this.debug ('User IP retrieved from the cookie.');
return this.user.ip;
}
// retrieve the user IP from the IP-API
try {
const response = await fetch ('https://api.ipify.org/?format=json', {
method: 'GET',
mode: 'cors',
});
const userData = await response.json ();
// save into local state
this.user.ip = userData.ip;
// save user into cookie
this.saveUserCookie ();
this.debug ('User IP retrieved from the IP-API.');
return this.user.ip;
} catch (e) {
this.debug ('Unable to retrieve the user IP.');
return false;
}
}
/**
* Returns the domain name. It's important for the cookie.
*/
getDomainName () {
return window.location.hostname.replace (/www|docs|blog/gi, '');
}
/**
* Retrieves the user from the cookie.
*/
getUserFromCookie () {
if (Cookies.get (PH_COOKIE)) {
return JSON.parse (Cookies.get (PH_COOKIE));
}
return {};
}
/**
* Saves the current this.user to cookie
*/
saveUserCookie () {
Cookies.set (PH_COOKIE, JSON.stringify (this.user), {
expires: 365,
domain: this.getDomainName (),
});
}
gatherUserDetails (posthog) {
// first load the current user
this.user = this.getUserFromCookie ();
const data = {};
const dataOnce = {};
let firstTouchSet = false;
// parse the UTM
const utm = this.parseUtmData ();
this.debug ('UTM data:' + JSON.stringify (utm));
if (utm !== null) {
for (const tag in utm) {
data['last-touch-' + tag] = utm[tag];
}
if (!this.user.hasOwnProperty ('first-touch')) {
for (const tag in utm) {
dataOnce['first-touch-' + tag] = utm[tag];
}
firstTouchSet = true;
}
}
// get the referrer data
let referrer = this.parseReferrerData ();
this.debug ('Referrer data:' + JSON.stringify (referrer));
if (referrer === null && utm !== null) {
this.debug ('Referrer is null, but UTM tags are available');
// in case the referrer is null, but we have the UTM tags, we should update the referrer value based on the UTM (this happens when it's a direct link c/p)
if (utm.hasOwnProperty ('utm_source')) {
referrer = {
source: utm['utm_source'].toLowerCase (),
domain: utm['utm_source'].toLowerCase () + '.com',
};
}
}
// populate the referrer data
if (referrer !== null) {
// for the referrers we always set the first touch and last touch data separately
// last touch is updated on every new visit if a referrer can be parsed
// first touch is only set once
// update last touch
data['last-touch-referral-source'] = referrer.source;
data['last-touch-referral-domain'] = referrer.domain;
// update first touch (only if one wasn't set previously)
if (!this.user.hasOwnProperty ('first-touch')) {
dataOnce['first-touch-referral-source'] = referrer.source;
dataOnce['first-touch-referral-domain'] = referrer.domain;
firstTouchSet = true;
}
}
this.debug ('PH data to be saved:' + JSON.stringify (data));
// only set data when we have something to set
if (Object.keys (data).length > 0) {
posthog.people.set (data);
this.debug ('PH data saved.');
if (firstTouchSet) {
// waiting for a fix: https://github.com/PostHog/posthog-js/issues/85
//posthog.people.set_once (dataOnce);
posthog.people.set (dataOnce);
this.user['first-touch'] = 1;
this.debug ('PH data once saved.');
this.debug (dataOnce);
}
this.saveUserCookie ();
}
}
/**
* Extracts and returns the UTM query strings.
*/
parseUtmData () {
if (!document.location.search || document.location.search == '') {
this.debug ('UTM data not available');
return null;
}
// parse the query strings
const vars = document.location.search.substring (1).split ('&');
const queryStrings = {};
for (let i = 0; i < vars.length; i++) {
let pair = vars[i].split ('=');
let name = decodeURIComponent (pair[0]);
if (name.indexOf ('utm_') !== -1) {
// some cleanup stupp
name = name.replace (/amp;|;/gi, '');
const value = decodeURIComponent (pair[1]).replace (/amp;|;/gi, '');
queryStrings[name] = value;
}
}
if (Object.keys (queryStrings).length < 1) {
return null;
}
return queryStrings;
}
/**
* Parses the referrer domain name.
*
* Returns 'null' in case of a `direct` access or in case a ref page is another page
* on the same domain. In all other cases, it returns an object.
*/
parseReferrerData () {
// referrer domain
const referrer = document.referrer;
this.debug ('Referrer domain set to:' + referrer);
// https://github.com/segmentio/inbound
if (
typeof referrer === 'undefined' ||
referrer === null ||
!referrer ||
referrer === ''
) {
return null;
}
// before doing any analysis of the referrer, let's check if ref is another internal page
if (
referrer.indexOf ('https://www.webiny.com') === 0 ||
referrer.indexOf ('https://docs.webiny.com') === 0 ||
referrer.indexOf ('localhost') !== -1
) {
return null;
}
let network = null;
// facebook
if (referrer.indexOf ('facebook.com') !== -1) {
network = 'facebook';
}
// twitter
if (
referrer.indexOf ('twitter.com') !== -1 ||
referrer.indexOf ('t.co') !== -1
) {
network = 'twitter';
}
// linkedin
if (
referrer.indexOf ('linkedin.com') !== -1 ||
referrer.indexOf ('lnkd.in') !== -1
) {
network = 'linkedin';
}
// reddit
if (referrer.indexOf ('reddit.com') !== -1) {
network = 'reddit';
}
// producthunt
if (referrer.indexOf ('producthunt.com') !== -1) {
network = 'producthunt';
}
// hackernoon
if (referrer.indexOf ('hackernoon.com') !== -1) {
network = 'hackernoon';
}
// google search (has to be before the google ad service)
// note: don't add TLD
if (referrer.indexOf ('google') !== -1) {
network = 'google-search';
}
// google ad service
if (referrer.indexOf ('googleadservices.com') !== -1) {
network = 'google-ads';
}
// baidu
if (referrer && referrer.indexOf ('baidu.com') !== -1) {
network = 'baidu';
}
// yandex
if (referrer && referrer.indexOf ('yandex.com') !== -1) {
network = 'yandex';
}
// bing
if (referrer && referrer.indexOf ('bing.com') !== -1) {
network = 'bing';
}
// gmail
if (referrer && referrer.indexOf ('mail.google.com') !== -1) {
network = 'google-mail';
}
// github
if (referrer.indexOf ('github.com') !== -1) {
network = 'github';
}
// npm
if (referrer.indexOf ('npmjs.com') !== -1) {
network = 'npm';
}
// webiny blog
if (referrer.indexOf ('blog.webiny.com') !== -1) {
network = 'webiny-blog';
}
// youtube
if (
referrer.indexOf ('youtube.com') !== -1 ||
referrer.indexOf ('youtu.be') !== -1
) {
network = 'youtube';
}
return {
source: network, // network is null if we haven't matched it
domain: referrer.replace (/https:\/\/|http\/\/|\//gi, ''),
};
}
/**
* Tracks action.
*/
trackAction (action, props) {
this.initializePosthogTracking (posthog => {
posthog.capture (action, props);
});
}
debug (msg) {
if (this.debugFlag) {
window['w_ph_debug'].push (msg);
}
}
}
export const activateTracking = () => {
const t = new Tracking ();
t.activateTracking ();
return t;
};
export const trackAction = (action, props) => {
const t = new Tracking ();
t.trackAction (action, props);
return t;
};