@arthurreira/analytics 0.8.0 → 0.10.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/dist/client.d.ts +5 -2
- package/dist/client.js +66 -3
- package/dist/index.d.ts +7 -1
- package/dist/index.js +56 -0
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
interface AnalyticsProps {
|
|
2
2
|
apiKey: string;
|
|
3
3
|
apiUrl: string;
|
|
4
|
+
wsUrl?: string;
|
|
4
5
|
}
|
|
5
|
-
declare function Analytics({ apiKey, apiUrl }: AnalyticsProps): null;
|
|
6
|
+
declare function Analytics({ apiKey, apiUrl, wsUrl }: AnalyticsProps): null;
|
|
6
7
|
|
|
7
|
-
declare function useAnalytics(apiUrl: string, apiKey: string
|
|
8
|
+
declare function useAnalytics(apiUrl: string, apiKey: string, options?: {
|
|
9
|
+
wsUrl?: string;
|
|
10
|
+
}): {
|
|
8
11
|
trackPageview: (path: string) => void;
|
|
9
12
|
trackClick: (e: MouseEvent, element: HTMLElement) => void;
|
|
10
13
|
trackScroll: (depth: number) => void;
|
package/dist/client.js
CHANGED
|
@@ -124,6 +124,60 @@ async function trackCTA(apiUrl, apiKey, sessionId, path, ctaId, ctaVariant) {
|
|
|
124
124
|
});
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
// src/lib/presence.ts
|
|
128
|
+
var DEFAULT_WS_URL = "wss://edge.arthurreira.dev/realtime";
|
|
129
|
+
function connectPresence(apiKey, sessionId, wsUrl = DEFAULT_WS_URL) {
|
|
130
|
+
if (typeof WebSocket === "undefined") {
|
|
131
|
+
return { disconnect: () => {
|
|
132
|
+
} };
|
|
133
|
+
}
|
|
134
|
+
let ws = null;
|
|
135
|
+
let attempts = 0;
|
|
136
|
+
let intentionalClose = false;
|
|
137
|
+
let retryTimer = null;
|
|
138
|
+
let targetUrl;
|
|
139
|
+
try {
|
|
140
|
+
const u = new URL(wsUrl);
|
|
141
|
+
u.searchParams.set("api_key", apiKey);
|
|
142
|
+
u.searchParams.set("session_id", sessionId);
|
|
143
|
+
targetUrl = u.toString();
|
|
144
|
+
} catch {
|
|
145
|
+
return { disconnect: () => {
|
|
146
|
+
} };
|
|
147
|
+
}
|
|
148
|
+
function connect() {
|
|
149
|
+
try {
|
|
150
|
+
ws = new WebSocket(targetUrl);
|
|
151
|
+
} catch {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
ws.onopen = () => {
|
|
155
|
+
attempts = 0;
|
|
156
|
+
ws.send(JSON.stringify({ type: "join", api_key: apiKey, session_id: sessionId }));
|
|
157
|
+
};
|
|
158
|
+
ws.onclose = () => {
|
|
159
|
+
if (intentionalClose || attempts >= 3) return;
|
|
160
|
+
const delay = Math.pow(2, attempts) * 1e3;
|
|
161
|
+
attempts++;
|
|
162
|
+
retryTimer = setTimeout(connect, delay);
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
connect();
|
|
166
|
+
return {
|
|
167
|
+
disconnect: () => {
|
|
168
|
+
intentionalClose = true;
|
|
169
|
+
if (retryTimer !== null) {
|
|
170
|
+
clearTimeout(retryTimer);
|
|
171
|
+
retryTimer = null;
|
|
172
|
+
}
|
|
173
|
+
if (ws) {
|
|
174
|
+
ws.close();
|
|
175
|
+
ws = null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
127
181
|
// src/hooks/useAnalytics.ts
|
|
128
182
|
var SESSION_EXPIRY_MINUTES = 30;
|
|
129
183
|
async function getOrCreateSession(apiUrl, apiKey, visitorId) {
|
|
@@ -142,24 +196,33 @@ async function getOrCreateSession(apiUrl, apiKey, visitorId) {
|
|
|
142
196
|
localStorage.setItem("af_session_last_activity", String(now));
|
|
143
197
|
return newSessionId;
|
|
144
198
|
}
|
|
145
|
-
function useAnalytics(apiUrl, apiKey) {
|
|
199
|
+
function useAnalytics(apiUrl, apiKey, options) {
|
|
146
200
|
const sessionId = useRef(null);
|
|
147
201
|
const pendingEvents = useRef([]);
|
|
148
202
|
const pathname = useRef(typeof window !== "undefined" ? window?.location?.pathname ?? "" : "");
|
|
203
|
+
const presence = useRef(null);
|
|
149
204
|
useEffect(() => {
|
|
150
205
|
if (typeof window === "undefined") return;
|
|
151
206
|
const visitor_id = localStorage.getItem("af_analytics_visitor_id") || crypto.randomUUID();
|
|
152
207
|
localStorage.setItem("af_analytics_visitor_id", visitor_id);
|
|
208
|
+
let cancelled = false;
|
|
153
209
|
getOrCreateSession(apiUrl, apiKey, visitor_id).then((id) => {
|
|
210
|
+
if (cancelled) return;
|
|
154
211
|
sessionId.current = id;
|
|
212
|
+
presence.current = connectPresence(apiKey, id, options?.wsUrl);
|
|
155
213
|
pendingEvents.current.forEach((fn) => fn());
|
|
156
214
|
pendingEvents.current = [];
|
|
157
215
|
});
|
|
216
|
+
return () => {
|
|
217
|
+
cancelled = true;
|
|
218
|
+
};
|
|
158
219
|
}, []);
|
|
159
220
|
useEffect(() => {
|
|
160
221
|
if (typeof window === "undefined") return;
|
|
161
222
|
const sendEnd = () => {
|
|
162
223
|
if (!sessionId.current) return;
|
|
224
|
+
presence.current?.disconnect();
|
|
225
|
+
presence.current = null;
|
|
163
226
|
navigator.sendBeacon(`${apiUrl}/sessions/${sessionId.current}/end`);
|
|
164
227
|
localStorage.removeItem("af_session_id");
|
|
165
228
|
localStorage.removeItem("af_session_last_activity");
|
|
@@ -209,8 +272,8 @@ function useAnalytics(apiUrl, apiKey) {
|
|
|
209
272
|
}
|
|
210
273
|
|
|
211
274
|
// src/components/Analytics.tsx
|
|
212
|
-
function Analytics({ apiKey, apiUrl }) {
|
|
213
|
-
const { trackPageview: trackPageview2, trackClick: trackClick2, trackScroll: trackScroll2, trackCopy: trackCopy2, trackError: trackError2 } = useAnalytics(apiUrl, apiKey);
|
|
275
|
+
function Analytics({ apiKey, apiUrl, wsUrl }) {
|
|
276
|
+
const { trackPageview: trackPageview2, trackClick: trackClick2, trackScroll: trackScroll2, trackCopy: trackCopy2, trackError: trackError2 } = useAnalytics(apiUrl, apiKey, { wsUrl });
|
|
214
277
|
const lastTracked = useRef2(null);
|
|
215
278
|
const lastScrollDepth = useRef2(0);
|
|
216
279
|
const pathname = typeof window !== "undefined" ? window.location.pathname : "";
|
package/dist/index.d.ts
CHANGED
|
@@ -7,4 +7,10 @@ declare function trackSearch(apiUrl: string, apiKey: string, sessionId: string,
|
|
|
7
7
|
declare function trackError(apiUrl: string, apiKey: string, sessionId: string, path: string, error: Error): Promise<void>;
|
|
8
8
|
declare function trackCTA(apiUrl: string, apiKey: string, sessionId: string, path: string, ctaId: string, ctaVariant?: string): Promise<void>;
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
declare const DEFAULT_WS_URL = "wss://edge.arthurreira.dev/realtime";
|
|
11
|
+
interface PresenceConnection {
|
|
12
|
+
disconnect: () => void;
|
|
13
|
+
}
|
|
14
|
+
declare function connectPresence(apiKey: string, sessionId: string, wsUrl?: string): PresenceConnection;
|
|
15
|
+
|
|
16
|
+
export { DEFAULT_WS_URL, type PresenceConnection, connectPresence, createSession, trackCTA, trackClick, trackCopy, trackError, trackPageview, trackScroll, trackSearch };
|
package/dist/index.js
CHANGED
|
@@ -114,7 +114,63 @@ async function trackCTA(apiUrl, apiKey, sessionId, path, ctaId, ctaVariant) {
|
|
|
114
114
|
cta_variant: ctaVariant || null
|
|
115
115
|
});
|
|
116
116
|
}
|
|
117
|
+
|
|
118
|
+
// src/lib/presence.ts
|
|
119
|
+
var DEFAULT_WS_URL = "wss://edge.arthurreira.dev/realtime";
|
|
120
|
+
function connectPresence(apiKey, sessionId, wsUrl = DEFAULT_WS_URL) {
|
|
121
|
+
if (typeof WebSocket === "undefined") {
|
|
122
|
+
return { disconnect: () => {
|
|
123
|
+
} };
|
|
124
|
+
}
|
|
125
|
+
let ws = null;
|
|
126
|
+
let attempts = 0;
|
|
127
|
+
let intentionalClose = false;
|
|
128
|
+
let retryTimer = null;
|
|
129
|
+
let targetUrl;
|
|
130
|
+
try {
|
|
131
|
+
const u = new URL(wsUrl);
|
|
132
|
+
u.searchParams.set("api_key", apiKey);
|
|
133
|
+
u.searchParams.set("session_id", sessionId);
|
|
134
|
+
targetUrl = u.toString();
|
|
135
|
+
} catch {
|
|
136
|
+
return { disconnect: () => {
|
|
137
|
+
} };
|
|
138
|
+
}
|
|
139
|
+
function connect() {
|
|
140
|
+
try {
|
|
141
|
+
ws = new WebSocket(targetUrl);
|
|
142
|
+
} catch {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
ws.onopen = () => {
|
|
146
|
+
attempts = 0;
|
|
147
|
+
ws.send(JSON.stringify({ type: "join", api_key: apiKey, session_id: sessionId }));
|
|
148
|
+
};
|
|
149
|
+
ws.onclose = () => {
|
|
150
|
+
if (intentionalClose || attempts >= 3) return;
|
|
151
|
+
const delay = Math.pow(2, attempts) * 1e3;
|
|
152
|
+
attempts++;
|
|
153
|
+
retryTimer = setTimeout(connect, delay);
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
connect();
|
|
157
|
+
return {
|
|
158
|
+
disconnect: () => {
|
|
159
|
+
intentionalClose = true;
|
|
160
|
+
if (retryTimer !== null) {
|
|
161
|
+
clearTimeout(retryTimer);
|
|
162
|
+
retryTimer = null;
|
|
163
|
+
}
|
|
164
|
+
if (ws) {
|
|
165
|
+
ws.close();
|
|
166
|
+
ws = null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
117
171
|
export {
|
|
172
|
+
DEFAULT_WS_URL,
|
|
173
|
+
connectPresence,
|
|
118
174
|
createSession,
|
|
119
175
|
trackCTA,
|
|
120
176
|
trackClick,
|
package/package.json
CHANGED