react-marketing-tools 1.0.0-alpha.4 → 1.0.0-beta.3
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 +154 -219
- package/dist/attribution/attribution.d.ts +21 -0
- package/dist/attribution/tracker.d.ts +21 -0
- package/dist/autocapture/clicks.d.ts +11 -0
- package/dist/chunks/cookies.js +49 -0
- package/dist/chunks/core.js +408 -168
- package/dist/core/errors.d.ts +1 -1
- package/dist/core/types.d.ts +78 -2
- 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/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 +8 -0
- package/dist/server.js +229 -0
- package/dist/shared/cookies.d.ts +5 -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 +46 -14
package/README.md
CHANGED
|
@@ -1,226 +1,161 @@
|
|
|
1
|
+
# React Marketing Tools
|
|
1
2
|
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
React Marketing Tools are a set of tools to make it easier for you to implement analytics and track user journeys, interactions throughout your App. using dataLayer/Google Tag Manager, GA4 fetch directly or coming soon facebook pixel.
|
|
5
|
-
|
|
6
|
-
* [React Marketing Tools Demo](https://codepen.io/bronz3beard/pen/yLZmMeg)
|
|
7
|
-
* [Detailed Blog post on React Marketing Tools Implementation](https://blog.heyrory.com/google-analytics-4-google-tag-manager)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
# PR's
|
|
11
|
-
- Have a look at the [PR template doc](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs) for best approach to getting your pr merged.
|
|
12
|
-
|
|
13
|
-
# CHANGELOG
|
|
14
|
-
- You can view it [here](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/CHANGELOG.md)
|
|
15
|
-
|
|
16
|
-
# Usage and setup examples.
|
|
17
|
-
|
|
18
|
-
### Setup configuration
|
|
19
|
-
- Setting up config without provider is also an option, in this case you will only need to import _buildConfig_
|
|
20
|
-
```js
|
|
21
|
-
import React from 'react'
|
|
22
|
-
import ReactDOM from 'react-dom/client'
|
|
23
|
-
import {
|
|
24
|
-
ReactMarketingProvider,
|
|
25
|
-
buildConfig,
|
|
26
|
-
BuildConfigOptions,
|
|
27
|
-
Tokens,
|
|
28
|
-
} from 'react-marketing-tools'
|
|
29
|
-
import App from './App'
|
|
30
|
-
|
|
31
|
-
/*
|
|
32
|
-
TOKENS are optional
|
|
33
|
-
const TOKENS: Tokens = {
|
|
34
|
-
|
|
35
|
-
// if withServerLocationInfo is true you must supply this token.
|
|
36
|
-
IP_INFO_TOKEN: 'SOME_TOKEN',
|
|
37
|
-
|
|
38
|
-
// if analyticsType = analyticsPlatform.GOOGLE the below tokens must be supplied.
|
|
39
|
-
GA4_PUBLIC_API_SECRET: 'SOME_TOKEN',
|
|
40
|
-
GA4_PUBLIC_MEASUREMENT_ID: 'SOME_TOKEN',
|
|
41
|
-
}
|
|
42
|
-
*/
|
|
43
|
-
|
|
44
|
-
// These are the keys for the values you want to include from your user data
|
|
45
|
-
// these must be included for any user data to be collected by analytics event if user data is hardcoded when passed in.
|
|
46
|
-
const includeUserKeys = [
|
|
47
|
-
'firstName',
|
|
48
|
-
'lastName',
|
|
49
|
-
]
|
|
50
|
-
|
|
51
|
-
const analyticsConfig: BuildConfigOptions = {
|
|
52
|
-
appName: 'my-awesome-app', // required
|
|
53
|
-
appSessionCookieName: 'APP_SESSION',
|
|
54
|
-
eventActionPrefix: { // this will extend the default values of eventActionPrefix
|
|
55
|
-
ACTION: 'ACTION',
|
|
56
|
-
OTHER_EVENT_NAME_TYPE: 'OTHER_EVENT_NAME_TYPE'
|
|
57
|
-
},
|
|
58
|
-
globalEventActionList: { // this will extend the default values of globalEventActionList
|
|
59
|
-
SIGN_IN: 'SIGN_IN',
|
|
60
|
-
SIGN_UP: 'SIGN_UP',
|
|
61
|
-
IMPORTANT_BUTTON_CLICKED: 'IMPORTANT_BUTTON_CLICKED'
|
|
62
|
-
},
|
|
63
|
-
// TOKENS // (optional),
|
|
64
|
-
includeUserKeys,
|
|
65
|
-
showMissingUserAttributesInConsole: false, // a boolean condition to show or hide "user" attributes that are not included in the "includeUserKeys" array, by console logging in dev tools.
|
|
66
|
-
withDeviceInfo: true, // (optional) has default value
|
|
67
|
-
withServerLocationInfo: false, // (optional) has default value
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* @type {Object} buildConfig -> options: all attributes of the options object must have a value, other than withDeviceInfo.
|
|
72
|
-
* @property {string} appName: the name of your app this value must be passed in.
|
|
73
|
-
* @property {string} appSessionCookieName This is used to get the cookie from storage based on a key you use, the value from the cookie will be used in "client_id:"
|
|
74
|
-
* @property {Object} eventActionPrefix: is a { key: 'value' } object that allows you to extend "analyticsEventActionPrefixList" object with custom eventActionPrefix. To see the build in list call the function showMeBuildInEventActionPrefixList().
|
|
75
|
-
* @property {Object} globalEventActionList: is a { key: 'value' } object that allows you to extend "analyticsGlobalEventActionList" object with custom eventActionNames. To see the build in list call the function showMeBuildInGlobalEventActionList().
|
|
76
|
-
* @property {Array} includeUserKeys: is an array of strings that represent keys from your user data that you want to whitelist, user data you wan to hash.
|
|
77
|
-
* @property {Boolean} showMissingUserAttributesInConsole a boolean condition to show or hide "user" attributes that are not included in the "includeUserKeys" array, by console logging in dev tools.
|
|
78
|
-
* @property {Object} TOKENS: is a { key: 'value' } object that includes the following keys, IP_INFO_TOKEN, GA4_PUBLIC_API_SECRET, GA4_PUBLIC_MEASUREMENT_ID, depending on if you need these features enabled.
|
|
79
|
-
* @property {Boolean} withDeviceInfo: if you want device information added to "globalVars" set this to true false by default.
|
|
80
|
-
* @property {Boolean} withServerLocationInfo: if you want server information added to "journeyProps" set this to true false by default.
|
|
81
|
-
*/
|
|
82
|
-
buildConfig(analyticsConfig)
|
|
83
|
-
|
|
84
|
-
ReactDOM.createRoot(document.getElementById('root')).render(
|
|
85
|
-
<React.StrictMode>
|
|
86
|
-
<ReactMarketingProvider>
|
|
87
|
-
<App />
|
|
88
|
-
</ReactMarketingProvider>
|
|
89
|
-
</React.StrictMode>
|
|
90
|
-
)
|
|
3
|
+
[](https://www.npmjs.com/package/react-marketing-tools?activeTab=versions)
|
|
4
|
+
[](./LICENSE)
|
|
91
5
|
|
|
6
|
+
One `track()` call for Google Tag Manager, Google Analytics 4 and the Meta Pixel, with Consent Mode v2, UTM attribution
|
|
7
|
+
and personal-data redaction built in.
|
|
8
|
+
|
|
9
|
+
**[Try it in the playground](https://bronz3beard.github.io/react-marketing-tools/)**: see what each vendor receives for
|
|
10
|
+
every event, without sending anything.
|
|
11
|
+
|
|
12
|
+
> **1.0 is in beta** on the `next` tag. `npm install react-marketing-tools` still installs 0.4.x, whose API 1.0
|
|
13
|
+
> replaces; see the [migration guide](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/migration-v1.md)
|
|
14
|
+
> and the [changelog](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/CHANGELOG.md).
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npm install react-marketing-tools@next
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Requires React 18 or 19. The package is ESM-only; server rendering needs Node.js 22.12 or later.
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
Create one instance:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
// analytics.ts
|
|
30
|
+
import { createAnalytics } from 'react-marketing-tools'
|
|
31
|
+
|
|
32
|
+
export const analytics = createAnalytics({
|
|
33
|
+
consent: 'denied', // until your consent banner records the visitor's choice
|
|
34
|
+
gtm: { containerId: 'GTM-XXXXXXX' },
|
|
35
|
+
ga4: { measurementId: 'G-XXXXXXX' },
|
|
36
|
+
metaPixel: { pixelId: '1234567890123456' },
|
|
37
|
+
})
|
|
92
38
|
```
|
|
93
39
|
|
|
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
|
-
dataLayerCheck: false,
|
|
178
|
-
userDataKeysToHashArray: null,
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
await trackAnalyticsEvent(trackingData)
|
|
182
|
-
}, [count])
|
|
183
|
-
|
|
184
|
-
/* Example: use GA4 directly
|
|
185
|
-
const handleButtonClick = useCallback(async () => {
|
|
186
|
-
const countActual = count + 1
|
|
187
|
-
|
|
188
|
-
setCount(countActual)
|
|
189
|
-
|
|
190
|
-
const eventNameInfo: EventNameInfo = {
|
|
191
|
-
eventName: 'count button click',
|
|
192
|
-
actionPrefix: eventActionPrefixList.INTERACTION,
|
|
193
|
-
globalAppEvent: analyticsGlobalEventActionList.AUTHENTICATED,
|
|
194
|
-
previousGlobalAppEvent: analyticsGlobalEventActionList.UNAUTHENTICATED,
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const trackingData: TrackAnalyticsEventOptions = {
|
|
198
|
-
data: {
|
|
199
|
-
count: countActual,
|
|
200
|
-
firstName: 'bob',
|
|
201
|
-
lastName: 'yeah nah',
|
|
202
|
-
email: 'yeahnah@gmail.com',
|
|
203
|
-
},
|
|
204
|
-
eventNameInfo,
|
|
205
|
-
analyticsType: analyticsPlatform.GOOGLE,
|
|
206
|
-
userDataKeysToHashArray: ['email', 'firstName', 'lastName'],
|
|
207
|
-
consoleLogData: {
|
|
208
|
-
showJourneyPropsPayload: true,
|
|
209
|
-
},
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
await trackAnalyticsEvent(trackingData)
|
|
213
|
-
}, [count])
|
|
214
|
-
*/
|
|
215
|
-
|
|
216
|
-
...
|
|
217
|
-
|
|
218
|
-
return (
|
|
219
|
-
...
|
|
220
|
-
)
|
|
221
|
-
}
|
|
40
|
+
Provide it to your app:
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
// main.tsx
|
|
44
|
+
import { AnalyticsProvider } from 'react-marketing-tools'
|
|
45
|
+
import { analytics } from './analytics'
|
|
46
|
+
|
|
47
|
+
createRoot(document.getElementById('root')!).render(
|
|
48
|
+
<AnalyticsProvider analytics={analytics}>
|
|
49
|
+
<App />
|
|
50
|
+
</AnalyticsProvider>,
|
|
51
|
+
)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Track from any component:
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
import { useAnalytics } from 'react-marketing-tools'
|
|
58
|
+
|
|
59
|
+
export const SignUpButton = () => {
|
|
60
|
+
const { track } = useAnalytics()
|
|
61
|
+
|
|
62
|
+
// Reaches GTM, GA4 and Meta (as CompleteRegistration) with one shared event_id
|
|
63
|
+
return <button onClick={() => track('sign_up', { method: 'google' })}>Sign up</button>
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Or without code, with `autocapture: { clicks: true }` in the config:
|
|
68
|
+
|
|
69
|
+
```html
|
|
70
|
+
<button data-analytics-event="cta_click" data-analytics-param-location="hero">Start</button>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Follow a multi-step flow as a funnel:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const checkout = analytics.journey('checkout')
|
|
77
|
+
checkout.step('shipping') // journey_start, then journey_step
|
|
78
|
+
checkout.complete({ value: 42, currency: 'USD' }) // journey_complete
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Report Core Web Vitals (LCP, INP, CLS) to GA4 and Tag Manager, after `npm install web-vitals@^6`:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { trackWebVitals } from 'react-marketing-tools/web-vitals'
|
|
85
|
+
|
|
86
|
+
void trackWebVitals(analytics)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The same instance identifies users, records consent and gives a consenting visitor a stable ID:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
analytics.identify('user-42', { email: 'ada@example.com' }) // user id for GTM and GA4, advanced matching for Meta
|
|
93
|
+
analytics.consent.update({ analytics: 'granted', ads: 'granted' }) // Google Consent Mode v2 and Meta consent
|
|
94
|
+
const visitorId = await analytics.getVisitorId() // random by default, or a fingerprint; never sent to GA4
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Configure only the destinations you use. Without React, import `createAnalytics` from `react-marketing-tools/core` and
|
|
98
|
+
call `analytics.start()` yourself.
|
|
99
|
+
|
|
100
|
+
Send events that happen on your server, such as a purchase confirmed by a payment webhook, from
|
|
101
|
+
`react-marketing-tools/server`:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { sendMeasurementProtocolEvent } from 'react-marketing-tools/server'
|
|
105
|
+
|
|
106
|
+
await sendMeasurementProtocolEvent({
|
|
107
|
+
measurementId: 'G-XXXXXXX',
|
|
108
|
+
apiSecret: process.env.GA4_API_SECRET!,
|
|
109
|
+
clientId: order.ga4ClientId, // saved at checkout with readGa4Cookies()
|
|
110
|
+
events: [{ name: 'purchase', params: { transaction_id: order.id, value: 42, currency: 'USD' } }],
|
|
111
|
+
})
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`sendConversionsApiEvent` does the same for Meta, hashing customer information as Meta requires. To have Meta receive
|
|
115
|
+
the events the browser Pixel misses, counted once, relay them through your server:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
// analytics.ts
|
|
119
|
+
createAnalytics({ /* …as above */ server: { endpoint: '/api/track' } })
|
|
120
|
+
|
|
121
|
+
// app/api/track/route.ts (Next.js; any Request → Response server works)
|
|
122
|
+
import { createTrackHandler } from 'react-marketing-tools/server'
|
|
222
123
|
|
|
124
|
+
export const POST = createTrackHandler({
|
|
125
|
+
allowedOrigins: ['https://shop.example.com'],
|
|
126
|
+
meta: { pixelId: '1234567890123456', accessToken: process.env.META_CAPI_TOKEN! },
|
|
127
|
+
})
|
|
223
128
|
```
|
|
224
129
|
|
|
225
|
-
##
|
|
226
|
-
|
|
130
|
+
## What each destination receives
|
|
131
|
+
|
|
132
|
+
| | Google Tag Manager | Google Analytics 4 | Meta Pixel | Conversions API relay |
|
|
133
|
+
| --- | --- | --- | --- | --- |
|
|
134
|
+
| `track()` | dataLayer push with `event_id` | gtag.js event | standard or custom event with `eventID` | the same event from your server |
|
|
135
|
+
| `identify()` | `user_id` | `user_id` | advanced matching | user data, hashed on your server |
|
|
136
|
+
| `consent.update()` | Consent Mode v2 | Consent Mode v2 | `grant` / `revoke` | only with `adUserData` |
|
|
137
|
+
| Attribution | `attribution` (last touch) | read from the page URL | `fbc` | `fbc`, `fbp` |
|
|
138
|
+
|
|
139
|
+
## Documentation
|
|
140
|
+
|
|
141
|
+
- [All docs](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/README.md)
|
|
142
|
+
- [Getting started](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/getting-started.md)
|
|
143
|
+
- [React](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/react.md): provider, hook, Next.js App Router, single-page apps
|
|
144
|
+
- [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
|
|
145
|
+
- [Configuration](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/configuration.md)
|
|
146
|
+
- [Consent](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/consent.md): Consent Mode v2 and Global Privacy Control
|
|
147
|
+
- [Attribution](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/attribution-utm.md): UTM params and ad click IDs
|
|
148
|
+
- [Visitor ID](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/visitor-id.md): a stable ID for consenting visitors, random or fingerprint
|
|
149
|
+
- [Google Tag Manager](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/google-tag-manager.md)
|
|
150
|
+
- [Google Analytics 4](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/google-analytics-4.md)
|
|
151
|
+
- [Meta Pixel](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/meta-pixel.md)
|
|
152
|
+
- [Server-side tagging](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/server-side-tagging.md)
|
|
153
|
+
- [GA4 Measurement Protocol](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/measurement-protocol.md): GA4 events from your server
|
|
154
|
+
- [Meta Conversions API](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/meta-conversions-api.md): Meta events from your server
|
|
155
|
+
- [Debugging](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/debugging.md): see what's sent, and fix common problems
|
|
156
|
+
- [Migrating from 0.4](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/migration-v1.md)
|
|
157
|
+
- [Changelog](https://github.com/bronz3beard/react-marketing-tools/blob/main/docs/CHANGELOG.md)
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Attribution, CampaignParam } from '../core/types.js';
|
|
2
|
+
export declare const CAMPAIGN_PARAMS: readonly CampaignParam[];
|
|
3
|
+
/**
|
|
4
|
+
* The campaign behind a visit, from its landing URL. `undefined` when the URL carries no campaign params, so a plain
|
|
5
|
+
* navigation never replaces an earlier touch. Values are email-redacted (email tools put addresses in `utm_term`).
|
|
6
|
+
*/
|
|
7
|
+
export declare const parseAttribution: ({ url, referrer, capturedAt, }: {
|
|
8
|
+
url: string;
|
|
9
|
+
referrer?: string;
|
|
10
|
+
capturedAt: number;
|
|
11
|
+
}) => Attribution | undefined;
|
|
12
|
+
/** Stored attribution is untrusted input: anything that isn't a well-formed touch is discarded. */
|
|
13
|
+
export declare const toAttribution: (value: unknown) => Attribution | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Meta's click ID for the Conversions API: the Pixel's `_fbc` cookie as-is (Meta may append to it), otherwise built from
|
|
16
|
+
* the touch's `fbclid` in Meta's documented `fb.1.<creation time ms>.<fbclid>` format.
|
|
17
|
+
*/
|
|
18
|
+
export declare const deriveFbc: ({ fbcCookie, touch, }: {
|
|
19
|
+
fbcCookie?: string;
|
|
20
|
+
touch?: Attribution;
|
|
21
|
+
}) => string | undefined;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Attribution } from '../core/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* First and last campaign touch. The caller decides when storage may be used: reading or writing device storage both
|
|
4
|
+
* need analytics consent (ePrivacy Directive Art. 5(3)).
|
|
5
|
+
*/
|
|
6
|
+
export declare const createAttributionTracker: ({ ttlDays }: {
|
|
7
|
+
ttlDays: number;
|
|
8
|
+
}) => {
|
|
9
|
+
/** Records a touch in memory. */
|
|
10
|
+
observe(touch: Attribution | undefined): void;
|
|
11
|
+
/** Merges touches stored on earlier visits: the oldest unexpired first touch wins; this session's last touch wins. */
|
|
12
|
+
restore(): void;
|
|
13
|
+
persist(): void;
|
|
14
|
+
/** Erases stored touches, e.g. when analytics consent is withdrawn. In-memory touches are kept for this page. */
|
|
15
|
+
erase(): void;
|
|
16
|
+
get: () => {
|
|
17
|
+
firstTouch: Attribution | undefined;
|
|
18
|
+
lastTouch: Attribution | undefined;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export type AttributionTracker = ReturnType<typeof createAttributionTracker>;
|
|
@@ -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 };
|