@splendidlabz/tracking 0.2.9 → 0.2.11
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/node.js +64 -13
- package/lib/fb/readme.md +157 -19
- package/lib/fb/web.js +1 -2
- package/package.json +2 -2
package/lib/fb/node.js
CHANGED
|
@@ -18,9 +18,27 @@ export default function FB({ pixelID, testEventCode, accessToken, framework }) {
|
|
|
18
18
|
// When used as API call — frontend would have done all the work
|
|
19
19
|
// So we can just send the body to the CAPI request
|
|
20
20
|
async sendEvent(props = {}, options = {}) {
|
|
21
|
-
const { pixel_id, test_event_code, ...rest } = props
|
|
21
|
+
const { pixel_id, test_event_code, user_data, ...rest } = props
|
|
22
|
+
const { context } = options
|
|
23
|
+
|
|
24
|
+
const {
|
|
25
|
+
p: { fbc, fbp },
|
|
26
|
+
o: userProps,
|
|
27
|
+
} = splitObject(user_data, ['fbc', 'fbp'])
|
|
28
|
+
|
|
29
|
+
const data = {
|
|
30
|
+
...rest,
|
|
31
|
+
user_data: await normalizeUserData({
|
|
32
|
+
context,
|
|
33
|
+
framework,
|
|
34
|
+
userProps,
|
|
35
|
+
fbc,
|
|
36
|
+
fbp,
|
|
37
|
+
}),
|
|
38
|
+
}
|
|
39
|
+
|
|
22
40
|
const body = {
|
|
23
|
-
data: [omitEmpty(
|
|
41
|
+
data: [omitEmpty(data)],
|
|
24
42
|
test_event_code: test_event_code || testEventCode,
|
|
25
43
|
}
|
|
26
44
|
return this.sendCAPIRequest(body, { ...options, pixel_id })
|
|
@@ -44,8 +62,11 @@ export default function FB({ pixelID, testEventCode, accessToken, framework }) {
|
|
|
44
62
|
)
|
|
45
63
|
}
|
|
46
64
|
|
|
47
|
-
|
|
48
|
-
|
|
65
|
+
const supportedFrameworks = ['astro', 'express', 'fastify', 'koa']
|
|
66
|
+
if (!supportedFrameworks.includes(framework)) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`Unsupported framework: ${framework}. Supported frameworks: ${supportedFrameworks.join(', ')}`,
|
|
69
|
+
)
|
|
49
70
|
}
|
|
50
71
|
|
|
51
72
|
const normalizedUserData = await normalizeUserData({
|
|
@@ -97,6 +118,9 @@ export default function FB({ pixelID, testEventCode, accessToken, framework }) {
|
|
|
97
118
|
}
|
|
98
119
|
}
|
|
99
120
|
|
|
121
|
+
// ========================
|
|
122
|
+
// Helper Functions
|
|
123
|
+
// ========================
|
|
100
124
|
function getEventParamDefaults(framework, context) {
|
|
101
125
|
return {
|
|
102
126
|
event_id: randomString(10),
|
|
@@ -108,11 +132,40 @@ function getEventParamDefaults(framework, context) {
|
|
|
108
132
|
|
|
109
133
|
function getUrl(framework, context) {
|
|
110
134
|
if (framework === 'astro') return context.url.href
|
|
135
|
+
if (framework === 'express') {
|
|
136
|
+
return context.protocol + '://' + context.get('host') + context.originalUrl
|
|
137
|
+
}
|
|
138
|
+
if (framework === 'fastify') {
|
|
139
|
+
return context.protocol + '://' + context.hostname + context.url
|
|
140
|
+
}
|
|
141
|
+
if (framework === 'koa') {
|
|
142
|
+
return context.protocol + '://' + context.host + context.url
|
|
143
|
+
}
|
|
111
144
|
}
|
|
112
145
|
|
|
113
|
-
|
|
114
|
-
|
|
146
|
+
function getIP(framework, context) {
|
|
147
|
+
if (framework === 'astro') return context.clientAddress
|
|
148
|
+
if (framework === 'express')
|
|
149
|
+
return context.ip || context.connection?.remoteAddress
|
|
150
|
+
if (framework === 'fastify') return context.ip
|
|
151
|
+
if (framework === 'koa') return context.ip
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function getUserAgent(framework, context) {
|
|
155
|
+
if (framework === 'astro') return context.request.headers.get('user-agent')
|
|
156
|
+
if (framework === 'express') return context.get('user-agent')
|
|
157
|
+
if (framework === 'fastify') return context.headers['user-agent']
|
|
158
|
+
if (framework === 'koa') return context.get('user-agent')
|
|
159
|
+
}
|
|
115
160
|
|
|
161
|
+
function getCookie(framework, context, name) {
|
|
162
|
+
if (framework === 'astro') return context.cookies.get(name)?.value
|
|
163
|
+
if (framework === 'express') return context.cookies?.[name]
|
|
164
|
+
if (framework === 'fastify') return context.cookies?.[name]
|
|
165
|
+
if (framework === 'koa') return context.cookies?.get(name)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function normalizeUserData({ context, framework, userProps, fbc, fbp }) {
|
|
116
169
|
const { p: userPropsToHash, o: otherUserProps } = splitObject(
|
|
117
170
|
userProps,
|
|
118
171
|
USER_PROPERTIES_TO_HASH,
|
|
@@ -120,13 +173,11 @@ async function normalizeUserData({ context, framework, userProps, fbc, fbp }) {
|
|
|
120
173
|
|
|
121
174
|
const hashedProps = await formatUserData(userPropsToHash, sha256Hash)
|
|
122
175
|
|
|
123
|
-
// Get
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (!fbc) fbc = context.cookies.get('_fbc')?.value
|
|
129
|
-
}
|
|
176
|
+
// Get client data from context
|
|
177
|
+
const ip = getIP(framework, context)
|
|
178
|
+
const userAgent = getUserAgent(framework, context)
|
|
179
|
+
if (!fbp) fbp = getCookie(framework, context, '_fbp')
|
|
180
|
+
if (!fbc) fbc = getCookie(framework, context, '_fbc')
|
|
130
181
|
|
|
131
182
|
const userData = omitEmpty({
|
|
132
183
|
...hashedProps,
|
package/lib/fb/readme.md
CHANGED
|
@@ -1,42 +1,43 @@
|
|
|
1
|
-
|
|
1
|
+
Designed to provide a unified interface for Facebook Pixel and Conversion API.
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
Config
|
|
2
6
|
|
|
3
7
|
```js
|
|
4
8
|
export default {
|
|
5
9
|
fb: {
|
|
6
10
|
pixelID: 'PIXEL ID',
|
|
7
11
|
testEventCode: 'TEST EVENT CODE',
|
|
12
|
+
capiEndpoint: '/api/tracking/facebook/',
|
|
13
|
+
accessToken: import.meta.env.FB_CAPI_TOKEN,
|
|
14
|
+
framework: 'astro', // or 'express', 'fastify', 'koa'
|
|
8
15
|
},
|
|
9
16
|
}
|
|
10
17
|
```
|
|
11
18
|
|
|
12
|
-
|
|
19
|
+
Browser
|
|
13
20
|
|
|
14
21
|
```js
|
|
15
22
|
import FB from '@splendidlabz/tracking/fb/web'
|
|
16
|
-
import config from './
|
|
17
|
-
|
|
18
|
-
export const fb = FB({
|
|
19
|
-
...config.fb,
|
|
20
|
-
capiEndpoint: '/api/tracking/facebook/',
|
|
21
|
-
})
|
|
23
|
+
import config from './config'
|
|
24
|
+
export const fb = FB(config.fb)
|
|
22
25
|
```
|
|
23
26
|
|
|
24
27
|
Server
|
|
25
28
|
|
|
26
29
|
```js
|
|
27
30
|
import FB from '@splendidlabz/tracking/fb/node'
|
|
28
|
-
import config from './
|
|
29
|
-
|
|
30
|
-
export const fb = FB({
|
|
31
|
-
...config.fb,
|
|
32
|
-
framework: 'astro',
|
|
33
|
-
accessToken: import.meta.env.FB_CAPI_TOKEN,
|
|
34
|
-
})
|
|
31
|
+
import config from './config'
|
|
32
|
+
export const fb = FB(config.fb)
|
|
35
33
|
```
|
|
36
34
|
|
|
37
|
-
|
|
35
|
+
Include API to dedupe events through CAPI
|
|
36
|
+
|
|
37
|
+
**Astro:**
|
|
38
38
|
|
|
39
39
|
```js
|
|
40
|
+
// /api/tracking/facebook/
|
|
40
41
|
import { fb } from '@/services/tracking/node'
|
|
41
42
|
import { JSONResponse, parseData } from '@splendidlabz/astro/server'
|
|
42
43
|
export const prerender = false
|
|
@@ -49,6 +50,143 @@ export async function POST(context) {
|
|
|
49
50
|
}
|
|
50
51
|
```
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
**Express:**
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
// Requires cookie-parser middleware for cookie support
|
|
57
|
+
import express from 'express'
|
|
58
|
+
import cookieParser from 'cookie-parser'
|
|
59
|
+
import { fb } from './services/tracking/node.js'
|
|
60
|
+
|
|
61
|
+
const app = express()
|
|
62
|
+
app.use(express.json())
|
|
63
|
+
app.use(cookieParser())
|
|
64
|
+
|
|
65
|
+
app.post('/api/tracking/facebook', async (req, res) => {
|
|
66
|
+
const { response, error } = await fb.sendEvent(req.body)
|
|
67
|
+
if (response) return res.status(200).json(response.body)
|
|
68
|
+
if (error) return res.status(500).json(error)
|
|
69
|
+
})
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Fastify:**
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
// Requires @fastify/cookie plugin for cookie support
|
|
76
|
+
import Fastify from 'fastify'
|
|
77
|
+
import fastifyCookie from '@fastify/cookie'
|
|
78
|
+
import { fb } from './services/tracking/node.js'
|
|
79
|
+
|
|
80
|
+
const fastify = Fastify()
|
|
81
|
+
await fastify.register(fastifyCookie)
|
|
82
|
+
|
|
83
|
+
fastify.post('/api/tracking/facebook', async (request, reply) => {
|
|
84
|
+
const { response, error } = await fb.sendEvent(request.body)
|
|
85
|
+
if (response) return reply.status(200).send(response.body)
|
|
86
|
+
if (error) return reply.status(500).send(error)
|
|
87
|
+
})
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Koa:**
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
// Requires koa-bodyparser and koa-cookie middleware
|
|
94
|
+
import Koa from 'koa'
|
|
95
|
+
import bodyParser from 'koa-bodyparser'
|
|
96
|
+
import cookie from 'koa-cookie'
|
|
97
|
+
import { fb } from './services/tracking/node.js'
|
|
98
|
+
|
|
99
|
+
const app = new Koa()
|
|
100
|
+
app.use(bodyParser())
|
|
101
|
+
app.use(cookie())
|
|
102
|
+
|
|
103
|
+
app.use(async (ctx) => {
|
|
104
|
+
if (ctx.path === '/api/tracking/facebook' && ctx.method === 'POST') {
|
|
105
|
+
const { response, error } = await fb.sendEvent(ctx.request.body)
|
|
106
|
+
if (response) {
|
|
107
|
+
ctx.status = 200
|
|
108
|
+
ctx.body = response.body
|
|
109
|
+
}
|
|
110
|
+
if (error) {
|
|
111
|
+
ctx.status = 500
|
|
112
|
+
ctx.body = error
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Usage
|
|
119
|
+
|
|
120
|
+
Browser:
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
fb.track('event_name', { ...props })
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Node (requires passing the request context):
|
|
127
|
+
|
|
128
|
+
**Astro:**
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
fb.track('event_name', { context, ...props })
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Express:**
|
|
135
|
+
|
|
136
|
+
```js
|
|
137
|
+
app.post('/some-route', async (req, res) => {
|
|
138
|
+
await fb.track('event_name', { context: req, ...props })
|
|
139
|
+
res.json({ success: true })
|
|
140
|
+
})
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Fastify:**
|
|
144
|
+
|
|
145
|
+
```js
|
|
146
|
+
fastify.post('/some-route', async (request, reply) => {
|
|
147
|
+
await fb.track('event_name', { context: request, ...props })
|
|
148
|
+
reply.send({ success: true })
|
|
149
|
+
})
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Koa:**
|
|
153
|
+
|
|
154
|
+
```js
|
|
155
|
+
app.use(async (ctx) => {
|
|
156
|
+
if (ctx.path === '/some-route' && ctx.method === 'POST') {
|
|
157
|
+
await fb.track('event_name', { context: ctx, ...props })
|
|
158
|
+
ctx.body = { success: true }
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Event Reference
|
|
164
|
+
|
|
165
|
+
- [Standard Event Reference](https://developers.facebook.com/docs/meta-pixel/reference#standard-events)
|
|
166
|
+
- [Advanced Matching Reference](https://developers.facebook.com/docs/meta-pixel/advanced/advanced-matching/)
|
|
167
|
+
- [Conversion API Params Reference](https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/customer-information-parameters)
|
|
168
|
+
|
|
169
|
+
## Testing Your Implementation
|
|
170
|
+
|
|
171
|
+
Go to `Events Manager > Datasets > Test Events` and copy your event tracking code. Add it to your config.
|
|
172
|
+
|
|
173
|
+
**IMPORTANT**: Remove `testEventCode` after your tests! You don't want this in your production code.
|
|
174
|
+
|
|
175
|
+
```js
|
|
176
|
+
export default {
|
|
177
|
+
fb: {
|
|
178
|
+
pixelID: 'PIXEL ID',
|
|
179
|
+
testEventCode: 'TEST EVENT CODE',
|
|
180
|
+
// ...
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Under the same `Test Events` tab, enter a website to test with.
|
|
186
|
+
|
|
187
|
+
- Note that localhost cannot be used for browser-testing — FB requires a valid domain
|
|
188
|
+
- To test if server & browser events are deduplicated, use a local tunnel like ngrok.
|
|
189
|
+
- Make sure your website — and your tunnel — is listed under your Pixel's allow list under
|
|
190
|
+
`Dataset > Settings > Allow list`. Feel free to remove the tunnel from the allow list after testing.
|
|
191
|
+
|
|
192
|
+
- Context is to automatically get Client IP Address and User Agent.
|
package/lib/fb/web.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { notEmpty, splitObject } from '@splendidlabz/utils'
|
|
2
2
|
import { getCookie, randomString, sha256Hash } from '@splendidlabz/utils/dom'
|
|
3
|
+
import zlFetch from 'zl-fetch'
|
|
3
4
|
import {
|
|
4
5
|
STANDARD_EVENTS,
|
|
5
6
|
USER_PROPERTIES,
|
|
6
7
|
USER_PROPERTIES_TO_HASH,
|
|
7
8
|
} from './consts.js'
|
|
8
|
-
|
|
9
|
-
import zlFetch from 'zl-fetch'
|
|
10
9
|
import { formatUserData } from './utils.js'
|
|
11
10
|
|
|
12
11
|
export default function FB({ pixelID, testEventCode, capiEndpoint }) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@splendidlabz/tracking",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "Simple Tracking Helper",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"author": "Zell Liew <zellwk@gmail.com> (https://zellwk.com/)",
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@splendidlabz/utils": "1.10.
|
|
23
|
+
"@splendidlabz/utils": "1.10.3",
|
|
24
24
|
"posthog-js": "^1.234.6",
|
|
25
25
|
"posthog-node": "^5.5.1",
|
|
26
26
|
"zl-fetch": "^6.0.6"
|