@affiliateo/web 4.7.0 → 4.8.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 +274 -238
- package/dist/bot-detect.d.ts +4 -4
- package/dist/bot-detect.js +6 -6
- package/dist/provider.js +122 -80
- package/dist/queue.d.ts +7 -3
- package/dist/queue.js +32 -15
- package/dist/turnstile.d.ts +3 -3
- package/dist/turnstile.js +6 -6
- package/dist/types.d.ts +24 -15
- package/dist/user-id.d.ts +18 -0
- package/dist/user-id.js +40 -0
- package/package.json +46 -44
package/dist/turnstile.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* Cloudflare Turnstile token minting. Mirrors the hosted t.js tracker:
|
|
3
3
|
* mint ONE invisible token at SDK init and attach it to the first pageview.
|
|
4
4
|
* The server verifies the token before creating the visitor row (and before
|
|
5
|
-
* counting a pay-per-click web_traffic click
|
|
5
|
+
* counting a pay-per-click web_traffic click - the billable path REQUIRES a
|
|
6
6
|
* genuine pass when TURNSTILE_SECRET is configured). Subsequent events from
|
|
7
7
|
* the same visitor_id are trusted because the row was created behind a
|
|
8
8
|
* verified token, so the cost is one mint + one verify per session.
|
|
9
9
|
*
|
|
10
10
|
* Tokens are single-use at Cloudflare's siteverify, so consumeToken()
|
|
11
|
-
* hands the token out exactly once
|
|
11
|
+
* hands the token out exactly once - re-sending a used token would get the
|
|
12
12
|
* event rejected server-side, not warn-allowed.
|
|
13
13
|
*
|
|
14
14
|
* Failure modes: if the Turnstile script is blocked (ad blockers, strict
|
|
@@ -17,20 +17,20 @@
|
|
|
17
17
|
* those through; commission-on-sale (web_affiliate) tracking never depends
|
|
18
18
|
* on the token at all.
|
|
19
19
|
*/
|
|
20
|
-
// Public sitekey (safe to expose)
|
|
20
|
+
// Public sitekey (safe to expose) - the same one public/t.js ships. The
|
|
21
21
|
// matching secret lives in TURNSTILE_SECRET on the server.
|
|
22
22
|
const TURNSTILE_SITEKEY = '0x4AAAAAADR5UW2x6axFd13p';
|
|
23
23
|
const TURNSTILE_SCRIPT_SRC = 'https://challenges.cloudflare.com/turnstile/v0/api.js?onload=__affiliateoTSLoaded&render=explicit';
|
|
24
24
|
// Hard deadline for the whole mint (script load + challenge). t.js relies on
|
|
25
25
|
// Turnstile's own timeout-callback, but a script tag that never loads never
|
|
26
|
-
// fires ANY callback
|
|
26
|
+
// fires ANY callback - this timer makes sure the first pageview can't be
|
|
27
27
|
// held hostage by a blocked challenges.cloudflare.com.
|
|
28
28
|
const MINT_TIMEOUT_MS = 3000;
|
|
29
29
|
let token = null;
|
|
30
30
|
let minting = null;
|
|
31
31
|
/**
|
|
32
32
|
* Load the Turnstile script (once) and run the invisible challenge. Resolves
|
|
33
|
-
* when a token is available OR the mint failed/timed out
|
|
33
|
+
* when a token is available OR the mint failed/timed out - never rejects, so
|
|
34
34
|
* callers can unconditionally `.then(sendPageview)`.
|
|
35
35
|
*/
|
|
36
36
|
export function mintTurnstileToken() {
|
|
@@ -60,7 +60,7 @@ export function mintTurnstileToken() {
|
|
|
60
60
|
document.body.appendChild(holder);
|
|
61
61
|
window.turnstile.render(holder, {
|
|
62
62
|
sitekey: TURNSTILE_SITEKEY,
|
|
63
|
-
// Invisible widget
|
|
63
|
+
// Invisible widget - never shows UI. Real browsers pass the
|
|
64
64
|
// challenge in 50-200ms with zero user interaction.
|
|
65
65
|
size: 'invisible',
|
|
66
66
|
callback: (tok) => {
|
package/dist/types.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export interface AffiliateoConfig {
|
|
2
|
-
/** Your Affiliateo app ID
|
|
2
|
+
/** Your Affiliateo app ID - shown in the dashboard when you create a
|
|
3
3
|
* Web App Affiliate app. One of `appId` / `campaignId` is required;
|
|
4
4
|
* `appId` wins when both are set. */
|
|
5
5
|
appId?: string;
|
|
6
|
-
/** @deprecated Affiliateo campaigns are now called apps
|
|
6
|
+
/** @deprecated Affiliateo campaigns are now called apps - use `appId`.
|
|
7
7
|
* Kept working so existing integrations don't break. */
|
|
8
8
|
campaignId?: string;
|
|
9
9
|
/** Base URL for the API. Defaults to https://affiliateo.com */
|
|
@@ -55,7 +55,7 @@ export interface AffiliateoConfig {
|
|
|
55
55
|
* true, the SDK logs every action it takes (init, pageview fired,
|
|
56
56
|
* goal tracked, blocked by bot detection, blocked by localhost, queue
|
|
57
57
|
* flush attempted, etc.). Only the merchant or anyone manually opening
|
|
58
|
-
* DevTools sees these
|
|
58
|
+
* DevTools sees these - normal visitors never look at the console.
|
|
59
59
|
*
|
|
60
60
|
* Turn on temporarily when investigating "why aren't my events
|
|
61
61
|
* showing up?". Leave off in production.
|
|
@@ -121,7 +121,14 @@ export interface AffiliateoContextValue {
|
|
|
121
121
|
* both events resolve to the same user_id and the funnel reports a
|
|
122
122
|
* single converted person.
|
|
123
123
|
*
|
|
124
|
-
*
|
|
124
|
+
* Since 4.8.0 a different user signing in on this browser switches it
|
|
125
|
+
* to them (it used to be refused). The SDK stores the id and re-sends it
|
|
126
|
+
* once per visit; calling it again in the same visit sends nothing. Does
|
|
127
|
+
* nothing while opted out, and skips ids that mean "nobody" ("null",
|
|
128
|
+
* "undefined", "guest", "0", RevenueCat's $RCAnonymousID:...) that the
|
|
129
|
+
* server would refuse.
|
|
130
|
+
*
|
|
131
|
+
* Idempotent: safe to call on every page load. user_id only -
|
|
125
132
|
* the SDK does not accept or transmit email or any other PII.
|
|
126
133
|
*
|
|
127
134
|
* Example:
|
|
@@ -142,22 +149,24 @@ export interface AffiliateoContextValue {
|
|
|
142
149
|
* useEffect(() => { if (open) trackPageview('/pricing-modal') }, [open])
|
|
143
150
|
* } */
|
|
144
151
|
trackPageview: (path?: string) => Promise<void>;
|
|
145
|
-
/**
|
|
146
|
-
*
|
|
147
|
-
* is
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* the same browser doesn't get their actions merged into the previous
|
|
151
|
-
* user's funnel. Without this, shared family computers / kiosks /
|
|
152
|
-
* hot-desks silently pollute attribution.
|
|
152
|
+
/** The user signed out (4.8.0). Call it on sign-out: this browser starts
|
|
153
|
+
* over as a new anonymous customer of the app, so whoever signs in next
|
|
154
|
+
* (or nobody) is not counted as the person who just left, and everything
|
|
155
|
+
* already recorded stays with them. Forgets the stored user id. The
|
|
156
|
+
* visitor id, the affiliate ref and tracking carry on unchanged.
|
|
153
157
|
*
|
|
154
158
|
* Example:
|
|
155
|
-
* const {
|
|
159
|
+
* const { logout } = useAffiliateRef()
|
|
156
160
|
* async function handleLogout() {
|
|
157
161
|
* await supabase.auth.signOut()
|
|
158
|
-
* await
|
|
162
|
+
* await logout()
|
|
159
163
|
* router.push('/')
|
|
160
164
|
* } */
|
|
165
|
+
logout: () => Promise<void>;
|
|
166
|
+
/** The same as logout() since 4.8.0. It used to mint a fresh visitor id
|
|
167
|
+
* and clear the affiliate ref, which split one person into two visitors
|
|
168
|
+
* and lost their affiliate. Kept as an alias so existing integrations
|
|
169
|
+
* get the sign-out they meant. */
|
|
161
170
|
reset: () => Promise<void>;
|
|
162
171
|
/** Stop tracking this visitor. Sets a persistent opt-out flag in
|
|
163
172
|
* localStorage and silences all subsequent track / identify / pageview
|
|
@@ -196,7 +205,7 @@ export interface AffiliateoContextValue {
|
|
|
196
205
|
* }
|
|
197
206
|
*
|
|
198
207
|
* Returns a Promise that resolves when the flush attempt completes
|
|
199
|
-
* (success OR failure
|
|
208
|
+
* (success OR failure - failed events stay in the queue for the next
|
|
200
209
|
* flush). Safe to await without timeout concerns. */
|
|
201
210
|
flush: () => Promise<void>;
|
|
202
211
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which user ids may name a signed-in person (4.8.0).
|
|
3
|
+
*
|
|
4
|
+
* Every device that signs in with the same user id becomes ONE customer of
|
|
5
|
+
* the app on Affiliateo's side, so an id that really means "nobody" ("null",
|
|
6
|
+
* "undefined", "guest", "0") would merge every stranger whose site sent it
|
|
7
|
+
* into one person. The server refuses these with a 400; this is the same
|
|
8
|
+
* list, checked here so a wrong call is skipped (and says so with
|
|
9
|
+
* debug={true}) instead of costing a request.
|
|
10
|
+
*
|
|
11
|
+
* KEEP IN SYNC with src/lib/customers/user-id.ts in the Affiliateo web repo,
|
|
12
|
+
* and with public/t.js, which the vanilla snippet loads.
|
|
13
|
+
*/
|
|
14
|
+
export declare const MAX_USER_ID_LENGTH = 128;
|
|
15
|
+
/** The trimmed id when it can name a person, otherwise null. A number (a
|
|
16
|
+
* database row id passed straight through) is read as its digits, the way
|
|
17
|
+
* the server reads it. */
|
|
18
|
+
export declare function usableUserId(value: unknown): string | null;
|
package/dist/user-id.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which user ids may name a signed-in person (4.8.0).
|
|
3
|
+
*
|
|
4
|
+
* Every device that signs in with the same user id becomes ONE customer of
|
|
5
|
+
* the app on Affiliateo's side, so an id that really means "nobody" ("null",
|
|
6
|
+
* "undefined", "guest", "0") would merge every stranger whose site sent it
|
|
7
|
+
* into one person. The server refuses these with a 400; this is the same
|
|
8
|
+
* list, checked here so a wrong call is skipped (and says so with
|
|
9
|
+
* debug={true}) instead of costing a request.
|
|
10
|
+
*
|
|
11
|
+
* KEEP IN SYNC with src/lib/customers/user-id.ts in the Affiliateo web repo,
|
|
12
|
+
* and with public/t.js, which the vanilla snippet loads.
|
|
13
|
+
*/
|
|
14
|
+
export const MAX_USER_ID_LENGTH = 128;
|
|
15
|
+
const NOT_A_USER = new Set([
|
|
16
|
+
'null', '(null)', '<null>', 'nil', 'none', 'no', 'undefined', 'unidentified',
|
|
17
|
+
'unknown', 'anonymous', 'anon', 'guest', 'nan', 'false', 'true',
|
|
18
|
+
'0', '-1', '[]', '{}', '[object object]',
|
|
19
|
+
]);
|
|
20
|
+
/** The trimmed id when it can name a person, otherwise null. A number (a
|
|
21
|
+
* database row id passed straight through) is read as its digits, the way
|
|
22
|
+
* the server reads it. */
|
|
23
|
+
export function usableUserId(value) {
|
|
24
|
+
let raw;
|
|
25
|
+
if (typeof value === 'string')
|
|
26
|
+
raw = value;
|
|
27
|
+
else if (typeof value === 'number' && Number.isFinite(value))
|
|
28
|
+
raw = String(value);
|
|
29
|
+
else
|
|
30
|
+
return null;
|
|
31
|
+
const id = raw.trim();
|
|
32
|
+
if (id.length < 1 || id.length > MAX_USER_ID_LENGTH)
|
|
33
|
+
return null;
|
|
34
|
+
if (NOT_A_USER.has(id.toLowerCase()))
|
|
35
|
+
return null;
|
|
36
|
+
// RevenueCat's own anonymous id names an install, not a user.
|
|
37
|
+
if (id.startsWith('$RCAnonymousID:'))
|
|
38
|
+
return null;
|
|
39
|
+
return id;
|
|
40
|
+
}
|
package/package.json
CHANGED
|
@@ -1,44 +1,46 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@affiliateo/web",
|
|
3
|
-
"version": "4.
|
|
4
|
-
"description": "Affiliateo web SDK
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"module": "dist/index.js",
|
|
7
|
-
"types": "dist/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"types": "./dist/index.d.ts",
|
|
11
|
-
"import": "./dist/index.js",
|
|
12
|
-
"require": "./dist/index.js"
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
"scripts": {
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@affiliateo/web",
|
|
3
|
+
"version": "4.8.0",
|
|
4
|
+
"description": "Affiliateo web SDK: affiliate attribution and tracking for web apps",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"prebuild": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"prepack": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"react": ">=18.0.0"
|
|
23
|
+
},
|
|
24
|
+
"peerDependenciesMeta": {
|
|
25
|
+
"react": {
|
|
26
|
+
"optional": true
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/react": "^19.0.0",
|
|
31
|
+
"typescript": "^5.0.0"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"affiliateo",
|
|
35
|
+
"affiliate",
|
|
36
|
+
"attribution",
|
|
37
|
+
"stripe",
|
|
38
|
+
"web",
|
|
39
|
+
"session",
|
|
40
|
+
"tracking"
|
|
41
|
+
],
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"files": [
|
|
44
|
+
"dist"
|
|
45
|
+
]
|
|
46
|
+
}
|