react-marketing-tools 1.0.0-alpha.5 → 1.0.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/README.md +338 -24
- package/dist/attribution/attribution.d.ts +11 -4
- package/dist/autocapture/clicks.d.ts +11 -0
- package/dist/chunks/cookies.js +49 -0
- package/dist/chunks/core.js +357 -211
- package/dist/core/errors.d.ts +1 -1
- package/dist/core/types.d.ts +58 -3
- package/dist/core.d.ts +1 -1
- package/dist/destinations/serverRelay.d.ts +34 -0
- package/dist/fingerprintjs.d.ts +8 -0
- package/dist/fingerprintjs.js +7 -0
- package/dist/identity/visitorId.d.ts +14 -0
- package/dist/journeys/journey.d.ts +8 -0
- package/dist/server/aiCrawlers.d.ts +37 -0
- package/dist/server/conversionsApi.d.ts +38 -0
- package/dist/server/createTrackHandler.d.ts +20 -0
- package/dist/server/ga4Cookies.d.ts +11 -0
- package/dist/server/measurementProtocol.d.ts +34 -0
- package/dist/server/normalize.d.ts +19 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.js +252 -0
- package/dist/shared/hash.d.ts +4 -0
- package/dist/shared/uuid.d.ts +1 -0
- package/dist/webVitals.d.ts +8 -0
- package/dist/webVitals.js +16 -0
- package/package.json +41 -6
package/README.md
CHANGED
|
@@ -1,87 +1,401 @@
|
|
|
1
1
|
# React Marketing Tools
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/react-marketing-tools)
|
|
4
4
|
[](./LICENSE)
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
Send one event from your React app and it reaches Google Tag Manager, Google Analytics 4 and the Meta Pixel at once.
|
|
7
|
+
The library keeps the visitor's privacy choices, remembers which campaign brought them, strips personal data out of
|
|
8
|
+
events, and can send the same events again from your server so they still arrive when a browser blocks tracking.
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
**[Try it in the playground](https://bronz3beard.github.io/react-marketing-tools/)**: press a button and see exactly
|
|
11
|
+
what each of those services would receive. Nothing is sent anywhere.
|
|
12
|
+
|
|
13
|
+
> **Setting this up with an AI assistant?** There's a prompt written for that:
|
|
14
|
+
> [Set this up with an AI assistant](#set-this-up-with-an-ai-assistant). It interviews you, writes the wiring, and
|
|
15
|
+
> tells you what to click in each service.
|
|
16
|
+
|
|
17
|
+
> **Coming from 0.4?** 1.0 is a rewrite, and the API is different. The
|
|
18
|
+
> [migration guide](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/migration-v1.md) maps the old
|
|
19
|
+
> names to the new ones, and the
|
|
20
|
+
> [changelog](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/CHANGELOG.md) lists everything that
|
|
21
|
+
> changed.
|
|
22
|
+
|
|
23
|
+
## Contents
|
|
24
|
+
|
|
25
|
+
- [What you need before you start](#what-you-need-before-you-start)
|
|
26
|
+
- [Install](#install)
|
|
27
|
+
- [Set this up with an AI assistant](#set-this-up-with-an-ai-assistant)
|
|
28
|
+
- [Quick start](#quick-start)
|
|
29
|
+
- [1. Create the analytics instance](#1-create-the-analytics-instance)
|
|
30
|
+
- [2. Hand it to your app](#2-hand-it-to-your-app)
|
|
31
|
+
- [3. Track an event](#3-track-an-event)
|
|
32
|
+
- [Track clicks without writing code](#track-clicks-without-writing-code)
|
|
33
|
+
- [Follow a multi-step flow](#follow-a-multi-step-flow)
|
|
34
|
+
- [Tell the library who the visitor is](#tell-the-library-who-the-visitor-is)
|
|
35
|
+
- [Record what the visitor consented to](#record-what-the-visitor-consented-to)
|
|
36
|
+
- [Recognise a returning visitor](#recognise-a-returning-visitor)
|
|
37
|
+
- [Report how fast your pages are](#report-how-fast-your-pages-are)
|
|
38
|
+
- [Send events from your server](#send-events-from-your-server)
|
|
39
|
+
- [A purchase confirmed by a payment webhook](#a-purchase-confirmed-by-a-payment-webhook)
|
|
40
|
+
- [Send the Pixel's events from your server too](#send-the-pixels-events-from-your-server-too)
|
|
41
|
+
- [Use it without React](#use-it-without-react)
|
|
42
|
+
- [What each service receives](#what-each-service-receives)
|
|
43
|
+
- [Documentation](#documentation)
|
|
44
|
+
- [License](#license)
|
|
45
|
+
|
|
46
|
+
## What you need before you start
|
|
47
|
+
|
|
48
|
+
This library sends events to accounts you already have. It doesn't create or configure them for you, and it isn't a
|
|
49
|
+
replacement for them. Before you install it, set up the ones you want to use:
|
|
50
|
+
|
|
51
|
+
| You want | What you need first | What the library needs from it |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| Google Tag Manager | A container, with the tags and triggers that decide what happens to each event | The container ID, like `GTM-XXXXXXX` |
|
|
54
|
+
| Google Analytics 4 | A property with a web data stream | The measurement ID, like `G-XXXXXXX` |
|
|
55
|
+
| Meta Pixel | A dataset (pixel) in Meta Events Manager | The pixel ID, a long number |
|
|
56
|
+
| Events sent from your server to GA4 | An API secret on that same data stream | The secret, kept on your server |
|
|
57
|
+
| Events sent from your server to Meta | A Conversions API access token in Events Manager | The token, kept on your server |
|
|
58
|
+
|
|
59
|
+
Two things worth knowing:
|
|
60
|
+
|
|
61
|
+
- **With Tag Manager, your container still decides what happens.** The library puts each event into the dataLayer, the
|
|
62
|
+
list of events Tag Manager watches. Until you add a trigger and a tag for an event name, the event is recorded but
|
|
63
|
+
goes nowhere.
|
|
64
|
+
- **With Google Analytics 4, events arrive on their own.** They show up as events with the name you tracked. Using the
|
|
65
|
+
names Google recommends, such as `purchase` or `sign_up`, fills in GA4's built-in reports; your own names appear in
|
|
66
|
+
reports and explorations once you use them.
|
|
67
|
+
|
|
68
|
+
You only configure the services you use. Many teams start with Tag Manager alone and add the rest later.
|
|
11
69
|
|
|
12
70
|
## Install
|
|
13
71
|
|
|
14
72
|
```sh
|
|
15
|
-
npm install react-marketing-tools
|
|
73
|
+
npm install react-marketing-tools
|
|
16
74
|
```
|
|
17
75
|
|
|
18
|
-
|
|
76
|
+
Works with React 18 and 19. The package is published as ES modules, the `import` style of JavaScript, so it works in
|
|
77
|
+
every current bundler and in Node.js 22.12 or later; it can't be loaded with `require()`.
|
|
78
|
+
|
|
79
|
+
## Set this up with an AI assistant
|
|
80
|
+
|
|
81
|
+
There's a prompt you can paste into any AI coding assistant. It reads your project, asks what you want to measure and
|
|
82
|
+
which analytics accounts you have, then writes the setup, a checklist of what to click in Google Tag Manager, Google
|
|
83
|
+
Analytics and Meta, and the steps to check it all worked.
|
|
84
|
+
|
|
85
|
+
**[Get the prompt](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/agent-setup.md)**, paste it into
|
|
86
|
+
your assistant, and answer its questions. That page also lists the six things to check before you trust what it wrote,
|
|
87
|
+
and an `AGENTS.md` block to keep in your repo so later sessions don't re-guess the conventions this one chose.
|
|
88
|
+
|
|
89
|
+
The prompt is tested: three assistants set up a Next.js app and a Vite app from scratch, and what they got wrong is
|
|
90
|
+
what those rules are there to prevent.
|
|
19
91
|
|
|
20
|
-
|
|
92
|
+
Working by hand is just as quick for a simple setup: the [quick start](#quick-start) below, or the
|
|
93
|
+
[integration walkthrough](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/integration-walkthrough.md)
|
|
94
|
+
for an app that already has analytics in it.
|
|
21
95
|
|
|
22
|
-
|
|
96
|
+
## Quick start
|
|
97
|
+
|
|
98
|
+
Adding this to an app that already exists, with analytics already in it? Follow the
|
|
99
|
+
[integration walkthrough](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/integration-walkthrough.md)
|
|
100
|
+
instead: same steps, plus how to prove events arrive and how to retire the library you're replacing.
|
|
101
|
+
|
|
102
|
+
### 1. Create the analytics instance
|
|
23
103
|
|
|
24
104
|
```ts
|
|
25
105
|
// analytics.ts
|
|
26
106
|
import { createAnalytics } from 'react-marketing-tools'
|
|
27
107
|
|
|
28
108
|
export const analytics = createAnalytics({
|
|
29
|
-
|
|
109
|
+
// Nothing is stored or sent until you record the visitor's choice; see "Record what the visitor consented to".
|
|
110
|
+
consent: 'denied',
|
|
30
111
|
gtm: { containerId: 'GTM-XXXXXXX' },
|
|
31
112
|
ga4: { measurementId: 'G-XXXXXXX' },
|
|
32
113
|
metaPixel: { pixelId: '1234567890123456' },
|
|
33
114
|
})
|
|
34
115
|
```
|
|
35
116
|
|
|
36
|
-
|
|
117
|
+
Creating the instance does nothing on its own: no scripts load and no cookies are written until your app starts it,
|
|
118
|
+
which the provider below does. That makes this file safe to import anywhere, including in server-rendered pages.
|
|
119
|
+
|
|
120
|
+
### 2. Hand it to your app
|
|
37
121
|
|
|
38
122
|
```tsx
|
|
39
123
|
// main.tsx
|
|
124
|
+
import { StrictMode } from 'react'
|
|
125
|
+
import { createRoot } from 'react-dom/client'
|
|
40
126
|
import { AnalyticsProvider } from 'react-marketing-tools'
|
|
41
127
|
import { analytics } from './analytics'
|
|
128
|
+
import { App } from './App'
|
|
42
129
|
|
|
43
130
|
createRoot(document.getElementById('root')!).render(
|
|
44
|
-
<
|
|
45
|
-
<
|
|
46
|
-
|
|
131
|
+
<StrictMode>
|
|
132
|
+
<AnalyticsProvider analytics={analytics}>
|
|
133
|
+
<App />
|
|
134
|
+
</AnalyticsProvider>
|
|
135
|
+
</StrictMode>,
|
|
47
136
|
)
|
|
48
137
|
```
|
|
49
138
|
|
|
50
|
-
|
|
139
|
+
The provider loads the vendor scripts once your app is running in the browser. Anything you track before that is held
|
|
140
|
+
and sent in order afterwards, so you never lose an event that happened during startup.
|
|
141
|
+
|
|
142
|
+
### 3. Track an event
|
|
51
143
|
|
|
52
144
|
```tsx
|
|
145
|
+
// SignUpButton.tsx
|
|
53
146
|
import { useAnalytics } from 'react-marketing-tools'
|
|
54
147
|
|
|
55
148
|
export const SignUpButton = () => {
|
|
56
149
|
const { track } = useAnalytics()
|
|
57
150
|
|
|
58
|
-
|
|
59
|
-
|
|
151
|
+
return (
|
|
152
|
+
<button onClick={() => track('sign_up', { method: 'google' })}>
|
|
153
|
+
Sign up
|
|
154
|
+
</button>
|
|
155
|
+
)
|
|
60
156
|
}
|
|
61
157
|
```
|
|
62
158
|
|
|
63
|
-
|
|
159
|
+
That one call reaches all three services: Tag Manager gets a `sign_up` event, Google Analytics 4 gets `sign_up`, and
|
|
160
|
+
the Meta Pixel gets `CompleteRegistration`, the name Meta uses for the same thing. All three carry the same event ID,
|
|
161
|
+
so when the same action arrives twice, from the browser and from your server, Meta counts it once.
|
|
162
|
+
|
|
163
|
+
## Track clicks without writing code
|
|
164
|
+
|
|
165
|
+
Turn it on once, and then mark the elements you care about. No analytics code in your components:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
// analytics.ts
|
|
169
|
+
import { createAnalytics } from 'react-marketing-tools'
|
|
170
|
+
|
|
171
|
+
export const analytics = createAnalytics({
|
|
172
|
+
consent: 'denied',
|
|
173
|
+
gtm: { containerId: 'GTM-XXXXXXX' },
|
|
174
|
+
autocapture: { clicks: true },
|
|
175
|
+
})
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
// PricingPage.tsx
|
|
180
|
+
export const PricingPage = () => (
|
|
181
|
+
<section>
|
|
182
|
+
<h1>Pricing</h1>
|
|
183
|
+
<button
|
|
184
|
+
data-analytics-event="cta_click"
|
|
185
|
+
data-analytics-param-location="pricing_header"
|
|
186
|
+
data-analytics-param-plan="pro"
|
|
187
|
+
>
|
|
188
|
+
Start free trial
|
|
189
|
+
</button>
|
|
190
|
+
</section>
|
|
191
|
+
)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
A click anywhere inside that button, including on an icon or text inside it, sends the same event as
|
|
195
|
+
`track('cta_click', { location: 'pricing_header', plan: 'pro' })`.
|
|
196
|
+
|
|
197
|
+
- `data-analytics-event` is the event name. Every `data-analytics-param-*` attribute becomes one detail on the event,
|
|
198
|
+
with dashes turned into underscores: `data-analytics-param-button-text` arrives as `button_text`.
|
|
199
|
+
- Values are text. For numbers, such as a price you want to add up, call `track()` instead.
|
|
200
|
+
- It works for elements added later, for example after a route change, and for links as well as buttons.
|
|
201
|
+
- If several marked elements are nested, the closest one to the click wins.
|
|
202
|
+
|
|
203
|
+
## Follow a multi-step flow
|
|
204
|
+
|
|
205
|
+
A journey groups the steps of a flow such as a checkout, so you can see where people drop out:
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
// Checkout.tsx
|
|
209
|
+
import { useState } from 'react'
|
|
210
|
+
import { useAnalytics } from 'react-marketing-tools'
|
|
211
|
+
|
|
212
|
+
export const Checkout = () => {
|
|
213
|
+
const { journey } = useAnalytics()
|
|
214
|
+
// Create it once for this flow, not on every render: each journey has its own ID.
|
|
215
|
+
const [checkout] = useState(() => journey('checkout'))
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
<>
|
|
219
|
+
<button onClick={() => checkout.step('shipping')}>Continue to payment</button>
|
|
220
|
+
<button onClick={() => checkout.complete({ value: 42, currency: 'USD' })}>Pay</button>
|
|
221
|
+
<button onClick={() => checkout.abandon('changed_mind')}>Cancel</button>
|
|
222
|
+
</>
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
The first call also sends a `journey_start` event, and every event in the flow carries the same journey ID. In Google
|
|
228
|
+
Analytics 4 you can then build a funnel from `journey_start`, `journey_step` and `journey_complete`.
|
|
229
|
+
|
|
230
|
+
## Tell the library who the visitor is
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
// after your sign-in code succeeds
|
|
234
|
+
import { analytics } from './analytics'
|
|
235
|
+
|
|
236
|
+
analytics.identify('user-42', { email: 'ada@example.com' })
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
The ID is your own user ID: it goes to Tag Manager and Google Analytics 4 so you can join sessions to accounts. The
|
|
240
|
+
email address is not sent to Google. It goes only to Meta, which uses contact details to match a visitor to a Facebook
|
|
241
|
+
or Instagram account, and it's scrambled into an unreadable fingerprint (a hash) before it's sent. Call
|
|
242
|
+
`analytics.reset()` when someone signs out.
|
|
243
|
+
|
|
244
|
+
## Record what the visitor consented to
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
// from your cookie banner, when the visitor answers
|
|
248
|
+
import { analytics } from './analytics'
|
|
249
|
+
|
|
250
|
+
analytics.consent.update({ analytics: 'granted', ads: 'granted' })
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Until this is called, the instance follows the `consent` value you passed when you created it. The library passes the
|
|
254
|
+
choice to Google's Consent Mode, which is how Google's tags are told what a visitor agreed to, and to the Meta Pixel,
|
|
255
|
+
which holds events until it's allowed to send them. The same choice controls what the library itself stores in the
|
|
256
|
+
browser. Both parts of the choice can be set separately if your banner asks separately.
|
|
257
|
+
|
|
258
|
+
## Recognise a returning visitor
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
import { analytics } from './analytics'
|
|
262
|
+
|
|
263
|
+
// inside an async function, any time after your app has started
|
|
264
|
+
const visitorId = await analytics.getVisitorId()
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
This is a random ID kept in the browser, given only to visitors who consented to analytics, so you can join a person's
|
|
268
|
+
visits in your own systems. It's never sent to Google Analytics. You can swap it for a browser fingerprint if you want
|
|
269
|
+
to recognise people who clear their cookies; see [Visitor ID](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/visitor-id.md).
|
|
270
|
+
|
|
271
|
+
## Report how fast your pages are
|
|
272
|
+
|
|
273
|
+
Core Web Vitals are Google's three measures of page experience: how fast the main content appears, how quickly the page
|
|
274
|
+
responds to a tap or click, and how much the layout jumps around. Install Google's measuring library and pass it your
|
|
275
|
+
instance:
|
|
276
|
+
|
|
277
|
+
```sh
|
|
278
|
+
npm install web-vitals@^6
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
// main.tsx, after the render call
|
|
283
|
+
import { trackWebVitals } from 'react-marketing-tools/web-vitals'
|
|
284
|
+
import { analytics } from './analytics'
|
|
285
|
+
|
|
286
|
+
void trackWebVitals(analytics)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Each measurement arrives as an event named `LCP`, `INP` or `CLS` in Google Analytics 4 and Tag Manager. They're never
|
|
290
|
+
sent to Meta.
|
|
291
|
+
|
|
292
|
+
## Send events from your server
|
|
293
|
+
|
|
294
|
+
Some things happen where the browser can't see them, such as a payment your payment provider confirms minutes later.
|
|
295
|
+
Other events simply never make it, because an extension or browser setting blocks the tracking scripts. For both, send
|
|
296
|
+
the event from your server.
|
|
297
|
+
|
|
298
|
+
### A purchase confirmed by a payment webhook
|
|
64
299
|
|
|
65
300
|
```ts
|
|
66
|
-
|
|
67
|
-
|
|
301
|
+
// inside your payment webhook, where `order` is the order you just confirmed
|
|
302
|
+
import { sendMeasurementProtocolEvent } from 'react-marketing-tools/server'
|
|
303
|
+
|
|
304
|
+
await sendMeasurementProtocolEvent({
|
|
305
|
+
measurementId: 'G-XXXXXXX',
|
|
306
|
+
apiSecret: process.env.GA4_API_SECRET!,
|
|
307
|
+
// Saved when the visitor started checkout, with readGa4Cookies() from the same import.
|
|
308
|
+
clientId: order.ga4ClientId,
|
|
309
|
+
events: [
|
|
310
|
+
{
|
|
311
|
+
name: 'purchase',
|
|
312
|
+
params: { transaction_id: order.id, value: order.total, currency: 'USD' },
|
|
313
|
+
},
|
|
314
|
+
],
|
|
315
|
+
})
|
|
68
316
|
```
|
|
69
317
|
|
|
70
|
-
|
|
71
|
-
|
|
318
|
+
The same import has `sendConversionsApiEvent` for Meta, which scrambles customer details into hashes the way Meta
|
|
319
|
+
requires before sending them.
|
|
320
|
+
|
|
321
|
+
### Send the Pixel's events from your server too
|
|
322
|
+
|
|
323
|
+
Point the page at an address on your own site, and mount the handler there. Every event the Pixel receives is then sent
|
|
324
|
+
from your server as well, and Meta counts each one once:
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
// analytics.ts
|
|
328
|
+
import { createAnalytics } from 'react-marketing-tools'
|
|
329
|
+
|
|
330
|
+
export const analytics = createAnalytics({
|
|
331
|
+
consent: 'denied',
|
|
332
|
+
metaPixel: { pixelId: '1234567890123456' },
|
|
333
|
+
server: { endpoint: '/api/track' },
|
|
334
|
+
})
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
// app/api/track/route.ts — a Next.js route; any server that speaks Request and Response works
|
|
339
|
+
import { createTrackHandler } from 'react-marketing-tools/server'
|
|
340
|
+
|
|
341
|
+
export const POST = createTrackHandler({
|
|
342
|
+
allowedOrigins: ['https://shop.example.com'],
|
|
343
|
+
meta: {
|
|
344
|
+
pixelId: '1234567890123456',
|
|
345
|
+
accessToken: process.env.META_CAPI_TOKEN!,
|
|
346
|
+
},
|
|
347
|
+
})
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## Use it without React
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
// analytics.ts
|
|
354
|
+
import { createAnalytics } from 'react-marketing-tools/core'
|
|
355
|
+
|
|
356
|
+
export const analytics = createAnalytics({
|
|
357
|
+
consent: 'granted',
|
|
358
|
+
gtm: { containerId: 'GTM-XXXXXXX' },
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
analytics.start() // the React provider does this for you
|
|
362
|
+
analytics.track('sign_up', { method: 'google' })
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## What each service receives
|
|
366
|
+
|
|
367
|
+
| | Google Tag Manager | Google Analytics 4 | Meta Pixel | Your server, for Meta |
|
|
368
|
+
| --- | --- | --- | --- | --- |
|
|
369
|
+
| `track()` | the event in the dataLayer, with an event ID | the event through Google's tag | Meta's name for the event, with the same event ID | the same event again, so it still arrives when the browser is blocked |
|
|
370
|
+
| `identify()` | your user ID | your user ID | contact details, hashed by the Pixel | contact details, hashed by your server |
|
|
371
|
+
| `consent.update()` | Google Consent Mode | Google Consent Mode | permission to send, or to hold | only sends with permission to share data with ad platforms |
|
|
372
|
+
| Campaign the visitor came from | the campaign on every event | read from the page address by Google's tag | the Meta click ID | the Meta click and browser IDs |
|
|
72
373
|
|
|
73
374
|
## Documentation
|
|
74
375
|
|
|
376
|
+
- [All docs](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/README.md)
|
|
75
377
|
- [Getting started](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/getting-started.md)
|
|
76
|
-
- [
|
|
77
|
-
- [
|
|
378
|
+
- [Integration walkthrough](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/integration-walkthrough.md): add it to an existing app, step by step, and prove events arrive
|
|
379
|
+
- [React](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/react.md): provider, hook, single-page apps
|
|
380
|
+
- [Next.js](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/nextjs.md): a complete App Router setup, client and server
|
|
381
|
+
- [Tracking events](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/tracking-events.md): naming rules, page views, journeys, click autocapture, Web Vitals, users, personal data, errors
|
|
78
382
|
- [Configuration](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/configuration.md)
|
|
79
383
|
- [Consent](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/consent.md): Consent Mode v2 and Global Privacy Control
|
|
80
384
|
- [Attribution](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/attribution-utm.md): UTM params and ad click IDs
|
|
385
|
+
- [Visitor ID](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/visitor-id.md): a stable ID for consenting visitors, random or fingerprint
|
|
81
386
|
- [Google Tag Manager](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/google-tag-manager.md)
|
|
82
387
|
- [Google Analytics 4](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/google-analytics-4.md)
|
|
83
388
|
- [Meta Pixel](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/meta-pixel.md)
|
|
389
|
+
- [Other tools](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/custom-destinations.md): PostHog, Umami, Plausible, your own endpoint
|
|
390
|
+
- [Measuring AI activity](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/ai-traffic.md): AI features, visits from AI assistants, AI crawlers
|
|
84
391
|
- [Server-side tagging](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/server-side-tagging.md)
|
|
392
|
+
- [GA4 Measurement Protocol](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/measurement-protocol.md): GA4 events from your server
|
|
393
|
+
- [Meta Conversions API](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/meta-conversions-api.md): Meta events from your server
|
|
394
|
+
- [Error tracking](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/error-tracking.md): report errors to Google Analytics 4
|
|
395
|
+
- [Debugging](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/debugging.md): see what's sent, and fix common problems
|
|
396
|
+
- [Migrating from 0.4](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/migration-v1.md)
|
|
397
|
+
- [API summary](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/api-summary.md): every option, method and limit on one page
|
|
398
|
+
- [Set up with an AI assistant](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/agent-setup.md): a prompt that interviews you and writes the setup
|
|
85
399
|
- [Changelog](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/CHANGELOG.md)
|
|
86
400
|
|
|
87
401
|
## License
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import type { Attribution, CampaignParam } from '../core/types.js';
|
|
1
|
+
import type { AiSources, Attribution, CampaignParam } from '../core/types.js';
|
|
2
2
|
export declare const CAMPAIGN_PARAMS: readonly CampaignParam[];
|
|
3
|
+
/** Your label for the AI assistant a referrer belongs to: an exact hostname match, or a subdomain of one. */
|
|
4
|
+
export declare const matchAiSource: ({ referrer, aiSources, }: {
|
|
5
|
+
referrer?: string;
|
|
6
|
+
aiSources?: AiSources;
|
|
7
|
+
}) => string | undefined;
|
|
3
8
|
/**
|
|
4
|
-
* The campaign behind a visit, from its landing URL. `undefined` when the URL carries
|
|
5
|
-
*
|
|
9
|
+
* The campaign behind a visit, from its landing URL, or the AI assistant that sent it. `undefined` when the URL carries
|
|
10
|
+
* no campaign params and the referrer isn't one of `aiSources`, so a plain navigation never replaces an earlier touch.
|
|
11
|
+
* Values are email-redacted (email tools put addresses in `utm_term`).
|
|
6
12
|
*/
|
|
7
|
-
export declare const parseAttribution: ({ url, referrer, capturedAt, }: {
|
|
13
|
+
export declare const parseAttribution: ({ url, referrer, capturedAt, aiSources, }: {
|
|
8
14
|
url: string;
|
|
9
15
|
referrer?: string;
|
|
10
16
|
capturedAt: number;
|
|
17
|
+
aiSources?: AiSources;
|
|
11
18
|
}) => Attribution | undefined;
|
|
12
19
|
/** Stored attribution is untrusted input: anything that isn't a well-formed touch is discarded. */
|
|
13
20
|
export declare const toAttribution: (value: unknown) => Attribution | undefined;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { EventParams } from '../core/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The event a click asks for: the nearest element around the click target with `data-analytics-event`, and its
|
|
4
|
+
* `data-analytics-param-*` attributes as params (`data-analytics-param-button-text` → `button_text`).
|
|
5
|
+
*/
|
|
6
|
+
export declare const readClickEvent: (target: EventTarget | null) => {
|
|
7
|
+
name: string;
|
|
8
|
+
params: EventParams;
|
|
9
|
+
} | undefined;
|
|
10
|
+
/** Tracks clicks on marked elements through one listener on the document, so elements added later are covered too. */
|
|
11
|
+
export declare const captureClicks: (track: (name: string, params: EventParams) => void) => void;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//#region lib/core/validate.ts
|
|
2
|
+
var e = /^[A-Za-z][A-Za-z0-9_]{0,39}$/, t = [
|
|
3
|
+
"google_",
|
|
4
|
+
"ga_",
|
|
5
|
+
"firebase_"
|
|
6
|
+
], n = 25, r = 100, i = {
|
|
7
|
+
page_location: 1e3,
|
|
8
|
+
page_referrer: 420,
|
|
9
|
+
page_title: 300
|
|
10
|
+
}, a = [
|
|
11
|
+
"email",
|
|
12
|
+
"phone",
|
|
13
|
+
"first_name",
|
|
14
|
+
"last_name",
|
|
15
|
+
"address",
|
|
16
|
+
"password"
|
|
17
|
+
], o = String.raw`[\w.+-]+(?:@|%40)[\w-]+(?:\.[\w-]+)+`, s = "[redacted]", c = (n) => {
|
|
18
|
+
if (!e.test(n)) return "must start with a letter, contain only letters, digits and underscores, and be at most 40 characters";
|
|
19
|
+
let r = t.find((e) => n.toLowerCase().startsWith(e));
|
|
20
|
+
return r && `must not start with the reserved prefix "${r}"`;
|
|
21
|
+
}, l = (e) => {
|
|
22
|
+
let t = c(e);
|
|
23
|
+
return t && `event name "${e}" ${t}`;
|
|
24
|
+
}, u = (e) => {
|
|
25
|
+
let t = Object.keys(e), a = t.flatMap((t) => {
|
|
26
|
+
let n = c(t);
|
|
27
|
+
if (n) return [`param "${t}" ${n}`];
|
|
28
|
+
let a = e[t], o = i[t] ?? r;
|
|
29
|
+
return typeof a == "string" && a.length > o ? [`param "${t}" is longer than ${o} characters`] : [];
|
|
30
|
+
});
|
|
31
|
+
return t.length > n ? [`has ${t.length} params; the limit is ${n}`, ...a] : a;
|
|
32
|
+
}, d = (e) => new RegExp(o, "i").test(e), f = (e, t) => (typeof t == "string" || typeof t == "number") && a.some((t) => e.toLowerCase().includes(t)) ? s : typeof t == "string" ? t.replace(new RegExp(o, "gi"), s) : t, p = (e) => {
|
|
33
|
+
let t = Object.entries(e).map(([e, t]) => [
|
|
34
|
+
e,
|
|
35
|
+
t,
|
|
36
|
+
f(e, t)
|
|
37
|
+
]);
|
|
38
|
+
return {
|
|
39
|
+
params: Object.fromEntries(t.map(([e, , t]) => [e, t])),
|
|
40
|
+
redactedKeys: t.filter(([, e, t]) => t !== e).map(([e]) => e)
|
|
41
|
+
};
|
|
42
|
+
}, m = (e, t) => {
|
|
43
|
+
for (let n of e.split(";")) {
|
|
44
|
+
let e = n.indexOf("=");
|
|
45
|
+
if (e !== -1 && n.slice(0, e).trim() === t) return n.slice(e + 1).trim();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
//#endregion
|
|
49
|
+
export { p as a, u as i, d as n, l as r, m as t };
|