@splendidlabz/tracking 0.1.22 → 0.2.1

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/lib/fb/utils.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { isEmpty } from '@splendidlabz/utils'
2
- import { sha256Hash } from '@splendidlabz/utils/dom'
2
+ import { COUNTRY_CODES, US_STATE_CODES } from './consts.js'
3
3
 
4
- export async function formatUserData(data) {
4
+ export async function formatUserData(data, sha256HashFn) {
5
5
  if (isEmpty(data)) return {}
6
6
 
7
7
  // Lowercase all the values
8
+ // https://developers.facebook.com/docs/meta-pixel/advanced/advanced-matching/
8
9
  for (const [key, value] of Object.entries(data)) {
9
10
  data[key] = value.toLowerCase()
10
11
  }
@@ -24,349 +25,26 @@ export async function formatUserData(data) {
24
25
 
25
26
  const ret = {}
26
27
 
27
- if (email) ret.em = await sha256Hash(email)
28
- if (phone) ret.ph = await sha256Hash(phone)
29
- if (firstName) ret.fn = await sha256Hash(firstName)
30
- if (lastName) ret.ln = await sha256Hash(lastName)
31
- if (birthdate) ret.db = await sha256Hash(birthdate)
32
- if (gender) ret.ge = await sha256Hash(gender)
33
- if (city) ret.ct = await sha256Hash(city.replace(' ', ''))
28
+ if (email) ret.em = await sha256HashFn(email)
29
+ if (phone) ret.ph = await sha256HashFn(phone)
30
+ if (firstName) ret.fn = await sha256HashFn(firstName)
31
+ if (lastName) ret.ln = await sha256HashFn(lastName)
32
+ if (birthdate) ret.db = await sha256HashFn(birthdate)
33
+ if (gender) ret.ge = await sha256HashFn(gender)
34
+ if (city) ret.ct = await sha256HashFn(city.replace(' ', ''))
34
35
 
35
36
  if (country) {
36
37
  const c = COUNTRY_CODES[country]
37
- ret.country = await sha256Hash(c)
38
+ ret.country = await sha256HashFn(c)
38
39
 
39
40
  if (c === 'us' && state) {
40
- ret.st = await sha256Hash(US_STATE_CODES[state])
41
+ ret.st = await sha256HashFn(US_STATE_CODES[state])
41
42
  }
43
+
42
44
  if (c !== 'us' && state) {
43
- ret.st = await sha256Hash(state.replace(' ', '').replace('-', ''))
45
+ ret.st = await sha256HashFn(state.replace(' ', '').replace('-', ''))
44
46
  }
45
47
  }
46
- if (zip) ret.zp = await sha256Hash(zip)
48
+ if (zip) ret.zp = await sha256HashFn(zip)
47
49
  return ret
48
50
  }
49
-
50
- const US_STATE_CODES = {
51
- alabama: 'al',
52
- alaska: 'ak',
53
- 'american samoa': 'as',
54
- arizona: 'az',
55
- arkansas: 'ar',
56
- 'baker island': 'bi',
57
- california: 'ca',
58
- colorado: 'co',
59
- connecticut: 'ct',
60
- delaware: 'de',
61
- 'district of columbia': 'dc',
62
- florida: 'fl',
63
- 'federated states of micronesia': 'fm',
64
- georgia: 'ga',
65
- guam: 'gu',
66
- hawaii: 'hi',
67
- 'howland island': 'hi',
68
- idaho: 'id',
69
- illinois: 'il',
70
- indiana: 'in',
71
- iowa: 'ia',
72
- 'jarvis island': 'ji',
73
- 'johnston atoll': 'ja',
74
- kansas: 'ks',
75
- kentucky: 'ky',
76
- 'kingman reef': 'kr',
77
- louisiana: 'la',
78
- maine: 'me',
79
- 'marshall islands': 'mh',
80
- maryland: 'md',
81
- massachusetts: 'ma',
82
- michigan: 'mi',
83
- 'midway islands': 'mi',
84
- minnesota: 'mn',
85
- mississippi: 'ms',
86
- missouri: 'mo',
87
- montana: 'mt',
88
- 'navassa island': 'ni',
89
- nebraska: 'ne',
90
- nevada: 'nv',
91
- 'new hampshire': 'nh',
92
- 'new jersey': 'nj',
93
- 'new mexico': 'nm',
94
- 'new york': 'ny',
95
- 'north carolina': 'nc',
96
- 'north dakota': 'nd',
97
- 'northern mariana islands': 'mp',
98
- ohio: 'oh',
99
- oklahoma: 'ok',
100
- oregon: 'or',
101
- palau: 'pw',
102
- 'palmyra atoll': 'pa',
103
- pennsylvania: 'pa',
104
- 'puerto rico': 'pr',
105
- 'rhode island': 'ri',
106
- 'south carolina': 'sc',
107
- 'south dakota': 'sd',
108
- tennessee: 'tn',
109
- texas: 'tx',
110
- 'u.s. minor outlying islands': 'um',
111
- utah: 'ut',
112
- vermont: 'vt',
113
- virginia: 'va',
114
- 'virgin islands of the u.s.': 'vi',
115
- 'wake island': 'wi',
116
- washington: 'wa',
117
- 'west virginia': 'wv',
118
- wisconsin: 'wi',
119
- wyoming: 'wy',
120
- }
121
-
122
- const COUNTRY_CODES = {
123
- afghanistan: 'af',
124
- 'åland islands': 'ax',
125
- albania: 'al',
126
- algeria: 'dz',
127
- 'american samoa': 'as',
128
- andorra: 'ad',
129
- angola: 'ao',
130
- anguilla: 'ai',
131
- antarctica: 'aq',
132
- 'antigua and barbuda': 'ag',
133
- argentina: 'ar',
134
- armenia: 'am',
135
- aruba: 'aw',
136
- australia: 'au',
137
- austria: 'at',
138
- azerbaijan: 'az',
139
- bahamas: 'bs',
140
- bahrain: 'bh',
141
- bangladesh: 'bd',
142
- barbados: 'bb',
143
- belarus: 'by',
144
- belgium: 'be',
145
- belize: 'bz',
146
- benin: 'bj',
147
- bermuda: 'bm',
148
- bhutan: 'bt',
149
- 'bolivia (plurinational state of)': 'bo',
150
- 'bonaire, sint eustatius and saba': 'bq',
151
- 'bosnia and herzegovina': 'ba',
152
- botswana: 'bw',
153
- 'bouvet island': 'bv',
154
- brazil: 'br',
155
- 'british indian ocean territory': 'io',
156
- 'brunei darussalam': 'bn',
157
- brunei: 'bn',
158
- bulgaria: 'bg',
159
- 'burkina faso': 'bf',
160
- burundi: 'bi',
161
- 'cabo verde': 'cv',
162
- cambodia: 'kh',
163
- cameroon: 'cm',
164
- canada: 'ca',
165
- 'cayman islands': 'ky',
166
- 'central african republic': 'cf',
167
- chad: 'td',
168
- chile: 'cl',
169
- china: 'cn',
170
- 'christmas island': 'cx',
171
- 'cocos (keeling) islands': 'cc',
172
- colombia: 'co',
173
- comoros: 'km',
174
- 'democratic republic of the congo': 'cd',
175
- 'cook islands': 'ck',
176
- 'costa rica': 'cr',
177
- "côte d'ivoire": 'ci',
178
- croatia: 'hr',
179
- cuba: 'cu',
180
- curaçao: 'cw',
181
- cyprus: 'cy',
182
- czechia: 'cz',
183
- denmark: 'dk',
184
- djibouti: 'dj',
185
- dominica: 'dm',
186
- 'dominican republic': 'do',
187
- ecuador: 'ec',
188
- egypt: 'eg',
189
- 'el salvador': 'sv',
190
- 'equatorial guinea': 'gq',
191
- eritrea: 'er',
192
- estonia: 'ee',
193
- eswatini: 'sz',
194
- ethiopia: 'et',
195
- 'falkland islands': 'fk',
196
- 'faroe islands': 'fo',
197
- fiji: 'fj',
198
- finland: 'fi',
199
- france: 'fr',
200
- 'french guiana': 'gf',
201
- 'french polynesia': 'pf',
202
- 'french southern territories': 'tf',
203
- gabon: 'ga',
204
- gambia: 'gm',
205
- georgia: 'ge',
206
- germany: 'de',
207
- ghana: 'gh',
208
- gibraltar: 'gi',
209
- greece: 'gr',
210
- greenland: 'gl',
211
- grenada: 'gd',
212
- guadeloupe: 'gp',
213
- guam: 'gu',
214
- guatemala: 'gt',
215
- guernsey: 'gg',
216
- guinea: 'gn',
217
- 'guinea-bissau': 'gw',
218
- guyana: 'gy',
219
- haiti: 'ht',
220
- 'heard island and mcdonald islands': 'hm',
221
- 'holy see': 'va',
222
- honduras: 'hn',
223
- 'hong kong': 'hk',
224
- hungary: 'hu',
225
- iceland: 'is',
226
- india: 'in',
227
- indonesia: 'id',
228
- iran: 'ir',
229
- iraq: 'iq',
230
- ireland: 'ie',
231
- 'isle of man': 'im',
232
- israel: 'il',
233
- italy: 'it',
234
- jamaica: 'jm',
235
- japan: 'jp',
236
- jersey: 'je',
237
- jordan: 'jo',
238
- kazakhstan: 'kz',
239
- kenya: 'ke',
240
- kiribati: 'ki',
241
- 'north korea': 'kp',
242
- 'south korea': 'kr',
243
- kuwait: 'kw',
244
- kyrgyzstan: 'kg',
245
- laos: 'la',
246
- latvia: 'lv',
247
- lebanon: 'lb',
248
- lesotho: 'ls',
249
- liberia: 'lr',
250
- libya: 'ly',
251
- liechtenstein: 'li',
252
- lithuania: 'lt',
253
- luxembourg: 'lu',
254
- macao: 'mo',
255
- madagascar: 'mg',
256
- malawi: 'mw',
257
- malaysia: 'my',
258
- maldives: 'mv',
259
- mali: 'ml',
260
- malta: 'mt',
261
- 'marshall islands': 'mh',
262
- martinique: 'mq',
263
- mauritania: 'mr',
264
- mauritius: 'mu',
265
- mayotte: 'yt',
266
- mexico: 'mx',
267
- micronesia: 'fm',
268
- moldova: 'md',
269
- monaco: 'mc',
270
- mongolia: 'mn',
271
- montenegro: 'me',
272
- montserrat: 'ms',
273
- morocco: 'ma',
274
- mozambique: 'mz',
275
- myanmar: 'mm',
276
- namibia: 'na',
277
- nauru: 'nr',
278
- nepal: 'np',
279
- netherlands: 'nl',
280
- 'new caledonia': 'nc',
281
- 'new zealand': 'nz',
282
- nicaragua: 'ni',
283
- niger: 'ne',
284
- nigeria: 'ng',
285
- niue: 'nu',
286
- 'norfolk island': 'nf',
287
- 'north macedonia': 'mk',
288
- 'northern mariana islands': 'mp',
289
- norway: 'no',
290
- oman: 'om',
291
- pakistan: 'pk',
292
- palau: 'pw',
293
- palestine: 'ps',
294
- panama: 'pa',
295
- 'papua new guinea': 'pg',
296
- paraguay: 'py',
297
- peru: 'pe',
298
- philippines: 'ph',
299
- pitcairn: 'pn',
300
- poland: 'pl',
301
- portugal: 'pt',
302
- 'puerto rico': 'pr',
303
- qatar: 'qa',
304
- réunion: 're',
305
- romania: 'ro',
306
- 'russian federation': 'ru',
307
- rwanda: 'rw',
308
- 'saint barthélemy': 'bl',
309
- 'saint helena': 'sh',
310
- 'saint kitts and nevis': 'kn',
311
- 'saint lucia': 'lc',
312
- 'saint martin': 'mf',
313
- 'saint pierre and miquelon': 'pm',
314
- 'saint vincent and the grenadines': 'vc',
315
- samoa: 'ws',
316
- 'san marino': 'sm',
317
- 'sao tome and principe': 'st',
318
- 'saudi arabia': 'sa',
319
- senegal: 'sn',
320
- serbia: 'rs',
321
- seychelles: 'sc',
322
- 'sierra leone': 'sl',
323
- singapore: 'sg',
324
- 'sint maarten': 'sx',
325
- slovakia: 'sk',
326
- slovenia: 'si',
327
- 'solomon islands': 'sb',
328
- somalia: 'so',
329
- 'south africa': 'za',
330
- 'south georgia and the south sandwich islands': 'gs',
331
- 'south sudan': 'ss',
332
- spain: 'es',
333
- 'sri lanka': 'lk',
334
- sudan: 'sd',
335
- suriname: 'sr',
336
- 'svalbard and jan mayen': 'sj',
337
- sweden: 'se',
338
- switzerland: 'ch',
339
- 'syrian arab republic': 'sy',
340
- taiwan: 'tw',
341
- tajikistan: 'tj',
342
- tanzania: 'tz',
343
- thailand: 'th',
344
- 'timor-leste': 'tl',
345
- togo: 'tg',
346
- tokelau: 'tk',
347
- tonga: 'to',
348
- 'trinidad and tobago': 'tt',
349
- tunisia: 'tn',
350
- türkiye: 'tr',
351
- turkmenistan: 'tm',
352
- 'turks and caicos islands': 'tc',
353
- tuvalu: 'tv',
354
- uganda: 'ug',
355
- ukraine: 'ua',
356
- 'united arab emirates': 'ae',
357
- 'united kingdom': 'gb',
358
- 'united states minor outlying islands': 'um',
359
- 'united states': 'us',
360
- uruguay: 'uy',
361
- uzbekistan: 'uz',
362
- vanuatu: 'vu',
363
- venezuela: 've',
364
- 'viet nam': 'vn',
365
- 'british virgin islands': 'vg',
366
- 'u.s. virgin islands': 'vi',
367
- 'wallis and futuna': 'wf',
368
- 'western sahara': 'eh',
369
- yemen: 'ye',
370
- zambia: 'zm',
371
- zimbabwe: 'zw',
372
- }
package/lib/fb/web.js ADDED
@@ -0,0 +1,148 @@
1
+ import { notEmpty, splitObject } from '@splendidlabz/utils'
2
+ import { getCookie, randomString, sha256Hash } from '@splendidlabz/utils/dom'
3
+ import {
4
+ STANDARD_EVENTS,
5
+ USER_PROPERTIES,
6
+ USER_PROPERTIES_TO_HASH,
7
+ } from './consts.js'
8
+
9
+ import zlFetch from 'zl-fetch'
10
+ import { formatUserData } from './utils.js'
11
+
12
+ export default function FB({ pixelID, testEventCode, capiEndpoint }) {
13
+ return {
14
+ init: _ => initPixel(pixelID),
15
+
16
+ async createEvent(event_name, props = {}, options = {}) {
17
+ const { debug = false } = options
18
+ const { eventURL, actionSource, ...r } = props
19
+ const { p: userProperties, o: rest } = splitObject(r, USER_PROPERTIES)
20
+ const { p: userPropsToHash, o: otherUserProps } = splitObject(
21
+ userProperties,
22
+ USER_PROPERTIES_TO_HASH,
23
+ )
24
+
25
+ const user_data = {
26
+ ...(await formatUserData(userPropsToHash, sha256Hash)),
27
+ ...otherUserProps,
28
+ }
29
+
30
+ const eventID = await randomString()
31
+
32
+ const pixelData = {
33
+ pixelID,
34
+ eventID,
35
+ event_name,
36
+ user_data,
37
+ ...rest,
38
+ }
39
+
40
+ const capiData = {
41
+ pixel_id: pixelID,
42
+ event_id: eventID,
43
+ event_name,
44
+ event_time: Math.floor(new Date() / 1000),
45
+ event_source_url: eventURL || window.location.href,
46
+ action_source: actionSource || 'website',
47
+ user_data: {
48
+ ...user_data,
49
+ fbp: getCookie('_fbp'),
50
+ fbc: getCookie('_fbc'),
51
+ },
52
+ custom_data: rest,
53
+ test_event_code: testEventCode,
54
+ }
55
+
56
+ if (debug) {
57
+ console.log('Pixel Data', pixelData)
58
+ console.log('CAPI Data', capiData)
59
+ }
60
+
61
+ return { pixelData, capiData }
62
+ },
63
+
64
+ async track(event_name, props = {}, options = {}) {
65
+ const { debug = false, dryRun = false } = options
66
+ const { pixelData, capiData } = await this.createEvent(
67
+ event_name,
68
+ props,
69
+ options,
70
+ )
71
+
72
+ if (dryRun) return
73
+
74
+ this.sendPixelEvent(pixelData, options)
75
+ const { response, error } = await this.sendCAPIRequest(capiData)
76
+ if (debug) console.log('CAPI Response', response.body)
77
+ if (error) return Promise.reject(error)
78
+ return response.body
79
+ },
80
+
81
+ // Facebook Pixel Code
82
+ // Pixel Standard Reference
83
+ // https://developers.facebook.com/docs/meta-pixel/reference#standard-events
84
+ // For Advanced Matching when user data is available
85
+ // https://developers.facebook.com/docs/meta-pixel/advanced/advanced-matching/
86
+ sendPixelEvent(data, { debug = false }) {
87
+ const { pixelID, eventID, event_name, user_data = {}, ...rest } = data
88
+ const hasUserData = notEmpty(user_data)
89
+
90
+ const props = rest
91
+
92
+ // For Advanced Matching we provide user data.
93
+ if (hasUserData) {
94
+ fbq('init', pixelID, user_data)
95
+ if (debug) console.log('init', pixelID, user_data)
96
+ }
97
+
98
+ // `init` uses pixelID as the second param
99
+ if (event_name === 'init') {
100
+ fbq('init', pixelID)
101
+ if (debug) console.log('init', pixelID)
102
+ } else {
103
+ // Otherwise we can track the event as normal
104
+ const action = STANDARD_EVENTS.includes(event_name)
105
+ ? 'track'
106
+ : 'trackCustom'
107
+ fbq(action, event_name, props, { eventID })
108
+ if (debug) console.log(action, event_name, props, { eventID })
109
+ }
110
+ },
111
+
112
+ // CAPI Parameters: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/customer-information-parameters
113
+ sendCAPIRequest(data) {
114
+ return zlFetch.post(capiEndpoint, { body: data })
115
+ },
116
+ }
117
+ }
118
+
119
+ function initPixel(pixelID) {
120
+ // eslint-disable-next-line
121
+ !(function (f, b, e, v, n, t, s) {
122
+ if (f.fbq) return
123
+ n = f.fbq = function () {
124
+ n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments)
125
+ }
126
+ if (!f._fbq) f._fbq = n
127
+ n.push = n
128
+ n.loaded = !0
129
+ n.version = '2.0'
130
+ n.queue = []
131
+ t = b.createElement(e)
132
+ t.async = !0
133
+ t.src = v
134
+ s = b.getElementsByTagName(e)[0]
135
+ s.parentNode.insertBefore(t, s)
136
+ })(
137
+ window,
138
+ document,
139
+ 'script',
140
+ 'https://connect.facebook.net/en_US/fbevents.js',
141
+ )
142
+
143
+ if (typeof window === 'undefined') {
144
+ throw new Error(`'init' can only be called in the browser`)
145
+ } else {
146
+ fbq('init', pixelID)
147
+ }
148
+ }
@@ -0,0 +1,47 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, expect, test } from 'vitest'
3
+ import FB from './web.js'
4
+
5
+ describe('Web FB', () => {
6
+ test('createEvent', async () => {
7
+ // Set up cookies
8
+ const fbp = 'fb.1.12345.67890'
9
+ const fbc = 'fb.1.54321.09876'
10
+ document.cookie = `_fbp=${fbp}`
11
+ document.cookie = `_fbc=${fbc}`
12
+
13
+ const fb = FB({ pixelID: '1234567890', testEventCode: 'test' })
14
+ const { pixelData, capiData } = await fb.createEvent('Test Event', {
15
+ firstName: 'John',
16
+ lastName: 'Wick',
17
+ custom: 'yay',
18
+ })
19
+
20
+ // Pixel Data Tests
21
+ expect(pixelData.pixelID).toBe('1234567890')
22
+ expect(pixelData.eventID).toBeDefined()
23
+ expect(pixelData.eventID).toBe(capiData.event_id)
24
+ expect(pixelData.event_name).toBe('Test Event')
25
+ expect(pixelData.user_data.fn).toBeDefined()
26
+ expect(pixelData.user_data.ln).toBeDefined()
27
+ expect(pixelData.user_data.fn).not.toBe('John')
28
+ expect(pixelData.user_data.ln).not.toBe('Wick')
29
+ expect(pixelData.custom).toBe('yay')
30
+
31
+ // Capi Data Tests
32
+ expect(capiData.pixel_id).toBe('1234567890')
33
+ expect(capiData.event_id).toBeDefined()
34
+ expect(capiData.event_id).toBe(pixelData.eventID)
35
+ expect(capiData.event_name).toBe('Test Event')
36
+ expect(capiData.event_time).toBeDefined()
37
+ expect(capiData.event_source_url).toBeDefined()
38
+ expect(capiData.action_source).toBeDefined()
39
+ expect(capiData.user_data.fn).toBeDefined()
40
+ expect(capiData.user_data.ln).toBeDefined()
41
+ expect(capiData.user_data.fn).not.toBe('John')
42
+ expect(capiData.user_data.ln).not.toBe('Wick')
43
+ expect(capiData.user_data.fbp).toBe(fbp)
44
+ expect(capiData.user_data.fbc).toBe(fbc)
45
+ expect(capiData.custom_data.custom).toBe('yay')
46
+ })
47
+ })