@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.
@@ -0,0 +1,425 @@
1
+ export const STANDARD_EVENTS = [
2
+ 'AddPaymentInfo',
3
+ 'AddToCart',
4
+ 'AddToWishlist',
5
+ 'CompleteRegistration',
6
+ 'Contact',
7
+ 'CustomizeProduct',
8
+ 'Donate',
9
+ 'FindLocation',
10
+ 'InitiateCheckout',
11
+ 'Lead',
12
+ 'Purchase',
13
+ 'Schedule',
14
+ 'Search',
15
+ 'StartTrial',
16
+ 'SubmitApplication',
17
+ 'Subscribe',
18
+ 'ViewContent',
19
+ ]
20
+
21
+ // https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/server-event/
22
+ export const SERVER_EVENT_PARAMS = [
23
+ 'event_name',
24
+ 'event_time',
25
+ 'user_data',
26
+ 'custom_data',
27
+ 'event_source_url',
28
+ 'opt_out',
29
+ 'event_id',
30
+ 'action_source',
31
+ 'data_processing_options',
32
+ 'data_processing_options_country',
33
+ 'data_processing_options_state',
34
+ 'referrer_url',
35
+ 'customer_segmentation',
36
+ 'order_id',
37
+ ]
38
+
39
+ export const APP_DATA_PARAMS = [
40
+ 'advertiser_tracking_enabled',
41
+ 'application_tracking_enabled',
42
+ 'extinfo',
43
+ 'campaign_ids',
44
+ 'install_referrer',
45
+ 'installer_package',
46
+ 'url_schemes',
47
+ 'windows_attribution_id',
48
+ 'anon_id',
49
+ 'madid',
50
+ 'vendor_id',
51
+ ]
52
+
53
+ // https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/customer-information-parameters
54
+ export const USER_PROPERTIES_TO_HASH = [
55
+ 'email',
56
+ 'phone',
57
+ 'firstName',
58
+ 'lastName',
59
+ 'birthdate',
60
+ 'gender',
61
+ 'city',
62
+ 'state',
63
+ 'zip',
64
+ 'country',
65
+ 'externalID',
66
+ ]
67
+
68
+ // Other optional FB Stuff — doesn't seem very useful
69
+ export const USER_PROPERTIES_NOT_TO_HASH = [
70
+ 'subscription_id',
71
+ 'fb_login_id',
72
+ 'lead_id',
73
+ 'anon_id',
74
+ 'madid',
75
+ 'page_id',
76
+ 'page_scoped_user_id',
77
+ 'ctwa_clid',
78
+ 'ig_account_id',
79
+ 'ig_sid',
80
+ ]
81
+
82
+ export const USER_PROPERTIES = [
83
+ ...USER_PROPERTIES_TO_HASH,
84
+ ...USER_PROPERTIES_NOT_TO_HASH,
85
+ ]
86
+
87
+ // https://developers.facebook.com/docs/meta-pixel/reference/
88
+ export const CUSTOM_PROPERTIES = [
89
+ 'content_category',
90
+ 'content_ids',
91
+ 'content_name',
92
+ 'content_type',
93
+ 'contents',
94
+ 'currency',
95
+ 'delivery_category',
96
+ 'num_items',
97
+ 'predicted_ltv',
98
+ 'search_string',
99
+ 'status',
100
+ 'value',
101
+ ]
102
+
103
+ export const US_STATE_CODES = {
104
+ alabama: 'al',
105
+ alaska: 'ak',
106
+ 'american samoa': 'as',
107
+ arizona: 'az',
108
+ arkansas: 'ar',
109
+ 'baker island': 'bi',
110
+ california: 'ca',
111
+ colorado: 'co',
112
+ connecticut: 'ct',
113
+ delaware: 'de',
114
+ 'district of columbia': 'dc',
115
+ florida: 'fl',
116
+ 'federated states of micronesia': 'fm',
117
+ georgia: 'ga',
118
+ guam: 'gu',
119
+ hawaii: 'hi',
120
+ 'howland island': 'hi',
121
+ idaho: 'id',
122
+ illinois: 'il',
123
+ indiana: 'in',
124
+ iowa: 'ia',
125
+ 'jarvis island': 'ji',
126
+ 'johnston atoll': 'ja',
127
+ kansas: 'ks',
128
+ kentucky: 'ky',
129
+ 'kingman reef': 'kr',
130
+ louisiana: 'la',
131
+ maine: 'me',
132
+ 'marshall islands': 'mh',
133
+ maryland: 'md',
134
+ massachusetts: 'ma',
135
+ michigan: 'mi',
136
+ 'midway islands': 'mi',
137
+ minnesota: 'mn',
138
+ mississippi: 'ms',
139
+ missouri: 'mo',
140
+ montana: 'mt',
141
+ 'navassa island': 'ni',
142
+ nebraska: 'ne',
143
+ nevada: 'nv',
144
+ 'new hampshire': 'nh',
145
+ 'new jersey': 'nj',
146
+ 'new mexico': 'nm',
147
+ 'new york': 'ny',
148
+ 'north carolina': 'nc',
149
+ 'north dakota': 'nd',
150
+ 'northern mariana islands': 'mp',
151
+ ohio: 'oh',
152
+ oklahoma: 'ok',
153
+ oregon: 'or',
154
+ palau: 'pw',
155
+ 'palmyra atoll': 'pa',
156
+ pennsylvania: 'pa',
157
+ 'puerto rico': 'pr',
158
+ 'rhode island': 'ri',
159
+ 'south carolina': 'sc',
160
+ 'south dakota': 'sd',
161
+ tennessee: 'tn',
162
+ texas: 'tx',
163
+ 'u.s. minor outlying islands': 'um',
164
+ utah: 'ut',
165
+ vermont: 'vt',
166
+ virginia: 'va',
167
+ 'virgin islands of the u.s.': 'vi',
168
+ 'wake island': 'wi',
169
+ washington: 'wa',
170
+ 'west virginia': 'wv',
171
+ wisconsin: 'wi',
172
+ wyoming: 'wy',
173
+ }
174
+
175
+ export const COUNTRY_CODES = {
176
+ afghanistan: 'af',
177
+ 'åland islands': 'ax',
178
+ albania: 'al',
179
+ algeria: 'dz',
180
+ 'american samoa': 'as',
181
+ andorra: 'ad',
182
+ angola: 'ao',
183
+ anguilla: 'ai',
184
+ antarctica: 'aq',
185
+ 'antigua and barbuda': 'ag',
186
+ argentina: 'ar',
187
+ armenia: 'am',
188
+ aruba: 'aw',
189
+ australia: 'au',
190
+ austria: 'at',
191
+ azerbaijan: 'az',
192
+ bahamas: 'bs',
193
+ bahrain: 'bh',
194
+ bangladesh: 'bd',
195
+ barbados: 'bb',
196
+ belarus: 'by',
197
+ belgium: 'be',
198
+ belize: 'bz',
199
+ benin: 'bj',
200
+ bermuda: 'bm',
201
+ bhutan: 'bt',
202
+ 'bolivia (plurinational state of)': 'bo',
203
+ 'bonaire, sint eustatius and saba': 'bq',
204
+ 'bosnia and herzegovina': 'ba',
205
+ botswana: 'bw',
206
+ 'bouvet island': 'bv',
207
+ brazil: 'br',
208
+ 'british indian ocean territory': 'io',
209
+ 'brunei darussalam': 'bn',
210
+ brunei: 'bn',
211
+ bulgaria: 'bg',
212
+ 'burkina faso': 'bf',
213
+ burundi: 'bi',
214
+ 'cabo verde': 'cv',
215
+ cambodia: 'kh',
216
+ cameroon: 'cm',
217
+ canada: 'ca',
218
+ 'cayman islands': 'ky',
219
+ 'central african republic': 'cf',
220
+ chad: 'td',
221
+ chile: 'cl',
222
+ china: 'cn',
223
+ 'christmas island': 'cx',
224
+ 'cocos (keeling) islands': 'cc',
225
+ colombia: 'co',
226
+ comoros: 'km',
227
+ 'democratic republic of the congo': 'cd',
228
+ 'cook islands': 'ck',
229
+ 'costa rica': 'cr',
230
+ "côte d'ivoire": 'ci',
231
+ croatia: 'hr',
232
+ cuba: 'cu',
233
+ curaçao: 'cw',
234
+ cyprus: 'cy',
235
+ czechia: 'cz',
236
+ denmark: 'dk',
237
+ djibouti: 'dj',
238
+ dominica: 'dm',
239
+ 'dominican republic': 'do',
240
+ ecuador: 'ec',
241
+ egypt: 'eg',
242
+ 'el salvador': 'sv',
243
+ 'equatorial guinea': 'gq',
244
+ eritrea: 'er',
245
+ estonia: 'ee',
246
+ eswatini: 'sz',
247
+ ethiopia: 'et',
248
+ 'falkland islands': 'fk',
249
+ 'faroe islands': 'fo',
250
+ fiji: 'fj',
251
+ finland: 'fi',
252
+ france: 'fr',
253
+ 'french guiana': 'gf',
254
+ 'french polynesia': 'pf',
255
+ 'french southern territories': 'tf',
256
+ gabon: 'ga',
257
+ gambia: 'gm',
258
+ georgia: 'ge',
259
+ germany: 'de',
260
+ ghana: 'gh',
261
+ gibraltar: 'gi',
262
+ greece: 'gr',
263
+ greenland: 'gl',
264
+ grenada: 'gd',
265
+ guadeloupe: 'gp',
266
+ guam: 'gu',
267
+ guatemala: 'gt',
268
+ guernsey: 'gg',
269
+ guinea: 'gn',
270
+ 'guinea-bissau': 'gw',
271
+ guyana: 'gy',
272
+ haiti: 'ht',
273
+ 'heard island and mcdonald islands': 'hm',
274
+ 'holy see': 'va',
275
+ honduras: 'hn',
276
+ 'hong kong': 'hk',
277
+ hungary: 'hu',
278
+ iceland: 'is',
279
+ india: 'in',
280
+ indonesia: 'id',
281
+ iran: 'ir',
282
+ iraq: 'iq',
283
+ ireland: 'ie',
284
+ 'isle of man': 'im',
285
+ israel: 'il',
286
+ italy: 'it',
287
+ jamaica: 'jm',
288
+ japan: 'jp',
289
+ jersey: 'je',
290
+ jordan: 'jo',
291
+ kazakhstan: 'kz',
292
+ kenya: 'ke',
293
+ kiribati: 'ki',
294
+ 'north korea': 'kp',
295
+ 'south korea': 'kr',
296
+ kuwait: 'kw',
297
+ kyrgyzstan: 'kg',
298
+ laos: 'la',
299
+ latvia: 'lv',
300
+ lebanon: 'lb',
301
+ lesotho: 'ls',
302
+ liberia: 'lr',
303
+ libya: 'ly',
304
+ liechtenstein: 'li',
305
+ lithuania: 'lt',
306
+ luxembourg: 'lu',
307
+ macao: 'mo',
308
+ madagascar: 'mg',
309
+ malawi: 'mw',
310
+ malaysia: 'my',
311
+ maldives: 'mv',
312
+ mali: 'ml',
313
+ malta: 'mt',
314
+ 'marshall islands': 'mh',
315
+ martinique: 'mq',
316
+ mauritania: 'mr',
317
+ mauritius: 'mu',
318
+ mayotte: 'yt',
319
+ mexico: 'mx',
320
+ micronesia: 'fm',
321
+ moldova: 'md',
322
+ monaco: 'mc',
323
+ mongolia: 'mn',
324
+ montenegro: 'me',
325
+ montserrat: 'ms',
326
+ morocco: 'ma',
327
+ mozambique: 'mz',
328
+ myanmar: 'mm',
329
+ namibia: 'na',
330
+ nauru: 'nr',
331
+ nepal: 'np',
332
+ netherlands: 'nl',
333
+ 'new caledonia': 'nc',
334
+ 'new zealand': 'nz',
335
+ nicaragua: 'ni',
336
+ niger: 'ne',
337
+ nigeria: 'ng',
338
+ niue: 'nu',
339
+ 'norfolk island': 'nf',
340
+ 'north macedonia': 'mk',
341
+ 'northern mariana islands': 'mp',
342
+ norway: 'no',
343
+ oman: 'om',
344
+ pakistan: 'pk',
345
+ palau: 'pw',
346
+ palestine: 'ps',
347
+ panama: 'pa',
348
+ 'papua new guinea': 'pg',
349
+ paraguay: 'py',
350
+ peru: 'pe',
351
+ philippines: 'ph',
352
+ pitcairn: 'pn',
353
+ poland: 'pl',
354
+ portugal: 'pt',
355
+ 'puerto rico': 'pr',
356
+ qatar: 'qa',
357
+ réunion: 're',
358
+ romania: 'ro',
359
+ 'russian federation': 'ru',
360
+ rwanda: 'rw',
361
+ 'saint barthélemy': 'bl',
362
+ 'saint helena': 'sh',
363
+ 'saint kitts and nevis': 'kn',
364
+ 'saint lucia': 'lc',
365
+ 'saint martin': 'mf',
366
+ 'saint pierre and miquelon': 'pm',
367
+ 'saint vincent and the grenadines': 'vc',
368
+ samoa: 'ws',
369
+ 'san marino': 'sm',
370
+ 'sao tome and principe': 'st',
371
+ 'saudi arabia': 'sa',
372
+ senegal: 'sn',
373
+ serbia: 'rs',
374
+ seychelles: 'sc',
375
+ 'sierra leone': 'sl',
376
+ singapore: 'sg',
377
+ 'sint maarten': 'sx',
378
+ slovakia: 'sk',
379
+ slovenia: 'si',
380
+ 'solomon islands': 'sb',
381
+ somalia: 'so',
382
+ 'south africa': 'za',
383
+ 'south georgia and the south sandwich islands': 'gs',
384
+ 'south sudan': 'ss',
385
+ spain: 'es',
386
+ 'sri lanka': 'lk',
387
+ sudan: 'sd',
388
+ suriname: 'sr',
389
+ 'svalbard and jan mayen': 'sj',
390
+ sweden: 'se',
391
+ switzerland: 'ch',
392
+ 'syrian arab republic': 'sy',
393
+ taiwan: 'tw',
394
+ tajikistan: 'tj',
395
+ tanzania: 'tz',
396
+ thailand: 'th',
397
+ 'timor-leste': 'tl',
398
+ togo: 'tg',
399
+ tokelau: 'tk',
400
+ tonga: 'to',
401
+ 'trinidad and tobago': 'tt',
402
+ tunisia: 'tn',
403
+ türkiye: 'tr',
404
+ turkmenistan: 'tm',
405
+ 'turks and caicos islands': 'tc',
406
+ tuvalu: 'tv',
407
+ uganda: 'ug',
408
+ ukraine: 'ua',
409
+ 'united arab emirates': 'ae',
410
+ 'united kingdom': 'gb',
411
+ 'united states minor outlying islands': 'um',
412
+ 'united states': 'us',
413
+ uruguay: 'uy',
414
+ uzbekistan: 'uz',
415
+ vanuatu: 'vu',
416
+ venezuela: 've',
417
+ 'viet nam': 'vn',
418
+ 'british virgin islands': 'vg',
419
+ 'u.s. virgin islands': 'vi',
420
+ 'wallis and futuna': 'wf',
421
+ 'western sahara': 'eh',
422
+ yemen: 'ye',
423
+ zambia: 'zm',
424
+ zimbabwe: 'zw',
425
+ }
package/lib/fb/node.js ADDED
@@ -0,0 +1,141 @@
1
+ import { mix, omitEmpty, splitObject } from '@splendidlabz/utils'
2
+ import { randomString, sha256Hash } from '@splendidlabz/utils/node'
3
+ import zlFetch from 'zl-fetch'
4
+ import {
5
+ SERVER_EVENT_PARAMS,
6
+ USER_PROPERTIES,
7
+ USER_PROPERTIES_TO_HASH,
8
+ } from './consts.js'
9
+ import { formatUserData } from './utils.js'
10
+
11
+ // https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/
12
+ export default function FB({ pixelID, testEventCode, accessToken, framework }) {
13
+ framework = framework.toLowerCase()
14
+
15
+ if (!accessToken) throw new Error('Facebook Access Token required')
16
+
17
+ return {
18
+ // When used as API call — frontend would have done all the work
19
+ // So we can just send the body to the CAPI request
20
+ async sendEvent(props = {}, options = {}) {
21
+ const { pixel_id, test_event_code, ...rest } = props
22
+ const body = {
23
+ data: [omitEmpty(rest)],
24
+ test_event_code: test_event_code || testEventCode,
25
+ }
26
+ return this.sendCAPIRequest(body, { ...options, pixel_id })
27
+ },
28
+
29
+ async createEvent(eventName, props = {}, options = {}) {
30
+ const { context, ...p } = props
31
+ const { p: eventParams, o: r1 } = splitObject(p, SERVER_EVENT_PARAMS)
32
+ const { p: userProps, o: r2 } = splitObject(r1, USER_PROPERTIES)
33
+ const { fbc, fbp, ...rest } = r2
34
+
35
+ if (!framework) {
36
+ console.warn(
37
+ 'Framework not provided. Several properties will not be created.',
38
+ )
39
+ }
40
+
41
+ if (!context) {
42
+ console.warn(
43
+ 'Context not provided. Several properties will not be created.',
44
+ )
45
+ }
46
+
47
+ if (framework !== 'astro') {
48
+ throw new Error('Currently we only support the Astro framework')
49
+ }
50
+
51
+ const normalizedUserData = await normalizeUserData({
52
+ context,
53
+ framework,
54
+ userProps,
55
+ fbc,
56
+ fbp,
57
+ })
58
+
59
+ const data = omitEmpty(
60
+ mix(getEventParamDefaults(framework, context), eventParams, {
61
+ event_name: eventName,
62
+ user_data: normalizedUserData,
63
+ custom_data: rest,
64
+ }),
65
+ )
66
+
67
+ const body = omitEmpty({
68
+ test_event_code: testEventCode,
69
+ data: [data],
70
+ })
71
+
72
+ return body
73
+ },
74
+
75
+ // When used directly via backend
76
+ async track(eventName, props = {}, options = {}) {
77
+ const body = await this.createEvent(eventName, props, options)
78
+ return this.sendCAPIRequest(body, options)
79
+ },
80
+
81
+ async sendCAPIRequest(body, options) {
82
+ const { debug = false, dryRun = false, pixel_id } = options
83
+ const pixel = pixel_id || pixelID
84
+
85
+ if (debug) console.log(body)
86
+ if (dryRun) return
87
+
88
+ return zlFetch.post(
89
+ 'https://graph.facebook.com/v19.0/' + pixel + '/events',
90
+ {
91
+ auth: accessToken,
92
+ body,
93
+ returnError: true,
94
+ },
95
+ )
96
+ },
97
+ }
98
+ }
99
+
100
+ function getEventParamDefaults(framework, context) {
101
+ return {
102
+ event_id: randomString(10),
103
+ action_source: 'website',
104
+ event_time: Math.floor(new Date() / 1000),
105
+ event_source_url: getUrl(framework, context),
106
+ }
107
+ }
108
+
109
+ function getUrl(framework, context) {
110
+ if (framework === 'astro') return context.url.href
111
+ }
112
+
113
+ async function normalizeUserData({ context, framework, userProps, fbc, fbp }) {
114
+ let ip, userAgent
115
+
116
+ const { p: userPropsToHash, o: otherUserProps } = splitObject(
117
+ userProps,
118
+ USER_PROPERTIES_TO_HASH,
119
+ )
120
+
121
+ const hashedProps = await formatUserData(userPropsToHash, sha256Hash)
122
+
123
+ // Get other client data
124
+ if (framework === 'astro') {
125
+ ip = context.clientAddress
126
+ userAgent = context.request.headers.get('user-agent')
127
+ if (!fbp) fbp = context.cookies.get('_fbp')?.value
128
+ if (!fbc) fbc = context.cookies.get('_fbc')?.value
129
+ }
130
+
131
+ const userData = omitEmpty({
132
+ ...hashedProps,
133
+ ...otherUserProps,
134
+ client_ip_address: ip,
135
+ client_user_agent: userAgent,
136
+ fbc,
137
+ fbp,
138
+ })
139
+
140
+ return userData
141
+ }
@@ -0,0 +1,54 @@
1
+ Shared config
2
+
3
+ ```js
4
+ export default {
5
+ fb: {
6
+ pixelID: 'PIXEL ID',
7
+ testEventCode: 'TEST EVENT CODE',
8
+ },
9
+ }
10
+ ```
11
+
12
+ Setup
13
+
14
+ ```js
15
+ import FB from '@splendidlabz/tracking/fb/web'
16
+ import config from './shared'
17
+
18
+ export const fb = FB({
19
+ ...config.fb,
20
+ capiEndpoint: '/api/tracking/facebook/',
21
+ })
22
+ ```
23
+
24
+ Server
25
+
26
+ ```js
27
+ import FB from '@splendidlabz/tracking/fb/node'
28
+ import config from './shared'
29
+
30
+ export const fb = FB({
31
+ ...config.fb,
32
+ framework: 'astro',
33
+ accessToken: import.meta.env.FB_CAPI_TOKEN,
34
+ })
35
+ ```
36
+
37
+ Ads server for dedupe events
38
+
39
+ ```js
40
+ import { fb } from '@/services/tracking/node'
41
+ import { JSONResponse, parseData } from '@splendidlabz/astro/server'
42
+ export const prerender = false
43
+
44
+ export async function POST(context) {
45
+ const body = await parseData(context)
46
+ const { response, error } = await fb.sendEvent(body)
47
+ if (response) return new JSONResponse(response.body, { status: 200 })
48
+ if (error) return new JSONResponse(error, { status: 500 })
49
+ }
50
+ ```
51
+
52
+ Other stuff necessary.... well... check Facebook docs ba.
53
+ Perhaps should include a standard event list , or link to the docs.
54
+ Should also notify that `PageView` is going to be tracked as a custom event.