@affiliateo/web 1.0.0 → 1.1.5

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/package.json CHANGED
@@ -1,14 +1,22 @@
1
- {
2
- "name": "@affiliateo/web",
3
- "version": "1.0.0",
4
- "description": "Affiliateo web SDK — pass affiliate attribution metadata to your Stripe checkout",
5
- "main": "src/index.ts",
6
- "types": "src/index.ts",
7
- "scripts": {
8
- "build": "tsc",
9
- "typecheck": "tsc --noEmit"
10
- },
11
- "keywords": ["affiliateo", "affiliate", "attribution", "stripe", "web"],
12
- "license": "MIT",
13
- "files": ["src"]
14
- }
1
+ {
2
+ "name": "@affiliateo/web",
3
+ "version": "1.1.5",
4
+ "description": "Affiliateo web SDK — affiliate attribution and session tracking for web apps",
5
+ "main": "src/index.ts",
6
+ "types": "src/index.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "typecheck": "tsc --noEmit"
10
+ },
11
+ "peerDependencies": {
12
+ "react": ">=18.0.0"
13
+ },
14
+ "peerDependenciesMeta": {
15
+ "react": {
16
+ "optional": true
17
+ }
18
+ },
19
+ "keywords": ["affiliateo", "affiliate", "attribution", "stripe", "web", "session", "tracking"],
20
+ "license": "MIT",
21
+ "files": ["src"]
22
+ }
package/src/index.ts CHANGED
@@ -1,72 +1,29 @@
1
- /**
2
- * @affiliateo/web — Pass affiliate attribution metadata to your Stripe checkout.
3
- *
4
- * Usage:
5
- * ```ts
6
- * import { getMetadata } from '@affiliateo/web';
7
- *
8
- * const session = await stripe.checkout.sessions.create({
9
- * line_items: [...],
10
- * mode: 'payment',
11
- * metadata: getMetadata(req),
12
- * });
13
- * ```
14
- */
15
-
16
- /**
17
- * Extract affiliateo cookies from an incoming request.
18
- * Works with Next.js (App Router & Pages Router), Express, and any framework
19
- * that exposes cookies on the request object.
20
- *
21
- * Returns metadata object to pass to Stripe checkout session.
22
- */
23
- export function getMetadata(
24
- req: {
25
- cookies?: Record<string, string> | { get?: (name: string) => { value: string } | undefined }
26
- headers?: { cookie?: string } | Headers
27
- }
28
- ): { affiliateo_visitor_id?: string; affiliateo_ref?: string } {
29
- const metadata: { affiliateo_visitor_id?: string; affiliateo_ref?: string } = {}
30
-
31
- // Try Next.js App Router cookies (ReadonlyRequestCookies with .get())
32
- if (req.cookies && typeof req.cookies === 'object' && 'get' in req.cookies && typeof req.cookies.get === 'function') {
33
- const visitorId = req.cookies.get('affiliateo_visitor_id')
34
- const ref = req.cookies.get('affiliateo_ref')
35
- if (visitorId?.value) metadata.affiliateo_visitor_id = visitorId.value
36
- if (ref?.value) metadata.affiliateo_ref = ref.value
37
- return metadata
38
- }
39
-
40
- // Try plain object cookies (Express, Pages Router, etc.)
41
- if (req.cookies && typeof req.cookies === 'object') {
42
- const cookies = req.cookies as Record<string, string>
43
- if (cookies.affiliateo_visitor_id) metadata.affiliateo_visitor_id = cookies.affiliateo_visitor_id
44
- if (cookies.affiliateo_ref) metadata.affiliateo_ref = cookies.affiliateo_ref
45
- return metadata
46
- }
47
-
48
- // Fallback: parse cookie header manually
49
- const cookieHeader = req.headers instanceof Headers
50
- ? req.headers.get('cookie')
51
- : (req.headers as { cookie?: string })?.cookie
52
-
53
- if (cookieHeader) {
54
- const cookies = parseCookies(cookieHeader)
55
- if (cookies.affiliateo_visitor_id) metadata.affiliateo_visitor_id = cookies.affiliateo_visitor_id
56
- if (cookies.affiliateo_ref) metadata.affiliateo_ref = cookies.affiliateo_ref
57
- }
58
-
59
- return metadata
60
- }
61
-
62
- /**
63
- * Parse a cookie header string into key-value pairs.
64
- */
65
- function parseCookies(cookieHeader: string): Record<string, string> {
66
- const cookies: Record<string, string> = {}
67
- for (const pair of cookieHeader.split(';')) {
68
- const [key, ...rest] = pair.trim().split('=')
69
- if (key) cookies[key.trim()] = rest.join('=').trim()
70
- }
71
- return cookies
72
- }
1
+ /**
2
+ * @affiliateo/web — Affiliate attribution and session tracking for web apps.
3
+ *
4
+ * React usage:
5
+ * ```tsx
6
+ * import { AffiliateoProvider } from '@affiliateo/web';
7
+ *
8
+ * export default function App() {
9
+ * return (
10
+ * <AffiliateoProvider siteId="YOUR_SITE_ID">
11
+ * <YourApp />
12
+ * </AffiliateoProvider>
13
+ * );
14
+ * }
15
+ * ```
16
+ *
17
+ * Server-side (pass ref to Stripe checkout):
18
+ * ```ts
19
+ * import { getMetadata } from '@affiliateo/web';
20
+ *
21
+ * const session = await stripe.checkout.sessions.create({
22
+ * metadata: getMetadata(req),
23
+ * });
24
+ * ```
25
+ */
26
+
27
+ export { AffiliateoProvider, useAffiliateRef } from './provider'
28
+ export { getMetadata } from './metadata'
29
+ export type { AffiliateoConfig, AffiliateoContextValue } from './types'
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Extract affiliateo cookies from an incoming request.
3
+ * Works with Next.js (App Router & Pages Router), Express, and any framework
4
+ * that exposes cookies on the request object.
5
+ *
6
+ * Returns metadata object to pass to Stripe checkout session.
7
+ */
8
+ export function getMetadata(
9
+ req: {
10
+ cookies?: Record<string, string> | { get?: (name: string) => { value: string } | undefined }
11
+ headers?: { cookie?: string } | Headers
12
+ }
13
+ ): { affiliateo_visitor_id?: string; affiliateo_ref?: string } {
14
+ const metadata: { affiliateo_visitor_id?: string; affiliateo_ref?: string } = {}
15
+
16
+ // Try Next.js App Router cookies (ReadonlyRequestCookies with .get())
17
+ if (req.cookies && typeof req.cookies === 'object' && 'get' in req.cookies && typeof req.cookies.get === 'function') {
18
+ const visitorId = req.cookies.get('affiliateo_visitor_id')
19
+ const ref = req.cookies.get('affiliateo_ref')
20
+ if (visitorId?.value) metadata.affiliateo_visitor_id = visitorId.value
21
+ if (ref?.value) metadata.affiliateo_ref = ref.value
22
+ return metadata
23
+ }
24
+
25
+ // Try plain object cookies (Express, Pages Router, etc.)
26
+ if (req.cookies && typeof req.cookies === 'object') {
27
+ const cookies = req.cookies as Record<string, string>
28
+ if (cookies.affiliateo_visitor_id) metadata.affiliateo_visitor_id = cookies.affiliateo_visitor_id
29
+ if (cookies.affiliateo_ref) metadata.affiliateo_ref = cookies.affiliateo_ref
30
+ return metadata
31
+ }
32
+
33
+ // Fallback: parse cookie header manually
34
+ const cookieHeader = req.headers instanceof Headers
35
+ ? req.headers.get('cookie')
36
+ : (req.headers as { cookie?: string })?.cookie
37
+
38
+ if (cookieHeader) {
39
+ const cookies = parseCookies(cookieHeader)
40
+ if (cookies.affiliateo_visitor_id) metadata.affiliateo_visitor_id = cookies.affiliateo_visitor_id
41
+ if (cookies.affiliateo_ref) metadata.affiliateo_ref = cookies.affiliateo_ref
42
+ }
43
+
44
+ return metadata
45
+ }
46
+
47
+ /**
48
+ * Parse a cookie header string into key-value pairs.
49
+ */
50
+ function parseCookies(cookieHeader: string): Record<string, string> {
51
+ const cookies: Record<string, string> = {}
52
+ for (const pair of cookieHeader.split(';')) {
53
+ const [key, ...rest] = pair.trim().split('=')
54
+ if (key) cookies[key.trim()] = rest.join('=').trim()
55
+ }
56
+ return cookies
57
+ }
@@ -0,0 +1,147 @@
1
+ 'use client'
2
+
3
+ import React, { createContext, useContext, useEffect, useRef, useState } from 'react'
4
+ import type { AffiliateoConfig, AffiliateoContextValue } from './types'
5
+
6
+ const DEFAULT_API_URL = 'https://affiliateo.com'
7
+
8
+ // Cookie helpers
9
+ function getCookie(name: string): string | null {
10
+ if (typeof document === 'undefined') return null
11
+ const m = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'))
12
+ return m ? m[2] : null
13
+ }
14
+
15
+ function setCookie(name: string, value: string, days: number) {
16
+ if (typeof document === 'undefined') return
17
+ const dt = new Date()
18
+ dt.setTime(dt.getTime() + days * 864e5)
19
+ document.cookie = `${name}=${value};expires=${dt.toUTCString()};path=/;SameSite=Lax`
20
+ }
21
+
22
+ // Generate UUID
23
+ function uuid(): string {
24
+ if (typeof crypto !== 'undefined' && crypto.randomUUID) return crypto.randomUUID()
25
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
26
+ const r = (Math.random() * 16) | 0
27
+ const v = c === 'x' ? r : (r & 0x3) | 0x8
28
+ return v.toString(16)
29
+ })
30
+ }
31
+
32
+ const AffiliateoContext = createContext<AffiliateoContextValue>({
33
+ refCode: null,
34
+ visitorId: null,
35
+ })
36
+
37
+ /**
38
+ * Returns the affiliate ref code and visitor ID.
39
+ */
40
+ export function useAffiliateRef(): AffiliateoContextValue {
41
+ return useContext(AffiliateoContext)
42
+ }
43
+
44
+ export function AffiliateoProvider({
45
+ siteId,
46
+ apiUrl,
47
+ children,
48
+ }: AffiliateoConfig & { children: React.ReactNode }) {
49
+ const [state, setState] = useState<AffiliateoContextValue>({
50
+ refCode: null,
51
+ visitorId: null,
52
+ })
53
+
54
+ const apiBase = (apiUrl || DEFAULT_API_URL).replace(/\/$/, '')
55
+ const siteIdRef = useRef(siteId)
56
+ const visitorIdRef = useRef<string | null>(null)
57
+ const refRef = useRef<string | null>(null)
58
+ const sentRef = useRef(false)
59
+
60
+ useEffect(() => {
61
+ if (typeof window === 'undefined') return
62
+
63
+ // Get or create visitor ID
64
+ let vid = getCookie('affiliateo_visitor_id')
65
+ if (!vid) {
66
+ vid = uuid()
67
+ }
68
+ setCookie('affiliateo_visitor_id', vid, 365)
69
+ visitorIdRef.current = vid
70
+
71
+ // Read ref from URL params
72
+ const params = new URLSearchParams(window.location.search)
73
+ const ref = params.get('ref') || params.get('via') || params.get('source') || getCookie('affiliateo_ref') || null
74
+ if (ref) {
75
+ setCookie('affiliateo_ref', ref, 365)
76
+ }
77
+ refRef.current = ref
78
+
79
+ setState({ refCode: ref, visitorId: vid })
80
+
81
+ // Send event helper
82
+ function send(type: string) {
83
+ const payload = {
84
+ type,
85
+ site_id: siteIdRef.current,
86
+ visitor_id: visitorIdRef.current,
87
+ url: window.location.href,
88
+ path: window.location.pathname,
89
+ hostname: window.location.hostname,
90
+ referrer: document.referrer || '',
91
+ ref: refRef.current || '',
92
+ utm_source: params.get('utm_source') || '',
93
+ utm_medium: params.get('utm_medium') || '',
94
+ utm_campaign: params.get('utm_campaign') || '',
95
+ screen_width: window.innerWidth,
96
+ }
97
+ const data = JSON.stringify(payload)
98
+ if (navigator.sendBeacon) {
99
+ navigator.sendBeacon(`${apiBase}/api/v1/event`, new Blob([data], { type: 'application/json' }))
100
+ } else {
101
+ fetch(`${apiBase}/api/v1/event`, {
102
+ method: 'POST',
103
+ headers: { 'Content-Type': 'application/json' },
104
+ body: data,
105
+ keepalive: true,
106
+ }).catch(() => {})
107
+ }
108
+ }
109
+
110
+ // Send initial session_start
111
+ if (!sentRef.current) {
112
+ sentRef.current = true
113
+ send('session_start')
114
+ }
115
+
116
+ // Visibility change — session_start/session_end
117
+ let left = false
118
+ function onLeave() {
119
+ if (left) return
120
+ left = true
121
+ send('session_end')
122
+ }
123
+
124
+ function onVisibilityChange() {
125
+ if (document.visibilityState === 'hidden') {
126
+ onLeave()
127
+ } else if (document.visibilityState === 'visible') {
128
+ left = false
129
+ send('session_start')
130
+ }
131
+ }
132
+
133
+ document.addEventListener('visibilitychange', onVisibilityChange)
134
+ window.addEventListener('beforeunload', onLeave)
135
+
136
+ return () => {
137
+ document.removeEventListener('visibilitychange', onVisibilityChange)
138
+ window.removeEventListener('beforeunload', onLeave)
139
+ }
140
+ }, [siteId, apiBase])
141
+
142
+ return (
143
+ <AffiliateoContext.Provider value={state}>
144
+ {children}
145
+ </AffiliateoContext.Provider>
146
+ )
147
+ }
package/src/types.ts ADDED
@@ -0,0 +1,12 @@
1
+ export interface AffiliateoConfig {
2
+ siteId: string
3
+ /** Base URL for the API. Defaults to https://affiliateo.com */
4
+ apiUrl?: string
5
+ }
6
+
7
+ export interface AffiliateoContextValue {
8
+ /** The affiliate ref code from the URL (if present) */
9
+ refCode: string | null
10
+ /** The visitor ID stored in the cookie */
11
+ visitorId: string | null
12
+ }