@affiliateo/web 4.4.1 → 4.5.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 +238 -238
- package/dist/provider.d.ts +1 -1
- package/dist/provider.js +31 -16
- package/dist/types.d.ts +10 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,238 +1,238 @@
|
|
|
1
|
-
# @affiliateo/web
|
|
2
|
-
|
|
3
|
-
Affiliateo web SDK. track affiliate visits and pass attribution metadata to your Stripe checkout.
|
|
4
|
-
|
|
5
|
-
## Install
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install @affiliateo/web
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Setup
|
|
12
|
-
|
|
13
|
-
1. Connect your Stripe account on the Affiliateo dashboard
|
|
14
|
-
2. Wrap your app in `<AffiliateoProvider>` with your
|
|
15
|
-
3. At checkout time, call `getAttribution()` and pass the result to Stripe metadata
|
|
16
|
-
|
|
17
|
-
### Step 1. wrap your app
|
|
18
|
-
|
|
19
|
-
```tsx
|
|
20
|
-
// app/layout.tsx (Next.js App Router) or _app.tsx (Pages Router)
|
|
21
|
-
import { AffiliateoProvider } from '@affiliateo/web';
|
|
22
|
-
|
|
23
|
-
export default function RootLayout({ children }) {
|
|
24
|
-
return (
|
|
25
|
-
<AffiliateoProvider
|
|
26
|
-
{children}
|
|
27
|
-
</AffiliateoProvider>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
The provider:
|
|
33
|
-
- Reads `?ref=` from the URL when a visitor arrives
|
|
34
|
-
- Stores attribution in `localStorage` so it survives navigation
|
|
35
|
-
- Sends pageview events to Affiliateo for click tracking
|
|
36
|
-
|
|
37
|
-
### Step 2. in your checkout button (client-side)
|
|
38
|
-
|
|
39
|
-
```tsx
|
|
40
|
-
'use client'
|
|
41
|
-
import { getAttribution } from '@affiliateo/web';
|
|
42
|
-
|
|
43
|
-
async function handleCheckout() {
|
|
44
|
-
const attribution = getAttribution();
|
|
45
|
-
|
|
46
|
-
const res = await fetch('/api/checkout', {
|
|
47
|
-
method: 'POST',
|
|
48
|
-
body: JSON.stringify({ attribution }),
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
const { url } = await res.json();
|
|
52
|
-
window.location.href = url;
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
`getAttribution()` reads
|
|
57
|
-
|
|
58
|
-
### Step 3. in your checkout API route (server-side)
|
|
59
|
-
|
|
60
|
-
```ts
|
|
61
|
-
// app/api/checkout/route.ts
|
|
62
|
-
export async function POST(req) {
|
|
63
|
-
const { attribution } = await req.json();
|
|
64
|
-
|
|
65
|
-
const session = await stripe.checkout.sessions.create({
|
|
66
|
-
line_items: [{ price: 'price_xxx', quantity: 1 }],
|
|
67
|
-
mode: 'payment',
|
|
68
|
-
success_url: 'https://yoursite.com/success',
|
|
69
|
-
cancel_url: 'https://yoursite.com/cancel',
|
|
70
|
-
metadata: attribution,
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
return Response.json({ url: session.url });
|
|
74
|
-
}
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
That's it. Affiliateo's webhook will automatically read the metadata and credit the right affiliate when the payment succeeds.
|
|
78
|
-
|
|
79
|
-
## How it works
|
|
80
|
-
|
|
81
|
-
1. Visitor clicks affiliate link → `yoursite.com?ref=pinkproton`
|
|
82
|
-
2. `<AffiliateoProvider>` reads `?ref=pinkproton` from the URL and saves it in `localStorage`
|
|
83
|
-
3. As the visitor browses, attribution stays in their browser
|
|
84
|
-
4. At checkout, `getAttribution()` reads localStorage and returns the attribution object
|
|
85
|
-
5. Your server passes it as Stripe metadata when creating the checkout session
|
|
86
|
-
6. When Stripe fires the `checkout.session.completed` webhook, Affiliateo reads the metadata and credits the affiliate
|
|
87
|
-
|
|
88
|
-
## Why `localStorage` and not cookies?
|
|
89
|
-
|
|
90
|
-
- **Not blocked by Safari ITP or most privacy tools**
|
|
91
|
-
- **No CSRF concerns**
|
|
92
|
-
|
|
93
|
-
## GDPR / ePrivacy. merchant responsibility
|
|
94
|
-
|
|
95
|
-
The SDK writes persistent identifiers (`affiliateo_visitor_id`, `affiliateo_ref`, `affiliateo_campaign_id`) to the visitor's `localStorage` and sends pageview events that include the visitor's IP, user agent, and approximate geolocation to Affiliateo's servers. Under EU ePrivacy (Article 5(3)) and GDPR, persistent identifiers stored on a visitor's device require prior consent the same way cookies do.
|
|
96
|
-
|
|
97
|
-
If you serve visitors in the EU/EEA, UK, or any jurisdiction with equivalent rules, **you are responsible for obtaining consent before this script loads**, the same way you would for Google Analytics, Meta Pixel, or any other analytics tool. Typical approaches:
|
|
98
|
-
|
|
99
|
-
- Gate the `<AffiliateoProvider>` (or the `<script src="…/t.js">` tag) behind your cookie banner's "accept" callback so it only loads after consent.
|
|
100
|
-
- Document Affiliateo in your privacy policy as a processor (see our [Data Processing Agreement](https://affiliateo.com/dpa)) and list the categories of data collected.
|
|
101
|
-
|
|
102
|
-
Affiliateo acts as your data processor for visitor analytics. You are the controller and decide the lawful basis for processing your visitors' data.
|
|
103
|
-
|
|
104
|
-
## Alternative: vanilla HTML (no React)
|
|
105
|
-
|
|
106
|
-
If you're not using React, use the tracking script directly instead of the provider:
|
|
107
|
-
|
|
108
|
-
```html
|
|
109
|
-
<script defer
|
|
110
|
-
data-
|
|
111
|
-
src="https://affiliateo.com/t.js"
|
|
112
|
-
></script>
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
Then in your checkout flow (vanilla JS):
|
|
116
|
-
|
|
117
|
-
```js
|
|
118
|
-
const attribution = {
|
|
119
|
-
affiliateo_ref: localStorage.getItem('affiliateo_ref'),
|
|
120
|
-
affiliateo_visitor_id: localStorage.getItem('affiliateo_visitor_id'),
|
|
121
|
-
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
// Send to your server with the rest of your checkout payload
|
|
125
|
-
fetch('/api/checkout', {
|
|
126
|
-
method: 'POST',
|
|
127
|
-
body: JSON.stringify({ attribution }),
|
|
128
|
-
});
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
## Track conversion goals
|
|
132
|
-
|
|
133
|
-
Pageviews auto-track. For specific moments that matter to your funnel
|
|
134
|
-
(`signup_completed`, `trial_started`, `initiate_checkout`, etc.), use
|
|
135
|
-
`track()` or HTML data attributes.
|
|
136
|
-
|
|
137
|
-
### From React
|
|
138
|
-
|
|
139
|
-
```tsx
|
|
140
|
-
'use client'
|
|
141
|
-
import { useEffect } from 'react'
|
|
142
|
-
import { useAffiliateRef } from '@affiliateo/web'
|
|
143
|
-
|
|
144
|
-
export default function SignupSuccess() {
|
|
145
|
-
const { track } = useAffiliateRef()
|
|
146
|
-
useEffect(() => {
|
|
147
|
-
track('signup_completed', { plan: 'free' })
|
|
148
|
-
}, [])
|
|
149
|
-
return <div>Welcome!</div>
|
|
150
|
-
}
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
### HTML data attributes (zero JS, works everywhere)
|
|
154
|
-
|
|
155
|
-
```html
|
|
156
|
-
<button data-affiliateo-goal="initiate_checkout"
|
|
157
|
-
data-affiliateo-goal-price="49"
|
|
158
|
-
data-affiliateo-goal-plan="pro">
|
|
159
|
-
Subscribe
|
|
160
|
-
</button>
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
The SDK auto-fires the goal on click. Optional `data-affiliateo-goal-*`
|
|
164
|
-
attributes become metadata (kebab-case → snake_case, up to 10 params).
|
|
165
|
-
|
|
166
|
-
### Scroll-depth tracking
|
|
167
|
-
|
|
168
|
-
```html
|
|
169
|
-
<section data-affiliateo-scroll="pricing_viewed">
|
|
170
|
-
...your pricing content...
|
|
171
|
-
</section>
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
Fires the first time any pixel of the section overlaps the viewport.
|
|
175
|
-
Maximum permissiveness — if the user scrolled enough to see even a sliver,
|
|
176
|
-
it counts. Earlier versions required 50% visibility, which was unreachable
|
|
177
|
-
for tall sections (a pricing page with comparison tables silently dropped
|
|
178
|
-
out of every funnel). Great for landing pages where you want to know
|
|
179
|
-
which sections convert.
|
|
180
|
-
|
|
181
|
-
### Goal naming rules
|
|
182
|
-
|
|
183
|
-
Lowercase letters, digits, underscores, hyphens, colons. Max 64 chars.
|
|
184
|
-
Metadata bounded to 4KB JSON.
|
|
185
|
-
|
|
186
|
-
## Identify signed-in users (cross-device stitching)
|
|
187
|
-
|
|
188
|
-
Without `identify()`, the same person browsing on phone and buying on
|
|
189
|
-
laptop counts as two visitors. Call it once after sign-in:
|
|
190
|
-
|
|
191
|
-
```tsx
|
|
192
|
-
'use client'
|
|
193
|
-
import { useEffect } from 'react'
|
|
194
|
-
import { useAffiliateRef } from '@affiliateo/web'
|
|
195
|
-
import { useUser } from './your-auth'
|
|
196
|
-
|
|
197
|
-
export default function AuthBoundary({ children }) {
|
|
198
|
-
const { identify } = useAffiliateRef()
|
|
199
|
-
const user = useUser()
|
|
200
|
-
useEffect(() => {
|
|
201
|
-
if (user) identify(user.id)
|
|
202
|
-
}, [user])
|
|
203
|
-
return <>{children}</>
|
|
204
|
-
}
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
Idempotent. safe to fire on every page load while a user is signed in.
|
|
208
|
-
**user_id only.** The SDK does not accept, collect, or transmit email
|
|
209
|
-
or any other PII.
|
|
210
|
-
|
|
211
|
-
## API
|
|
212
|
-
|
|
213
|
-
### `<AffiliateoProvider
|
|
214
|
-
|
|
215
|
-
React provider. Wrap your app once. Reads `?ref=` from URL, persists to localStorage, sends pageview events, sets up data-attribute + scroll listeners.
|
|
216
|
-
|
|
217
|
-
Props:
|
|
218
|
-
- `
|
|
219
|
-
- `apiUrl` (optional): defaults to `https://affiliateo.com`
|
|
220
|
-
|
|
221
|
-
### `useAffiliateRef()`
|
|
222
|
-
|
|
223
|
-
React hook that returns `{ refCode, visitorId, track, identify }`.
|
|
224
|
-
|
|
225
|
-
- `track(eventName, metadata?)`. fire a custom goal
|
|
226
|
-
- `identify(userId)`. link this visitor to a merchant user_id (no PII)
|
|
227
|
-
|
|
228
|
-
### `getAttribution()`
|
|
229
|
-
|
|
230
|
-
Client-side function. Returns an object with `affiliateo_ref`, `affiliateo_visitor_id`, and `
|
|
231
|
-
|
|
232
|
-
## Framework support
|
|
233
|
-
|
|
234
|
-
Works with any JavaScript framework or vanilla JS:
|
|
235
|
-
- **Next.js** (App Router & Pages Router)
|
|
236
|
-
- **Remix** / **React Router**
|
|
237
|
-
- **Vite + React**
|
|
238
|
-
- **Vanilla JS/HTML** (use the `<script>` tag instead)
|
|
1
|
+
# @affiliateo/web
|
|
2
|
+
|
|
3
|
+
Affiliateo web SDK. track affiliate visits and pass attribution metadata to your Stripe checkout.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @affiliateo/web
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
1. Connect your Stripe account on the Affiliateo dashboard
|
|
14
|
+
2. Wrap your app in `<AffiliateoProvider>` with your app ID
|
|
15
|
+
3. At checkout time, call `getAttribution()` and pass the result to Stripe metadata
|
|
16
|
+
|
|
17
|
+
### Step 1. wrap your app
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
// app/layout.tsx (Next.js App Router) or _app.tsx (Pages Router)
|
|
21
|
+
import { AffiliateoProvider } from '@affiliateo/web';
|
|
22
|
+
|
|
23
|
+
export default function RootLayout({ children }) {
|
|
24
|
+
return (
|
|
25
|
+
<AffiliateoProvider appId="YOUR_APP_ID">
|
|
26
|
+
{children}
|
|
27
|
+
</AffiliateoProvider>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The provider:
|
|
33
|
+
- Reads `?ref=` from the URL when a visitor arrives
|
|
34
|
+
- Stores attribution in `localStorage` so it survives navigation
|
|
35
|
+
- Sends pageview events to Affiliateo for click tracking
|
|
36
|
+
|
|
37
|
+
### Step 2. in your checkout button (client-side)
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
'use client'
|
|
41
|
+
import { getAttribution } from '@affiliateo/web';
|
|
42
|
+
|
|
43
|
+
async function handleCheckout() {
|
|
44
|
+
const attribution = getAttribution();
|
|
45
|
+
|
|
46
|
+
const res = await fetch('/api/checkout', {
|
|
47
|
+
method: 'POST',
|
|
48
|
+
body: JSON.stringify({ attribution }),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const { url } = await res.json();
|
|
52
|
+
window.location.href = url;
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`getAttribution()` reads the stored attribution from `localStorage` and returns `affiliateo_ref`, `affiliateo_visitor_id`, and `affiliateo_app_id` as an object, ready to pass to Stripe.
|
|
57
|
+
|
|
58
|
+
### Step 3. in your checkout API route (server-side)
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
// app/api/checkout/route.ts
|
|
62
|
+
export async function POST(req) {
|
|
63
|
+
const { attribution } = await req.json();
|
|
64
|
+
|
|
65
|
+
const session = await stripe.checkout.sessions.create({
|
|
66
|
+
line_items: [{ price: 'price_xxx', quantity: 1 }],
|
|
67
|
+
mode: 'payment',
|
|
68
|
+
success_url: 'https://yoursite.com/success',
|
|
69
|
+
cancel_url: 'https://yoursite.com/cancel',
|
|
70
|
+
metadata: attribution,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return Response.json({ url: session.url });
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
That's it. Affiliateo's webhook will automatically read the metadata and credit the right affiliate when the payment succeeds.
|
|
78
|
+
|
|
79
|
+
## How it works
|
|
80
|
+
|
|
81
|
+
1. Visitor clicks affiliate link → `yoursite.com?ref=pinkproton`
|
|
82
|
+
2. `<AffiliateoProvider>` reads `?ref=pinkproton` from the URL and saves it in `localStorage`
|
|
83
|
+
3. As the visitor browses, attribution stays in their browser
|
|
84
|
+
4. At checkout, `getAttribution()` reads localStorage and returns the attribution object
|
|
85
|
+
5. Your server passes it as Stripe metadata when creating the checkout session
|
|
86
|
+
6. When Stripe fires the `checkout.session.completed` webhook, Affiliateo reads the metadata and credits the affiliate
|
|
87
|
+
|
|
88
|
+
## Why `localStorage` and not cookies?
|
|
89
|
+
|
|
90
|
+
- **Not blocked by Safari ITP or most privacy tools**
|
|
91
|
+
- **No CSRF concerns**
|
|
92
|
+
|
|
93
|
+
## GDPR / ePrivacy. merchant responsibility
|
|
94
|
+
|
|
95
|
+
The SDK writes persistent identifiers (`affiliateo_visitor_id`, `affiliateo_ref`, `affiliateo_campaign_id`) to the visitor's `localStorage` and sends pageview events that include the visitor's IP, user agent, and approximate geolocation to Affiliateo's servers. Under EU ePrivacy (Article 5(3)) and GDPR, persistent identifiers stored on a visitor's device require prior consent the same way cookies do.
|
|
96
|
+
|
|
97
|
+
If you serve visitors in the EU/EEA, UK, or any jurisdiction with equivalent rules, **you are responsible for obtaining consent before this script loads**, the same way you would for Google Analytics, Meta Pixel, or any other analytics tool. Typical approaches:
|
|
98
|
+
|
|
99
|
+
- Gate the `<AffiliateoProvider>` (or the `<script src="…/t.js">` tag) behind your cookie banner's "accept" callback so it only loads after consent.
|
|
100
|
+
- Document Affiliateo in your privacy policy as a processor (see our [Data Processing Agreement](https://affiliateo.com/dpa)) and list the categories of data collected.
|
|
101
|
+
|
|
102
|
+
Affiliateo acts as your data processor for visitor analytics. You are the controller and decide the lawful basis for processing your visitors' data.
|
|
103
|
+
|
|
104
|
+
## Alternative: vanilla HTML (no React)
|
|
105
|
+
|
|
106
|
+
If you're not using React, use the tracking script directly instead of the provider:
|
|
107
|
+
|
|
108
|
+
```html
|
|
109
|
+
<script defer
|
|
110
|
+
data-app="YOUR_APP_ID"
|
|
111
|
+
src="https://affiliateo.com/t.js"
|
|
112
|
+
></script>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Then in your checkout flow (vanilla JS):
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
const attribution = {
|
|
119
|
+
affiliateo_ref: localStorage.getItem('affiliateo_ref'),
|
|
120
|
+
affiliateo_visitor_id: localStorage.getItem('affiliateo_visitor_id'),
|
|
121
|
+
affiliateo_app_id: localStorage.getItem('affiliateo_campaign_id'), // stored under its legacy key
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Send to your server with the rest of your checkout payload
|
|
125
|
+
fetch('/api/checkout', {
|
|
126
|
+
method: 'POST',
|
|
127
|
+
body: JSON.stringify({ attribution }),
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Track conversion goals
|
|
132
|
+
|
|
133
|
+
Pageviews auto-track. For specific moments that matter to your funnel
|
|
134
|
+
(`signup_completed`, `trial_started`, `initiate_checkout`, etc.), use
|
|
135
|
+
`track()` or HTML data attributes.
|
|
136
|
+
|
|
137
|
+
### From React
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
'use client'
|
|
141
|
+
import { useEffect } from 'react'
|
|
142
|
+
import { useAffiliateRef } from '@affiliateo/web'
|
|
143
|
+
|
|
144
|
+
export default function SignupSuccess() {
|
|
145
|
+
const { track } = useAffiliateRef()
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
track('signup_completed', { plan: 'free' })
|
|
148
|
+
}, [])
|
|
149
|
+
return <div>Welcome!</div>
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### HTML data attributes (zero JS, works everywhere)
|
|
154
|
+
|
|
155
|
+
```html
|
|
156
|
+
<button data-affiliateo-goal="initiate_checkout"
|
|
157
|
+
data-affiliateo-goal-price="49"
|
|
158
|
+
data-affiliateo-goal-plan="pro">
|
|
159
|
+
Subscribe
|
|
160
|
+
</button>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The SDK auto-fires the goal on click. Optional `data-affiliateo-goal-*`
|
|
164
|
+
attributes become metadata (kebab-case → snake_case, up to 10 params).
|
|
165
|
+
|
|
166
|
+
### Scroll-depth tracking
|
|
167
|
+
|
|
168
|
+
```html
|
|
169
|
+
<section data-affiliateo-scroll="pricing_viewed">
|
|
170
|
+
...your pricing content...
|
|
171
|
+
</section>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Fires the first time any pixel of the section overlaps the viewport.
|
|
175
|
+
Maximum permissiveness — if the user scrolled enough to see even a sliver,
|
|
176
|
+
it counts. Earlier versions required 50% visibility, which was unreachable
|
|
177
|
+
for tall sections (a pricing page with comparison tables silently dropped
|
|
178
|
+
out of every funnel). Great for landing pages where you want to know
|
|
179
|
+
which sections convert.
|
|
180
|
+
|
|
181
|
+
### Goal naming rules
|
|
182
|
+
|
|
183
|
+
Lowercase letters, digits, underscores, hyphens, colons. Max 64 chars.
|
|
184
|
+
Metadata bounded to 4KB JSON.
|
|
185
|
+
|
|
186
|
+
## Identify signed-in users (cross-device stitching)
|
|
187
|
+
|
|
188
|
+
Without `identify()`, the same person browsing on phone and buying on
|
|
189
|
+
laptop counts as two visitors. Call it once after sign-in:
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
'use client'
|
|
193
|
+
import { useEffect } from 'react'
|
|
194
|
+
import { useAffiliateRef } from '@affiliateo/web'
|
|
195
|
+
import { useUser } from './your-auth'
|
|
196
|
+
|
|
197
|
+
export default function AuthBoundary({ children }) {
|
|
198
|
+
const { identify } = useAffiliateRef()
|
|
199
|
+
const user = useUser()
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
if (user) identify(user.id)
|
|
202
|
+
}, [user])
|
|
203
|
+
return <>{children}</>
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Idempotent. safe to fire on every page load while a user is signed in.
|
|
208
|
+
**user_id only.** The SDK does not accept, collect, or transmit email
|
|
209
|
+
or any other PII.
|
|
210
|
+
|
|
211
|
+
## API
|
|
212
|
+
|
|
213
|
+
### `<AffiliateoProvider appId="..." apiUrl?="...">`
|
|
214
|
+
|
|
215
|
+
React provider. Wrap your app once. Reads `?ref=` from URL, persists to localStorage, sends pageview events, sets up data-attribute + scroll listeners.
|
|
216
|
+
|
|
217
|
+
Props:
|
|
218
|
+
- `appId` (required): your Affiliateo app ID (`campaignId` still works as a deprecated alias)
|
|
219
|
+
- `apiUrl` (optional): defaults to `https://affiliateo.com`
|
|
220
|
+
|
|
221
|
+
### `useAffiliateRef()`
|
|
222
|
+
|
|
223
|
+
React hook that returns `{ refCode, visitorId, track, identify }`.
|
|
224
|
+
|
|
225
|
+
- `track(eventName, metadata?)`. fire a custom goal
|
|
226
|
+
- `identify(userId)`. link this visitor to a merchant user_id (no PII)
|
|
227
|
+
|
|
228
|
+
### `getAttribution()`
|
|
229
|
+
|
|
230
|
+
Client-side function. Returns an object with `affiliateo_ref`, `affiliateo_visitor_id`, and `affiliateo_app_id` from localStorage. Pass directly as Stripe `metadata`.
|
|
231
|
+
|
|
232
|
+
## Framework support
|
|
233
|
+
|
|
234
|
+
Works with any JavaScript framework or vanilla JS:
|
|
235
|
+
- **Next.js** (App Router & Pages Router)
|
|
236
|
+
- **Remix** / **React Router**
|
|
237
|
+
- **Vite + React**
|
|
238
|
+
- **Vanilla JS/HTML** (use the `<script>` tag instead)
|
package/dist/provider.d.ts
CHANGED
|
@@ -18,6 +18,6 @@ export declare function useAffiliateRef(): AffiliateoContextValue;
|
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
20
|
export declare function getAttribution(): Record<string, string>;
|
|
21
|
-
export declare function AffiliateoProvider({ campaignId, apiUrl, allowedHostnames, allowLocalhost, allowIframe, allowFileProtocol, debug, flushInterval, maxQueueSize, autoCapturePageviews, children, }: AffiliateoConfig & {
|
|
21
|
+
export declare function AffiliateoProvider({ appId, campaignId, apiUrl, allowedHostnames, allowLocalhost, allowIframe, allowFileProtocol, debug, flushInterval, maxQueueSize, autoCapturePageviews, children, }: AffiliateoConfig & {
|
|
22
22
|
children: React.ReactNode;
|
|
23
23
|
}): import("react/jsx-runtime").JSX.Element;
|
package/dist/provider.js
CHANGED
|
@@ -7,7 +7,10 @@ import { isDevHost } from './dev-host';
|
|
|
7
7
|
const DEFAULT_API_URL = 'https://affiliateo.com';
|
|
8
8
|
const LS_VISITOR_ID = 'affiliateo_visitor_id';
|
|
9
9
|
const LS_REF = 'affiliateo_ref';
|
|
10
|
-
|
|
10
|
+
// Storage key keeps its historical name: t.js and the WooCommerce tracker
|
|
11
|
+
// write the same key, and renaming it would orphan existing visitors'
|
|
12
|
+
// attribution mid-session.
|
|
13
|
+
const LS_APP_ID = 'affiliateo_campaign_id';
|
|
11
14
|
// Persistent opt-out flag (affiliateo_opt_out) —
|
|
12
15
|
// when set to "true" the SDK silently noops every outbound call. Survives
|
|
13
16
|
// page reloads. removed by optIn(). Checked at init time so a previously
|
|
@@ -149,13 +152,15 @@ export function getAttribution() {
|
|
|
149
152
|
const data = {};
|
|
150
153
|
const vid = lsGet(LS_VISITOR_ID);
|
|
151
154
|
const ref = lsGet(LS_REF);
|
|
152
|
-
const cid = lsGet(
|
|
155
|
+
const cid = lsGet(LS_APP_ID);
|
|
153
156
|
if (vid)
|
|
154
157
|
data.affiliateo_visitor_id = vid;
|
|
155
158
|
if (ref)
|
|
156
159
|
data.affiliateo_ref = ref;
|
|
160
|
+
// Key renamed from affiliateo_campaign_id in 4.5.0 — the Affiliateo
|
|
161
|
+
// webhook accepts both, so checkouts created by older SDKs keep working.
|
|
157
162
|
if (cid)
|
|
158
|
-
data.
|
|
163
|
+
data.affiliateo_app_id = cid;
|
|
159
164
|
return data;
|
|
160
165
|
}
|
|
161
166
|
// Double-mount detection. The host app should mount AffiliateoProvider
|
|
@@ -165,7 +170,7 @@ export function getAttribution() {
|
|
|
165
170
|
// mount (host has the call sites) but we can shout in dev so the
|
|
166
171
|
// mistake is visible.
|
|
167
172
|
let providerMountCount = 0;
|
|
168
|
-
export function AffiliateoProvider({ campaignId, apiUrl, allowedHostnames, allowLocalhost, allowIframe, allowFileProtocol, debug, flushInterval, maxQueueSize, autoCapturePageviews, children, }) {
|
|
173
|
+
export function AffiliateoProvider({ appId, campaignId, apiUrl, allowedHostnames, allowLocalhost, allowIframe, allowFileProtocol, debug, flushInterval, maxQueueSize, autoCapturePageviews, children, }) {
|
|
169
174
|
// Normalize autoCapturePageviews. Three shapes:
|
|
170
175
|
// - undefined / true / object → auto-capture ON (with object overrides)
|
|
171
176
|
// - false → auto-capture OFF entirely (host must call trackPageview)
|
|
@@ -211,12 +216,15 @@ export function AffiliateoProvider({ campaignId, apiUrl, allowedHostnames, allow
|
|
|
211
216
|
console.log('[Affiliateo] ' + msg);
|
|
212
217
|
}, [debug]);
|
|
213
218
|
const apiBase = (apiUrl || DEFAULT_API_URL).replace(/\/$/, '');
|
|
214
|
-
//
|
|
219
|
+
// appId is the documented prop; campaignId is the pre-4.5.0 name kept as
|
|
220
|
+
// a working alias so upgrading the package never breaks an integration.
|
|
221
|
+
const resolvedAppId = appId ?? campaignId ?? '';
|
|
222
|
+
// appIdRef stays current so callbacks defined inside useEffect
|
|
215
223
|
// always read the latest value, even after a prop change. Closures
|
|
216
224
|
// captured by host-side `useRef` no longer drift to a stale id.
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
// Mint visitorId once and put it in a ref so it survives
|
|
225
|
+
const appIdRef = useRef(resolvedAppId);
|
|
226
|
+
appIdRef.current = resolvedAppId;
|
|
227
|
+
// Mint visitorId once and put it in a ref so it survives appId
|
|
220
228
|
// prop changes without forcing a re-fetch / re-mint cycle. Same UUID
|
|
221
229
|
// for the lifetime of the localStorage entry across the whole tab.
|
|
222
230
|
const visitorIdRef = useRef(null);
|
|
@@ -230,7 +238,7 @@ export function AffiliateoProvider({ campaignId, apiUrl, allowedHostnames, allow
|
|
|
230
238
|
// handlers, which fire after mount.
|
|
231
239
|
const queueRef = useRef(null);
|
|
232
240
|
// track + identify are defined OUTSIDE the route-effect so they
|
|
233
|
-
// close over refs (
|
|
241
|
+
// close over refs (appIdRef, visitorIdRef) rather than the
|
|
234
242
|
// captured-once values from inside the effect. A consumer caching
|
|
235
243
|
// `track` keeps firing under the LATEST (campaignId, visitorId).
|
|
236
244
|
const track = useCallback(async (eventName, metadata) => {
|
|
@@ -246,7 +254,7 @@ export function AffiliateoProvider({ campaignId, apiUrl, allowedHostnames, allow
|
|
|
246
254
|
const name = eventName.trim().toLowerCase();
|
|
247
255
|
if (!name || name.length > 64 || !/^[a-z0-9_:-]+$/.test(name))
|
|
248
256
|
return;
|
|
249
|
-
const cid =
|
|
257
|
+
const cid = appIdRef.current;
|
|
250
258
|
const vid = visitorIdRef.current;
|
|
251
259
|
if (!cid || !vid)
|
|
252
260
|
return;
|
|
@@ -285,7 +293,7 @@ export function AffiliateoProvider({ campaignId, apiUrl, allowedHostnames, allow
|
|
|
285
293
|
// firebase uid, supabase uuid.
|
|
286
294
|
if (!uid || uid.length > 128)
|
|
287
295
|
return;
|
|
288
|
-
const cid =
|
|
296
|
+
const cid = appIdRef.current;
|
|
289
297
|
const vid = visitorIdRef.current;
|
|
290
298
|
if (!cid || !vid)
|
|
291
299
|
return;
|
|
@@ -315,7 +323,7 @@ export function AffiliateoProvider({ campaignId, apiUrl, allowedHostnames, allow
|
|
|
315
323
|
return;
|
|
316
324
|
if (isOptedOut())
|
|
317
325
|
return;
|
|
318
|
-
const cid =
|
|
326
|
+
const cid = appIdRef.current;
|
|
319
327
|
const vid = visitorIdRef.current;
|
|
320
328
|
if (!cid || !vid)
|
|
321
329
|
return;
|
|
@@ -425,6 +433,13 @@ export function AffiliateoProvider({ campaignId, apiUrl, allowedHostnames, allow
|
|
|
425
433
|
useEffect(() => {
|
|
426
434
|
if (typeof window === 'undefined')
|
|
427
435
|
return;
|
|
436
|
+
// Neither appId nor the legacy campaignId alias was passed — nothing to
|
|
437
|
+
// attribute events to, so stay dormant instead of firing junk.
|
|
438
|
+
if (!appIdRef.current) {
|
|
439
|
+
// eslint-disable-next-line no-console
|
|
440
|
+
console.error('[Affiliateo] Missing appId — pass your app ID to <AffiliateoProvider appId="...">.');
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
428
443
|
// Mount-count warning. Fires only when more than one Provider has
|
|
429
444
|
// ever mounted in the page lifetime. Dev-only; production swallows
|
|
430
445
|
// the warning to avoid noise in real merchant consoles.
|
|
@@ -613,7 +628,7 @@ export function AffiliateoProvider({ campaignId, apiUrl, allowedHostnames, allow
|
|
|
613
628
|
vid = uuid();
|
|
614
629
|
}
|
|
615
630
|
lsSet(LS_VISITOR_ID, vid);
|
|
616
|
-
lsSet(
|
|
631
|
+
lsSet(LS_APP_ID, appIdRef.current);
|
|
617
632
|
visitorIdRef.current = vid;
|
|
618
633
|
// Read ref. Cross-domain inbound wins, then fresh affiliate link
|
|
619
634
|
// params (?ref / ?via / ?source), then stored value.
|
|
@@ -628,7 +643,7 @@ export function AffiliateoProvider({ campaignId, apiUrl, allowedHostnames, allow
|
|
|
628
643
|
visitorId: vid,
|
|
629
644
|
track, identify, trackPageview, reset, optOut, optIn, flush,
|
|
630
645
|
});
|
|
631
|
-
log('init', {
|
|
646
|
+
log('init', { app: appIdRef.current, visitor: vid, ref: ref || null });
|
|
632
647
|
// lastSendTime drives the visibility-refire heuristic below (refire a
|
|
633
648
|
// pageview when a tab returns after the session window). Duplicate
|
|
634
649
|
// suppression itself is the persisted same-URL throttle inside
|
|
@@ -661,7 +676,7 @@ export function AffiliateoProvider({ campaignId, apiUrl, allowedHostnames, allow
|
|
|
661
676
|
ended = false;
|
|
662
677
|
queue.enqueue(endpoint, {
|
|
663
678
|
type: 'pageview',
|
|
664
|
-
campaign_id:
|
|
679
|
+
campaign_id: appIdRef.current,
|
|
665
680
|
visitor_id: vid,
|
|
666
681
|
session_id: getSessionId(),
|
|
667
682
|
url: href,
|
|
@@ -702,7 +717,7 @@ export function AffiliateoProvider({ campaignId, apiUrl, allowedHostnames, allow
|
|
|
702
717
|
queue.flushOnUnload();
|
|
703
718
|
const data = JSON.stringify({
|
|
704
719
|
type: 'session_end',
|
|
705
|
-
campaign_id:
|
|
720
|
+
campaign_id: appIdRef.current,
|
|
706
721
|
visitor_id: vid,
|
|
707
722
|
ref: ref || lsGet(LS_REF) || '',
|
|
708
723
|
});
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
export interface AffiliateoConfig {
|
|
2
|
-
|
|
2
|
+
/** Your Affiliateo app ID — shown in the dashboard when you create a
|
|
3
|
+
* Web App Affiliate app. One of `appId` / `campaignId` is required;
|
|
4
|
+
* `appId` wins when both are set. */
|
|
5
|
+
appId?: string;
|
|
6
|
+
/** @deprecated Affiliateo campaigns are now called apps — use `appId`.
|
|
7
|
+
* Kept working so existing integrations don't break. */
|
|
8
|
+
campaignId?: string;
|
|
3
9
|
/** Base URL for the API. Defaults to https://affiliateo.com */
|
|
4
10
|
apiUrl?: string;
|
|
5
11
|
/** Other domains that are part of the same merchant property and should
|
|
@@ -22,7 +28,7 @@ export interface AffiliateoConfig {
|
|
|
22
28
|
*
|
|
23
29
|
* Example:
|
|
24
30
|
* <AffiliateoProvider
|
|
25
|
-
*
|
|
31
|
+
* appId="..."
|
|
26
32
|
* allowedHostnames={['app.merchant.com', 'docs.merchant.com']}
|
|
27
33
|
* > */
|
|
28
34
|
allowedHostnames?: string[];
|
|
@@ -36,7 +42,7 @@ export interface AffiliateoConfig {
|
|
|
36
42
|
* land server-side).
|
|
37
43
|
*
|
|
38
44
|
* Example:
|
|
39
|
-
* <AffiliateoProvider
|
|
45
|
+
* <AffiliateoProvider appId="..." allowLocalhost> */
|
|
40
46
|
allowLocalhost?: boolean;
|
|
41
47
|
/** Allow tracking when the page is rendered inside an iframe. Defaults
|
|
42
48
|
* to false so scrapers, preview tools, and other sites embedding yours
|
|
@@ -55,7 +61,7 @@ export interface AffiliateoConfig {
|
|
|
55
61
|
* showing up?". Leave off in production.
|
|
56
62
|
*
|
|
57
63
|
* Example:
|
|
58
|
-
* <AffiliateoProvider
|
|
64
|
+
* <AffiliateoProvider appId="..." debug> */
|
|
59
65
|
debug?: boolean;
|
|
60
66
|
/** Allow tracking from pages served via the `file://` protocol (someone
|
|
61
67
|
* double-clicking a built HTML file instead of running a server).
|